Compare commits

...

202 Commits

Author SHA1 Message Date
Ian Lapham
72678ee667 Update token list (#3520) 2022-03-15 10:53:20 -04:00
Zach Pomerantz
4dd74f2144 fix: token img overflow (#3517) 2022-03-15 08:01:32 -05:00
Zach Pomerantz
e45c104135 fix: computed token img (#3519) 2022-03-15 08:00:46 -05:00
Crowdin Bot
98fcaacd9b chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-15 09:06:57 +00:00
Crowdin Bot
93551579e4 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-14 20:11:46 +00:00
Zach Pomerantz
8a9388ed81 feat: make Expando element (#3515)
* feat: make Expando element

* fix: cleanup

* fix: simplify margin

* fix: summary height

* fix: special case gap transition
2022-03-14 12:03:40 -07:00
Ian Lapham
884bf41da7 fix: add check for undefined slot0 and liquidity call results (#3513)
* add check for undefined slot0 and liquidity call results

* update null check

* update code order
2022-03-14 14:02:03 -04:00
Zach Pomerantz
5b686aea97 fix: exhaustive dep (#3514) 2022-03-14 10:03:19 -07:00
Zach Pomerantz
c4a456a085 chore: simplify Price in its own component (#3508)
* chore: simplify Price in its own component

* fix: restore reversal

* fix: output usdc naming
2022-03-14 09:54:04 -07:00
Zach Pomerantz
4b9098a7bf fix: token button overflow (#3504)
Removes the overflow property from TokenButton, which prevents the vertical scrollbar from rendering.
2022-03-14 09:42:38 -07:00
Zach Pomerantz
71a246f25c fix: expando icon direction (#3507) 2022-03-14 09:42:24 -07:00
Zach Pomerantz
8de048bc84 fix: input amount lag (#3503)
Propagates the exact amount to the trade currency amount without waiting for the trade to be computed. This allows things like insufficient balance or approval to be reflected in the UI while typing, instead of having a lag.
2022-03-13 22:45:14 -07:00
Zach Pomerantz
163e2d5560 fix: override Rule width (#3502)
* fix: override Rule width

* fix: include max-width
2022-03-11 17:26:02 -06:00
Zach Pomerantz
0edb0fe5e2 fix: memoize on-chain results (#3493)
* fix: memo-ize onchain results

* fix: typeof omission
2022-03-11 14:56:05 -08:00
Zach Pomerantz
496408b3db fix: token list fetching performance (#3480)
* fix: token list fetching

* fix: simplify naming
2022-03-11 11:57:49 -08:00
Zach Pomerantz
78b6f5c72d fix: destructure currencies for ref equality (#3498) 2022-03-11 11:25:17 -08:00
Zach Pomerantz
f9fb71a803 fix(perf): cache pools to avoid cost of instantiation (#3499) 2022-03-11 08:20:16 -08:00
Crowdin Bot
59d0046411 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-11 03:07:34 +00:00
Zach Pomerantz
b4e0234d07 fix: avoid summary wrap (#3497) 2022-03-10 15:59:45 -08:00
Zach Pomerantz
4a8dbda0b8 fix: memoize nested components (#3483) 2022-03-10 15:48:45 -08:00
Crowdin Bot
0cbb24c614 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-10 23:06:32 +00:00
Ian Lapham
a9dba258ff fix: update wrap fail state callback and input values (#3495)
* update wrap fail state callback and input values

* add error check for wrap status

* update error catching logic

* update error catching
2022-03-10 17:07:52 -05:00
Zach Pomerantz
fa163cb938 feat: price impact update (#3496)
* feat: green text on neg price impact

* chore: propagate all of usdc price impact

* chore: pass price impact to summary details

* chore: propagate slippage and impact warnings

* feat: update warnings on summary dialog

* chore: rm todo
2022-03-10 13:07:37 -08:00
Curly Brackets
b52273932a fix(ui): Auto Router icon not appearing on safari/ios (#3444)
* fix(ui): Auto Router icon not appearing on safari/ios

Closes #3138

* refactor: remove auto-router svg that are no longer in use
2022-03-10 14:37:32 -05:00
Crowdin Bot
9ad8f80e4e chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-10 18:07:32 +00:00
Zach Pomerantz
69bc598dea feat: display "no results found" message (#3467)
* feat: display when no results

* chore: use body not subhead
2022-03-10 09:53:56 -08:00
Ian Lapham
7feba045fc fix: add formatting to USDC values (#3478)
* add formatting to USDC values

* update formatting of USDC amounts to en

* apply same formatting to summary
2022-03-10 12:38:59 -05:00
Zach Pomerantz
04cee0a07d chore: bump to v0.0.23-beta (#3492) 2022-03-10 09:02:17 -08:00
Zach Pomerantz
ea73260e56 fix: accomodate longer locales (#3468) 2022-03-10 08:57:36 -08:00
Zach Pomerantz
b4bd2973a9 fix: update input when prefixing 0 to decimal (#3487) 2022-03-10 08:56:06 -08:00
Zach Pomerantz
a071b8adb0 fix: summary line break (#3486) 2022-03-10 08:55:55 -08:00
Zach Pomerantz
610acb0191 fix: font family overrides (#3485) 2022-03-10 08:55:40 -08:00
Zach Pomerantz
63bad8f890 fix: summary expando height (#3484) 2022-03-10 08:54:42 -08:00
Zach Pomerantz
32f955693f fix: restructure web3 to memoize (#3472) 2022-03-09 15:08:04 -08:00
Zach Pomerantz
96c66a5846 fix: incorrectly memoized hooks (#3471)
* fix: incorrectly memoized hooks

* fix: finish memoizing useUSDCPrice
2022-03-09 12:33:35 -08:00
Sara Reynolds
8c269a6d39 feat: add optimism and arbitrum to gas estimate support (#3434)
* add optimism and arbitrum to gas estimate support

* update sor version

* move to dependencies
2022-03-09 14:52:19 -05:00
Justin Domingue
36f111fa6f chore: upgrade to redux-toolkit 1.8 (#3464) 2022-03-09 12:19:24 -05:00
Ian Lapham
e569dc2152 fix: update default tokens on unsupported networks (#3470)
* update default tokens on unsupported networks

* update supported network with hook

* update defaults

* fix on default input token
2022-03-08 20:35:23 -05:00
Crowdin Bot
1aa042c5ef chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-08 21:07:02 +00:00
Zach Pomerantz
1450315b98 chore: bump to v0.0.21-beta (#3466) 2022-03-08 09:54:59 -08:00
Zach Pomerantz
aefbb3d812 feat: price impact warning (#3437)
* chore: mv usdc price impact to hook

* fix: popover padding

* feat: warn on high price impact from toolbar

* fix: display price impact on warning too

* chore: rename useUSDCValue params

* fix: conform uses of price impact color
2022-03-08 09:53:40 -08:00
Moody Salem
c3f12398cd fix: reverts the defaulting behavior of 1 eth to usdc (#3465) 2022-03-08 11:31:52 -05:00
Zach Pomerantz
2272f2a01a chore: specify a module for CRAv4 (#3462) 2022-03-08 08:19:56 -08:00
Zach Pomerantz
fb71078ea2 fix: fully specify locales (#3461) 2022-03-08 08:19:31 -08:00
Zach Pomerantz
1c7c93191e fix: default input to native currency (#3456)
* fix: default to native

* chore: fix import
2022-03-08 08:19:23 -08:00
Zach Pomerantz
0713f730b3 fix: widget sizing (#3455) 2022-03-08 08:19:12 -08:00
Crowdin Bot
5f7a18b411 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-08 16:07:25 +00:00
Curly Brackets
020c8d181a fix(ui): display wrapped currency symbol with current chain (#3441) 2022-03-08 09:06:31 -06:00
Zach Pomerantz
ab3f024031 fix: display loaded input amount (#3451) 2022-03-08 08:57:51 -06:00
Ian Lapham
d989c61de5 fix: update type on wrapped native currency (#3454)
* update wrapped native currency type

* fix build error on base check

* update type on mapping to permit undefined values

* undo unneeded changes

* update filter check
2022-03-07 21:04:21 -05:00
Zach Pomerantz
5dd8059734 fix: routing tooltip styles (#3459) 2022-03-07 15:08:01 -08:00
Zach Pomerantz
b50e5511ea chore: update loading colors (#3438)
* chore: update loading colors

* fix: loading opacity of 0.6
2022-03-07 09:40:15 -08:00
Crowdin Bot
1efe5e9cd5 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-06 15:06:35 +00:00
Zach Pomerantz
2944dc4d0b chore: bump to v0.0.21-beta (#3439) 2022-03-04 14:30:25 -08:00
Jordan Frankfurt
29ae755f2a add new token image to widget (#3433) 2022-03-04 11:14:31 -06:00
Zach Pomerantz
27b831b301 chore: convert inline spinner to svg (#3432)
* chore: convert inline spinner to svg

* feat: inline spinner outline
2022-03-04 08:59:09 -08:00
Ian Lapham
6d9d38819e feat: update loading icon (#3428)
* update loading icon

* color update, update file for spinner

* update loader styling size

* update file naming for spinner
2022-03-04 11:07:02 -05:00
Zach Pomerantz
2de29129ed feat: export types (#3411)
* feat: export used types

* fix: clean defaulting names

* chore: rename to useSyncTokenDefaults
2022-03-03 14:21:03 -08:00
Crowdin Bot
52af0e506b chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-03 22:07:03 +00:00
Noah Zinsmeister
4d69c946bf fix: Improve Vote page (#3429)
* introduce useQuorum

introduce LATEST_GOVERNOR_INDEX

* use CurrencyAmounts and fix % logic

* gate useQuorum to mainnet, just to be safe

* comment
2022-03-03 16:27:59 -05:00
Zach Pomerantz
542bf0bf66 feat: user select (#3410)
* feat: make data user-selectable

* fix: consider the whole node for focus

* fix: back out lineheight typing

* fix: straggling occurences

* chore: comment on root user-select
2022-03-03 11:09:12 -08:00
Zach Pomerantz
a4fbfae4ba fix: set appropriate types to nowrap (#3392) 2022-03-03 10:39:44 -08:00
Crowdin Bot
b2288258f2 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-03 18:11:42 +00:00
Ian Lapham
8703013b2d feat: use permit when valid on approvals (#3354)
* use permit when valid on approvals

* fix broken check for permit sig

* update conditionals

* update text

* remove unneeded else

* move permit and approve logic to combined hook

* update comment

* split txn and approval state, code clean

* organize disable conditions

* small changes

* update conditional check
2022-03-03 12:28:36 -05:00
Crowdin Bot
4f6173675d chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-03 09:11:25 +00:00
Crowdin Bot
2469eb58b9 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-03 07:14:57 +00:00
Crowdin Bot
e0a8ac2408 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-02 23:26:37 +00:00
Ian Lapham
0a736b5e62 feat: create donation banner (#3403)
* create donation banner

* update styling and copy

* only show banner on swap page

* dont use hard coded z index

* style updates
2022-03-02 17:48:12 -05:00
Jordan Frankfurt
b44eb8877c chore(widgets): export theme stuff from widget (#3409) 2022-03-02 14:20:55 -08:00
Zach Pomerantz
92e61fa34b fix: token balance reloading (#3408)
* fix: layout token balance on token select

* fix: do not clear block on window re-focus
2022-03-02 13:39:18 -08:00
Zach Pomerantz
ef62fd33b2 fix: error dialog transition (#3405) 2022-03-02 13:37:57 -08:00
Jordan Frankfurt
96a42f66d4 fix(widgets): disable interactions and don't show summary screen on unsupported networks (#3376)
* disable interactions and don't show summary screen on unsupported networks

* clear input values on unsupported network

* pr feedback

* pr feedback

* simpler active trade clearing

* rename some things
2022-03-02 13:45:31 -06:00
Crowdin Bot
c446f20d2f chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-02 19:11:16 +00:00
Jordan Frankfurt
5a1ef8fb7d feat(widgets): support wrapping native assets (#3301)
* feat(widgets): support wrapping native assets

* integrate wrap with swapInfo, start a useWrapCallback hook

* add loading state

* add pending state to (un)wrap transactions

* final cleanup

* janky merge conflict fix--disregard! this will change

* fixed

* 💢

* pr feedback

* z's pr feedback

* pr feedback

* zzmp pr feedback

* zzmp pr feedback
2022-03-02 12:36:35 -06:00
Zach Pomerantz
2863971640 fix: routing tooltip sizing (#3391) 2022-03-02 08:54:15 -08:00
Crowdin Bot
dcaf10ec29 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-02 07:31:50 +00:00
Zach Pomerantz
bca5113569 fix: connect wallet button (#3324)
* fix: make dialog provider an isolate

* fix: use button for connect wallet

* chore: document esoteric changes
2022-03-01 14:32:54 -08:00
David Walsh
6779c1a024 Fix #3117 - Open networks menu upon hover (#3378) 2022-03-01 14:45:22 -05:00
Ori Pomerantz
f79ef12494 fix(icons): Updated one of the Optimism images (#3357) 2022-03-01 14:37:18 -05:00
dependabot[bot]
7bcda46934 chore(deps): bump url-parse from 1.5.3 to 1.5.10 (#3371)
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.3 to 1.5.10.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.3...1.5.10)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-01 14:35:54 -05:00
dependabot[bot]
f4ba24cfd5 chore(deps): bump nanoid from 3.1.28 to 3.2.0 (#3173)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.28 to 3.2.0.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.28...3.2.0)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-01 14:35:01 -05:00
Zach Pomerantz
59c6ab16dd fix: exports (#3379)
* fix: cjs in cra

* chore: wrap cjs for mjs

* chore: export cjs and esm

* chore: export chunks

* fix: correctly export locales

* chore: note exports compat

* fix: use cjs for common deps

* chore: bump to v0.0.20-beta

* chore: rm outdated comment
2022-03-01 11:32:55 -08:00
Ian Lapham
db17dcbf2c list update (#3389) 2022-03-01 14:05:13 -05:00
Moody Salem
1835de7f5f fix: the vote page could not render proposals without signature data
fixes https://github.com/Uniswap/interface/issues/3380
2022-02-28 18:16:24 -05:00
Crowdin Bot
00f158209c chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-28 19:11:43 +00:00
Zach Pomerantz
2108ceedd5 chore: reduce token list block period (#3353) 2022-02-28 10:47:36 -08:00
Zach Pomerantz
ad080470da fix: use explicit value in Plural tag (#3363)
one is non-functional (I don't know why), but _1 seems to work 🤷
2022-02-28 10:41:15 -08:00
Moody Salem
fc34912b53 feat(swap): default 1 native to usdc on the swap page (#3347)
* feat(swap): default 1 eth to usdc on the swap page

* fix unit tests

* fix tests

* fix the issue better

* use the token list logo

* fix integration tests for swap and add one for eth/usdc

* address comments
2022-02-28 13:30:37 -05:00
jaclyn
c25d2b894c fix: update walletlink-connector to 6.2.13 (#3315)
* fix: update walletlink-connector to 6.2.12

* fix: update walletlink-connector to 6.2.13

Co-authored-by: Jaclyn Chan <jaclyn.chan@coinbase.com>
2022-02-28 11:13:36 -05:00
Moody Salem
83c99b8c04 chore: remove unused exports and unused integration test build env variable 2022-02-28 11:03:05 -05:00
Zach Pomerantz
ccdf1e7575 fix: convert to commonjs (#3373)
* chore: use fully specified path for non-main imports

* chore: convert to commonjs

- Pin @web3-react to exact cjs versions

* chore: bump to v0.0.18-beta

* chore: fix fonts.css comment

* fix: @ethersproject/providers version skew

* fix: @web3-react state typings
2022-02-28 07:52:22 -08:00
Crowdin Bot
c9faafee5e chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-25 08:08:05 +00:00
Zach Pomerantz
26a44fb51b chore: bump to v0.0.15-beta (#3358) 2022-02-24 11:36:00 -08:00
Zach Pomerantz
1e16ac8449 fix: wrap Eip1193Bridge to fix outstanding bugs (#3355)
* fix: wrap Eip1193Bridge to fix outstanding bugs

* revert: widget height change
2022-02-24 11:20:09 -08:00
Crowdin Bot
5b5e76573d chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-24 09:06:47 +00:00
Crowdin Bot
27cdbd0d5f chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-23 23:06:39 +00:00
Zach Pomerantz
b2a30b9bf1 fix: style nits (#3352)
* fix: prefix output usdc with $

* fix: space in output estimate warning

* fix: reset slippage warnings

* fix: display link icon for etherscan
2022-02-23 14:56:49 -08:00
Zach Pomerantz
dfad7b89ab chore: nextjs compatibility (#3351)
* fix: esm/cjs exports

* fix: guard visibility state WebAPI

* fix: nextjs styled-components imports

* fix: add ethers' optional deps

* fix: document access in useHasFocus

* fix: suffix babel-plugin-macros config for commonjs

* chore: rollup sourcemaps

* chore: export fonts separately

* chore: mv redux to peer dep

* fix: run tests off cjs config

* fix: run cosmos off cjs config
2022-02-23 13:12:07 -08:00
Zach Pomerantz
4fe35ea42e fix: max on WebKit (#3349)
* chore: add walletconnect to cosmos

* fix: onClickMax for TokenInput

* chore: add setImmediate
2022-02-23 12:50:22 -08:00
Zach Pomerantz
0d852b6165 fix: apply user-select to widget, not to constituents (#3345) 2022-02-23 12:49:59 -08:00
Zach Pomerantz
8ac3b836bd fix: slippage input size (#3350) 2022-02-23 12:25:17 -08:00
Crowdin Bot
12bc5957b4 chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-23 20:06:40 +00:00
Ian Lapham
a33187c33b feat: routing tooltip for widget (#3259)
* start file updates for routing tooltip

* start tooltip UI

* fix styling

* remove use of px and add header

* UI updates;

* update styles

* update file structure

* update routing components and type

* small code fixes

* fix broken sizing bug

* nit fixes
2022-02-23 14:22:21 -05:00
Zach Pomerantz
248bc07cf1 fix: lazily instantiate supported router providers (#3348)
* fix: iterate over enum values

* fix: lazily instantiate router providers
2022-02-23 10:46:29 -08:00
Crowdin Bot
369f8c94e3 chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-18 00:13:06 +00:00
Crowdin Bot
de5f0541ee chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-17 23:07:10 +00:00
Zach Pomerantz
48b3efc612 chore: bump to v0.0.14-beta (#3333) 2022-02-17 12:03:44 -08:00
Zach Pomerantz
90c59f31f3 fix: return eip1193bridge chainId as hexa (#3332) 2022-02-17 12:03:35 -08:00
Zach Pomerantz
0e709c257b fix: import fonts through sass only (#3331) 2022-02-17 10:49:47 -08:00
Crowdin Bot
7a3bb8de1d chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-17 18:09:07 +00:00
Crowdin Bot
8018d1b9dc chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-17 15:11:21 +00:00
Zach Pomerantz
1297aa57d3 chore: bump to v0.0.13-beta (#3328) 2022-02-16 17:00:43 -08:00
Zach Pomerantz
30e30189e1 fix: use a void signer for ethers providers (#3327) 2022-02-16 17:00:31 -08:00
Zach Pomerantz
6a602cf6d7 chore: bump to v0.0.12-beta (#3325) 2022-02-16 14:17:41 -08:00
Zach Pomerantz
4c966caa2a feat: accept {provider, signer} as ethers provider (#3314) 2022-02-16 11:11:45 -08:00
Zach Pomerantz
a60ea703b0 fix: token select ux (#3321) 2022-02-16 11:11:10 -08:00
Zach Pomerantz
ae664dc264 fix: compute insufficient balance and approval off of input (#3312) 2022-02-16 11:10:40 -08:00
Zach Pomerantz
b152b11515 fix: token select input handling (#3303) 2022-02-15 19:35:14 -08:00
Zach Pomerantz
0f51991109 feat: improved warning ux (#3310) 2022-02-15 19:34:02 -08:00
Zach Pomerantz
da8884d87d fix: action prop warning (#3304) 2022-02-15 19:33:29 -08:00
Zach Pomerantz
79bdc0c5ee fix: summary details heights (#3302) 2022-02-15 19:33:09 -08:00
Zach Pomerantz
82c30681ea fix: ignore stale SOR fetches (#3313)
* fix: propagate ROUTE_NOT_FOUND and fallback appropriately

* fix: display insufficient liquidities

* fix: ignore stale SOR results

* fix: retain trade state while loading

* fix: mv debouncing to SOR logic for sync state
2022-02-14 18:23:55 -08:00
Ian Lapham
41ef961679 feat: optimize client side SOR for widgets (#3294)
* start SOR updates

* update pool providers to static

* update router config

* remove log

* udpate defaults for chainId

* small changes
2022-02-14 13:35:05 -07:00
Zach Pomerantz
7de63ab462 feat: focus and hover hooks (#3287)
* feat: add focus/hover hooks

* refactor: use focus/hover hooks
2022-02-14 06:32:11 -08:00
Crowdin Bot
59c5989721 chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-12 15:07:37 +00:00
Zach Pomerantz
b042d2b3b4 fix: i18n resolution (#3299) 2022-02-11 10:34:03 -08:00
Zach Pomerantz
897e7f4581 fix: approval action button (#3297) 2022-02-11 08:47:45 -08:00
Zach Pomerantz
a7fb7dc906 chore: v0.0.11-beta 2022-02-11 08:13:06 -08:00
Crowdin Bot
5fe89b9d6c chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-11 04:07:20 +00:00
Zach Pomerantz
acbcd3763c style: update type sizes (#3292) 2022-02-10 20:37:58 -07:00
Zach Pomerantz
01c467b48c fix: max button flashing on tabbing (#3291) 2022-02-10 20:37:13 -07:00
Zach Pomerantz
636abe3b7b fix: respond to updated amounts immediately (#3289) 2022-02-10 20:35:34 -07:00
Zach Pomerantz
8404c6076c feat: confirm price impact (#3288)
* refactor: action button naming

* feat: high price impact acknowledgement
2022-02-10 20:33:51 -07:00
Zach Pomerantz
b4aac94c2c fix: settings ux (#3282)
* fix: max slippage warning logic

* fix: option border specificity

* fix: dialog resizing through animation

* fix: initial warning states

* fix: hide Modal class
2022-02-10 14:22:54 -08:00
Zach Pomerantz
f47fcc9c17 feat: focus input on token select (#3286) 2022-02-10 14:12:24 -08:00
Crowdin Bot
b5d27e2063 chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-10 22:06:48 +00:00
Jordan Frankfurt
26275ca580 fix(widgets): remove trading header (#3263)
* fix(widgets): remove trading header

* correct height for removed title, more scalable/consistent transform values

* 347->346 height
2022-02-10 15:59:26 -06:00
Zach Pomerantz
92b7ca8f55 feat: hide balance on blur (#3285)
* feat: hide balance when blurred

* refactor: express focused through props

* refactor: share Input/Output code
2022-02-10 13:39:08 -08:00
Jordan Frankfurt
c5ea01ce19 fix(widgets): complete etherscan link and stop timer on tx inclusion (#3267)
* fix(widgets): complete etherscan link and stop timer on tx inclusion

* use preexisting helper for etherscan link

* use z's EtherscanLink component

* pr review
2022-02-10 15:35:19 -06:00
Ian Lapham
88712b5065 fix: adjust max button for native currencies (#3279)
* adjust max button for native currencies

* update var naming
2022-02-10 12:54:01 -08:00
Zach Pomerantz
1af34ae016 feat: EtherscanA component (#3284)
* feat: EtherscanA component

* refactor: EtherscanLink from ExternalLink
2022-02-10 09:14:54 -08:00
Zach Pomerantz
9cb19dd0ea fix: filter integrator fee (#3281) 2022-02-10 08:51:39 -08:00
Zach Pomerantz
02a77254c7 fix: retain sig figs in localized nums (#3280) 2022-02-10 08:51:29 -08:00
Zach Pomerantz
69ed7015ab chore: clean up old generated css (#3277) 2022-02-10 08:51:18 -08:00
Zach Pomerantz
ff16d3f18f fix: theme colors (#3274) 2022-02-10 08:51:11 -08:00
Zach Pomerantz
5175cb6d1f fix: leave intermediate artifacts in watch (#3272) 2022-02-10 08:50:56 -08:00
Zach Pomerantz
b33686855d fix: link footer to uniswap.org (#3283) 2022-02-10 08:49:24 -08:00
Crowdin Bot
75ecc5810e chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-10 09:06:53 +00:00
Crowdin Bot
c30eb89725 chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-10 02:15:57 +00:00
Zach Pomerantz
108feace02 fix: enable max swap amount (#3278)
* fix: enable max swap amount

* fix: bad syntax

* fix: unnecessary optional
2022-02-09 17:54:27 -08:00
Zach Pomerantz
e2c013a4d8 chore: clean up old generated assets (#3276) 2022-02-09 17:31:41 -08:00
Zach Pomerantz
66308257d6 feat: connect wallet ux (#3275)
* fix: wallet styling

* feat: onConnectWallet prop
2022-02-09 17:27:26 -08:00
Zach Pomerantz
fd160531cc chore: add dts to .gitignore (#3273) 2022-02-09 15:52:31 -08:00
Zach Pomerantz
da36e638c2 fix: update max slippage state (#3268)
* fix: max slippage state

* chore: rename to useAllowedSlippage

* nit: maxSlippageInput name
2022-02-09 15:52:14 -08:00
Ian Lapham
fad55b8dbc update sig figs (#3270) 2022-02-09 15:44:15 -08:00
Zach Pomerantz
c9c59698de fix: tooltip overflow and cursor (#3271)
* fix: tooltip cursor

* fix: only clip dialog

* nit: clean up class name
2022-02-09 15:35:50 -08:00
Zach Pomerantz
828967031f fix: use greaterThan (#3269) 2022-02-09 14:23:53 -08:00
Tina
440ac0cba0 feat: track google analytics clientIds (#3264)
* store client id in localstorage

* remove newline

* use React.ga

* fix import
2022-02-09 12:01:01 -08:00
Jordan Frankfurt
b5a72cd63b fix(widgets): remove extra } in Trade caption (#3262) 2022-02-09 09:40:12 -06:00
Crowdin Bot
37f273aab4 chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-09 03:06:30 +00:00
Zach Pomerantz
3acd993ec0 chore: bundling nits (#3258)
* refactor: mv governance contracts to governance

* refactor: mv merkle contract to claim

* refactor: mv staking contract to staking

* chore: 0.0.6-beta

* chore: add @reduxjs/toolkit to peerDeps

* chore: v0.0.7-beta

* chore: add @reduxjs/toolkit to deps

* chore: v0.0.8-beta

* chore: swap web3-react aliasing

* chore: v0.0.9-beta

* chore: v0.0.10-beta
2022-02-08 16:43:14 -08:00
Crowdin Bot
58778b5775 chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-08 22:07:44 +00:00
Ian Lapham
5bc21bebc3 update summary details (#3254) 2022-02-08 13:33:31 -08:00
Crowdin Bot
c3d6727438 chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-08 20:07:57 +00:00
Zach Pomerantz
290f4bc1cb feat: bundle widgets (#3244)
* fix: tsconfig emissions

* fix: avoid importing app state in lib

* fix: export theming

* fix: asset resolutions

* fix: bundle widget

* test: update failing snapshots

* fix: maintain i18n for cosmos

* fix: use npm-compatible aliasing

* fix: include fonts in bundle
2022-02-08 11:04:48 -08:00
Jordan Frankfurt
f95275d5ac feat(widgets): Localize CurrencyAmounts and Prices (#3247)
* add basic number formatting

* test formatLocaleNumber

* localize CurrencyAmounts and Prices

* use lingui locale hook

* pr review

* cleaner type assertions

* check if locale is supported when formatting

* pr feedback
2022-02-08 12:45:40 -06:00
Jordan Frankfurt
0ec2dd4173 fix(widgets): fix broken unsupported network message (#3256)
* fix(widgets): fix broken unsupported network message

* require that the user be on a Uniswap chainId AND a token-list supported chainID before fetching swap info

* use .some instead of .reduce
2022-02-08 12:45:30 -06:00
Ian Lapham
3b3db6f6d0 fix: update widget loading state detection (#3253)
* update loading state detection

* create custom hook for amount detection
2022-02-08 10:38:48 -08:00
Jordan Frankfurt
707abd0071 feat(widgets): ensure passed locale is supported (#3245)
* ensure passed locale is supported

* warn on locale mismatch

* export SUPPORTED_LOCALES
2022-02-07 19:57:26 -06:00
Jordan Frankfurt
2efc1fb372 fix(widgets): convert widget colors from hex to hsl (#3239)
* convert widget colors from hex to hsl

* nits
2022-02-07 17:15:24 -06:00
Jordan Frankfurt
55b37825f3 fix(widgets): white accentText color on some buttons (#3238)
* white accentText color on some buttons

* put color calculations in useMemo, change accentText name, make hsl hex

* onAccent -> onAccentText
2022-02-07 16:56:10 -06:00
Ian Lapham
bb27b7a2ef feat: widget loading animations polish (#3232)
* create use best trade hook for widgets

* update comment in hook file

* add loading states to input / output fields

* update to not use imports from app

* remove custom loading component

* update var name and syncing detection logic

* fix USD div type

* simplify loading css, small changes
2022-02-07 14:38:07 -08:00
Zach Pomerantz
c595ba951b fix: isolate infura (#3241)
* fix: rm infura urls from lib

* fix: use passed providers for client SOR

* fix: clean up supported chain ids

* nit: rename params with specificity

* fix: use public rpc urls for l2

* fix: special-case rpc urls
2022-02-07 10:12:45 -08:00
Zach Pomerantz
96a122d7b8 chore: rename web3-react-alpha (#3243)
Renames widgets-web3-react/* to @widgets/web3-react/*.
npm treats nested packages as scoped, and requires scoped packages to begin with @.
2022-02-07 08:50:01 -08:00
Zach Pomerantz
610f7d3581 fix: named imports (webpack 5 compat) (#3242)
* fix: avoid json named imports

This is required by webpack 5, and is done to keep the widgets library compatible.
See https://webpack.js.org/migrate/5/#using-named-exports-from-json-modules

Note that this must be done upstream as well, in @uniswap/v3-sdk and @uniswap/router-sdk.

* chore: bump v3-sdk to avoid json named imports
2022-02-07 08:49:48 -08:00
Zach Pomerantz
781e774ce7 fix: set dialog wrapper with callback (#3240) 2022-02-04 16:08:50 -08:00
Ian Lapham
2aa1e40481 feat: create use best trade hook for widgets (#3226)
* create use best trade hook for widgets

* update comment in hook file

* refactor loading state conditional

* update logic in use best trade

* clean code in best trade hook
2022-02-04 18:38:27 -05:00
Zach Pomerantz
1c278d5012 fix: close summary after confirmation (#3233) 2022-02-03 15:04:11 -08:00
Jordan Frankfurt
a323a5c48b feat(widgets): convenience fee (#3231)
* feat(widgets): support convenience fee in trades (#3219)

* feat(widgets): support convenience fee in trades

* update call signature

* pr feedback

* set default convenience fee to undefined

* pr feedback
2022-02-03 14:48:30 -06:00
Zach Pomerantz
43931dd689 feat: chain-specific ttls (#3228) 2022-02-03 11:30:50 -08:00
Zach Pomerantz
efa3d5529c fix: only show max where appropriate (#3229) 2022-02-03 11:30:24 -08:00
Zach Pomerantz
5c0246cfc6 feat: outline tooltips (#3230) 2022-02-03 11:30:05 -08:00
Jordan Frankfurt
ee32418ff8 Revert "feat(widgets): support convenience fee in trades (#3219)" (#3224)
This reverts commit 8064dd8ede.
2022-02-03 10:46:18 -06:00
Zach Pomerantz
6e22389791 fix: slippage and price impact ux (#3222) 2022-02-03 08:23:27 -08:00
Jordan Frankfurt
8064dd8ede feat(widgets): support convenience fee in trades (#3219)
* feat(widgets): support convenience fee in trades

* update call signature

* pr feedback
2022-02-03 09:38:42 -06:00
Crowdin Bot
921310ef52 chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-03 08:06:38 +00:00
Ian Lapham
7b90fe137e update list component (#3221) 2022-02-02 17:52:38 -05:00
Ian Lapham
05b2711a8a feat: update widget with client side SOR (#3210)
* start SOR by creating custom widget hook

* update best trade hook to use SOR in widget

* update organization for client side SOR logic

* fix auto router chain id import

* remove dependency on react GA for widget

* update dependencies for SOr

* remove new useBestTrade.ts

* update loading logic for fetching hook

* update dependencies with import from ethersproject

* update import version

* add try catch on SOR usage

* code cleanup, nit fixes
2022-02-02 17:47:49 -05:00
Crowdin Bot
d060782242 chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-02 22:07:03 +00:00
Zach Pomerantz
e19e8492c9 feat: ux warnings (#3220)
* chore: mv Toolbar to a directory

* refactor: clean up Toolbar

* refactor: simplify Toolbar Caption

* feat: warn on price impact in Summary

* refactor: add computeRealizedPriceImpact util
2022-02-02 13:55:36 -08:00
Ian Lapham
800b5e0bda fix: fix pricing displays (#3214)
* fix pricing displays

* update rate logic, code clean
2022-02-02 13:43:12 -05:00
Ian Lapham
fc637071f9 update deadline signature data (#3215) 2022-02-02 12:33:00 -05:00
Moody Salem
1b78ceec10 chore: lockfile update only from the walletlink connector update 2022-02-02 00:13:29 -05:00
Moody Salem
e5be3ebf8f chore: put back the integrity hashes that were removed by the walletlink change 2022-02-02 00:12:59 -05:00
Brendan Weinstein
1c73719766 fix: update walletlink-connector to 6.2.11 (#3213) 2022-02-02 00:10:01 -05:00
Crowdin Bot
14c91f9bba chore(i18n): synchronize translations from crowdin [skip ci] 2022-02-02 00:13:11 +00:00
Zach Pomerantz
4b762ef5c9 feat: slippage warning ux (#3211)
* feat: setting input spacings

* feat: popover icon props

* fix: slippage input border

* feat: slippage input warning ux

* feat: slippage summary warning ux

* fix: summary layout

* fix: large icon compatibility

* fix: input option style

* fix: large icon compatibility

* fix: popover dimensions

* feat: tooltip hook

* fix: better max slippage popovers

* feat: error color input on invalid slippage

* fix: use default tx ttl

* fix: type userDeadline
2022-02-01 15:03:55 -08:00
Zach Pomerantz
c82b4fae64 fix: branded footer nits (#3209)
* chore: export brand color

* fix: target only children for extracted color transitions

* fix: branded footer nits
2022-01-31 14:08:39 -06:00
Zach Pomerantz
ab8c1e3e90 fix: input/output value/balance styles (#3207)
* fix: right-align balance

* fix: set min-height on text
2022-01-31 10:46:10 -08:00
Ian Lapham
7055d60406 remove survey (#3206) 2022-01-31 13:17:51 -05:00
Ian Lapham
c641cec651 update button color (#3205) 2022-01-31 13:02:58 -05:00
Brendan Weinstein
b6a47c734f fix: support networks other than ethereum mainnet for walletlink/coinbase wallet (#3202) 2022-01-31 12:03:00 -05:00
235 changed files with 9601 additions and 5546 deletions

3
.env
View File

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

View File

@@ -38,10 +38,10 @@ jobs:
run: yarn install --frozen-lockfile
- run: yarn cypress install
- run: yarn build
env:
CI: false
REACT_APP_NETWORK_URL: 'https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847'
REACT_APP_SERVICE_WORKER: false
- run: yarn test:e2e

6
.gitignore vendored
View File

@@ -17,11 +17,11 @@
# testing
/coverage
# production
# builds
/build
# widgets
/cosmos-export
/dist
/dts
# misc
.DS_Store

View File

@@ -4,6 +4,7 @@
],
"webpack": {
"configPath": "react-scripts/config/webpack.config",
"overridePath": "cosmos.override.js"
}
}
"overridePath": "cosmos.override.cjs"
},
"port": 5001
}

View File

@@ -17,6 +17,7 @@ module.exports = (webpackConfig) => ({
'process.env': {
...plugin.definitions['process.env'],
REACT_APP_IS_WIDGET: true,
REACT_APP_LOCALES: '"../locales"',
},
})
}

View File

@@ -2,16 +2,27 @@ describe('Swap', () => {
beforeEach(() => {
cy.visit('/swap')
})
it('starts with ETH selected by default', () => {
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
cy.get('#swap-currency-input .token-symbol-container').should('contain.text', 'ETH')
cy.get('#swap-currency-output .token-amount-input').should('not.have.value')
cy.get('#swap-currency-output .token-symbol-container').should('contain.text', 'Select a token')
})
it('can enter an amount into input', () => {
cy.get('#swap-currency-input .token-amount-input').type('0.001', { delay: 200 }).should('have.value', '0.001')
cy.get('#swap-currency-input .token-amount-input')
.clear()
.type('0.001', { delay: 200 })
.should('have.value', '0.001')
})
it('zero swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').type('0.0', { delay: 200 }).should('have.value', '0.0')
cy.get('#swap-currency-input .token-amount-input').clear().type('0.0', { delay: 200 }).should('have.value', '0.0')
})
it('invalid swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').type('\\', { delay: 200 }).should('have.value', '')
cy.get('#swap-currency-input .token-amount-input').clear().type('\\', { delay: 200 }).should('have.value', '')
})
it('can enter an amount into output', () => {

View File

@@ -1,31 +1,56 @@
{
"name": "@uniswap/interface",
"name": "@uniswap/widgets",
"version": "0.0.23-beta",
"description": "Uniswap Interface",
"homepage": ".",
"main": "dist/interface.js",
"module": "dist/interface.esm.js",
"types": "dist/index.d.ts",
"files": [
"lib",
"dist"
],
"private": true,
"type": "module",
"types": "dist/index.d.ts",
"main": "dist/cjs/index.cjs",
"module": "dist/index.js",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/cjs/index.cjs"
},
"./locales/*": {
"import": "./dist/locales/*.js",
"require": "./dist/cjs/locales/*.cjs"
},
"./fonts.css": {
"import": "./dist/fonts.css",
"require": "./dist/fonts.css"
}
},
"devDependencies": {
"@ethersproject/experimental": "^5.4.0",
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
"@gnosis.pm/safe-apps-web3-react": "^0.6.0",
"@graphql-codegen/cli": "1.21.5",
"@graphql-codegen/typescript": "1.22.3",
"@graphql-codegen/typescript-operations": "^1.18.2",
"@graphql-codegen/typescript-rtk-query": "^1.1.1",
"@lingui/cli": "^3.9.0",
"@lingui/core": "^3.9.0",
"@lingui/macro": "^3.9.0",
"@lingui/react": "^3.9.0",
"@metamask/jazzicon": "^2.0.0",
"@reach/dialog": "^0.10.3",
"@reach/portal": "^0.10.3",
"@react-hook/window-scroll": "^1.3.0",
"@reduxjs/toolkit": "^1.6.1",
"@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",
@@ -60,18 +85,15 @@
"@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "1.0.1",
"@uniswap/smart-order-router": "^2.5.10",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "^1.1.1",
"@web3-react/fortmatic-connector": "^6.0.9",
"@web3-react/injected-connector": "^6.0.7",
"@web3-react/portis-connector": "^6.0.9",
"@web3-react/walletconnect-connector": "^7.0.2-alpha.0",
"@web3-react/walletlink-connector": "^6.2.8",
"@web3-react/metamask": "8.0.13-beta.0",
"@web3-react/walletconnect": "8.0.18-beta.0",
"array.prototype.flat": "^1.2.4",
"array.prototype.flatmap": "^1.2.4",
"babel-plugin-macros": "^3.1.0",
"copy-to-clipboard": "^3.2.0",
"cross-env": "^7.0.3",
"cypress": "^7.7.0",
@@ -92,30 +114,45 @@
"polyfill-object.fromentries": "^1.0.1",
"prettier": "^2.2.1",
"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-is": "^17.0.2",
"react-markdown": "^4.3.1",
"react-redux": "^7.2.2",
"react-router-dom": "^5.0.0",
"react-scripts": "^4.0.3",
"react-spring": "^8.0.27",
"react-use-gesture": "^6.0.14",
"redux": "^4.1.2",
"redux-localstorage-simple": "^2.3.1",
"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",
"typechain": "^5.0.0",
"typescript": "^4.2.3",
"typescript": "^4.4.3",
"ua-parser-js": "^0.7.28",
"use-count-up": "^2.2.5",
"use-resize-observer": "^8.0.0",
"wcag-contrast": "^3.0.0",
"web-vitals": "^2.1.0",
"web3-react-abstract-connector": "npm:@web3-react/abstract-connector@^6.0.7",
"web3-react-fortmatic-connector": "npm:@web3-react/fortmatic-connector@^6.0.9",
"web3-react-injected-connector": "npm:@web3-react/injected-connector@^6.0.7",
"web3-react-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",
"workbox-core": "^6.1.0",
"workbox-precaching": "^6.1.0",
"workbox-routing": "^6.1.0"
@@ -132,10 +169,11 @@
"i18n:extract": "lingui extract --locale en-US",
"i18n:compile": "yarn i18n:extract && lingui compile",
"i18n:pseudo": "lingui extract --locale pseudo && lingui compile",
"postinstall": "yarn contracts:compile && yarn graphql:generate && yarn i18n: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.js",
"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"
@@ -154,32 +192,38 @@
},
"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.5",
"@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",
"@lingui/core": "^3.9.0",
"@lingui/macro": "^3.9.0",
"@lingui/react": "^3.9.0",
"@popperjs/core": "^2.4.4",
"@reduxjs/toolkit": "^1.6.1",
"@uniswap/redux-multicall": "^1.0.0",
"@uniswap/router-sdk": "^1.0.3",
"@uniswap/sdk-core": "^3.0.1",
"@uniswap/smart-order-router": "^2.5.20",
"@uniswap/token-lists": "^1.0.0-beta.27",
"@uniswap/v2-sdk": "^3.0.1",
"@uniswap/v3-sdk": "^3.7.1",
"@web3-react/core": "^6.0.9",
"@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",
"ajv": "^6.12.3",
"cids": "^1.0.0",
"immer": "^9.0.6",
@@ -192,24 +236,28 @@
"node-vibrant": "^3.2.1-alpha.1",
"polished": "^3.3.2",
"popper-max-size-modifier": "^0.2.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-feather": "^2.0.8",
"react-popper": "^2.2.3",
"react-redux": "^7.2.2",
"react-virtualized-auto-sizer": "^1.0.2",
"react-window": "^1.8.5",
"rebass": "^4.0.7",
"redux": "^4.1.2",
"setimmediate": "^1.0.5",
"styled-components": "^5.3.0",
"tiny-invariant": "^1.2.0",
"wcag-contrast": "^3.0.0",
"wicg-inert": "^3.1.1",
"widgets-web3-react/core": "npm:@web3-react/core@8.0.16-alpha.0",
"widgets-web3-react/eip1193": "npm:@web3-react/eip1193@8.0.16-alpha.0",
"widgets-web3-react/empty": "npm:@web3-react/empty@8.0.17-alpha.0",
"widgets-web3-react/metamask": "npm:@web3-react/metamask@8.0.16-alpha.0",
"widgets-web3-react/types": "npm:@web3-react/types@8.0.16-alpha.0",
"widgets-web3-react/url": "npm:@web3-react/url@8.0.17-alpha.0"
"web3-react-core": "npm:@web3-react/core@^6.0.9",
"wicg-inert": "^3.1.1"
},
"peerDependencies": {
"@babel/runtime": "^7.17.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.2.2",
"redux": "^4.1.2"
},
"optionalDependencies": {
"bufferutil": "^4.0.6",
"encoding": "^0.1.13",
"utf-8-validate": "^5.0.8"
}
}

View File

@@ -3,61 +3,174 @@
* This library lives in src/lib, but shares code with the interface application.
*/
import eslint from '@rollup/plugin-eslint'
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 typescript from 'rollup-plugin-typescript2'
import { CompilerOptions } from 'typescript'
import { dependencies } from './package.json'
const deps = Object.keys(dependencies)
const replacements = {
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 library = {
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: [
{
file: 'dist/widgets.js',
format: 'cjs',
inlineDynamicImports: true,
sourcemap: true,
dir: 'dist',
format: 'esm',
sourcemap: false,
},
{
file: 'dist/widgets.esm.js',
format: 'esm',
inlineDynamicImports: true,
sourcemap: true,
dir: 'dist/cjs',
entryFileNames: '[name].cjs',
chunkFileNames: '[name]-[hash].cjs',
format: 'cjs',
sourcemap: false,
},
],
// necessary because some nested imports (eg jotai/*) would otherwise not resolve.
external: (source: string) => Boolean(deps.find((dep) => source === dep || source.startsWith(dep + '/'))),
plugins: [
eslint({ include: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'] }),
json(), // imports json
replace({ ...replacements, preventAssignment: true }),
url(), // imports files (including svgs) as data URIs
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(), // imports sass styles
typescript({ tsconfig: './tsconfig.lib.json', useTsconfigDeclarationDir: true }),
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 typings = {
input: 'dist/dts/lib/index.d.ts',
output: {
file: 'dist/widgets.d.ts',
format: 'es',
},
external: (source: string) => source.endsWith('.scss'),
plugins: [dts({ compilerOptions: { baseUrl: 'dist/dts' } })],
const config = [check, type, transpile, locales]
export default config
function squelchTranspilationWarnings(warning: RollupWarning, warn: (warning: RollupWarning) => void) {
if (warning.pluginCode === 'TS5055') return
warn(warning)
}
const config = [library, typings]
export default config
function squelchTypeWarnings(warning: RollupWarning, warn: (warning: RollupWarning) => void) {
if (warning.code === 'UNUSED_EXTERNAL_IMPORT') return
warn(warning)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@@ -1,3 +1,3 @@
<svg width="100%" height="35" viewBox="800 0 300 200" xmlns="http://www.w3.org/2000/svg">
<line x1="0" x2="2000" y1="100" y2="100" stroke="currentColor" stroke-width="20" stroke-linecap="round" stroke-dasharray="1, 45"/>
<svg width="100%" height="35" viewBox="850 0 300 200" xmlns="http://www.w3.org/2000/svg">
<line x1="0" x2="3000" y1="100" y2="100" stroke="currentColor" stroke-width="20" stroke-linecap="round" stroke-dasharray="1, 45"/>
</svg>

Before

Width:  |  Height:  |  Size: 233 B

After

Width:  |  Height:  |  Size: 233 B

View File

@@ -1,16 +1,5 @@
<svg width="170" height="168" viewBox="0 0 170 168" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path opacity="0.6" d="M85.05 168C132.022 168 170.1 130.105 170.1 83.3593C170.1 36.6135 0 36.6135 0 83.3593C0 130.105 38.0782 168 85.05 168Z" fill="#FF505F"/>
<path opacity="0.6" d="M85.05 168C132.022 168 170.1 130.105 170.1 83.3593C170.1 36.6135 0 36.6135 0 83.3593C0 130.105 38.0782 168 85.05 168Z" fill="#FF0320"/>
<path d="M85.05 0C132.022 0 170.1 37.8949 170.1 84.6407C170.1 131.386 0 131.386 0 84.6407C0 37.8949 38.0782 0 85.05 0Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M144.665 64.0394L112.444 12.3742L89.0263 78.9477L144.665 64.0394Z" fill="#FF4E65"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M143.777 64.215L112.444 12.3742L165.349 58.4347L143.777 64.215Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M144.551 63.613L142.479 124.467L88.912 78.5213L144.551 63.613Z" fill="#D0001A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M143.663 63.7886L142.479 124.467L165.235 58.0083L143.663 63.7886Z" fill="#FF697B"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="170" height="168" fill="white"/>
</clipPath>
</defs>
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="250" cy="250" r="250" fill="#FF0420"/>
<path d="M177.133 316.446C162.247 316.446 150.051 312.943 140.544 305.938C131.162 298.808 126.471 288.676 126.471 275.541C126.471 272.789 126.784 269.411 127.409 265.408C129.036 256.402 131.35 245.581 134.352 232.947C142.858 198.547 164.812 181.347 200.213 181.347C209.845 181.347 218.476 182.973 226.107 186.225C233.738 189.352 239.742 194.106 244.12 200.486C248.498 206.74 250.688 214.246 250.688 223.002C250.688 225.629 250.375 228.944 249.749 232.947C247.873 244.08 245.621 254.901 242.994 265.408C238.616 282.546 231.048 295.368 220.29 303.874C209.532 312.255 195.147 316.446 177.133 316.446ZM179.76 289.426C186.766 289.426 192.707 287.362 197.586 283.234C202.59 279.106 206.155 272.789 208.281 264.283C211.158 252.524 213.348 242.266 214.849 233.51C215.349 230.883 215.599 228.194 215.599 225.441C215.599 214.058 209.657 208.366 197.774 208.366C190.768 208.366 184.764 210.43 179.76 214.558C174.882 218.687 171.379 225.004 169.253 233.51C167.001 241.891 164.749 252.149 162.498 264.283C161.997 266.784 161.747 269.411 161.747 272.163C161.747 283.672 167.752 289.426 179.76 289.426Z" fill="white"/>
<path d="M259.303 314.57C257.927 314.57 256.863 314.132 256.113 313.256C255.487 312.255 255.3 311.13 255.55 309.879L281.444 187.914C281.694 186.538 282.382 185.412 283.508 184.536C284.634 183.661 285.822 183.223 287.073 183.223H336.985C350.87 183.223 362.003 186.1 370.384 191.854C378.891 197.609 383.144 205.927 383.144 216.81C383.144 219.937 382.769 223.19 382.018 226.567C378.891 240.953 372.574 251.586 363.067 258.466C353.685 265.346 340.8 268.786 324.413 268.786H299.082L290.451 309.879C290.2 311.255 289.512 312.38 288.387 313.256C287.261 314.132 286.072 314.57 284.822 314.57H259.303ZM325.727 242.892C330.98 242.892 335.546 241.453 339.424 238.576C343.427 235.699 346.054 231.571 347.305 226.192C347.68 224.065 347.868 222.189 347.868 220.563C347.868 216.935 346.805 214.183 344.678 212.307C342.551 210.305 338.924 209.305 333.795 209.305H311.278L304.148 242.892H325.727Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,11 +1,11 @@
import { Trans } from '@lingui/macro'
import { AbstractConnector } from '@web3-react/abstract-connector'
import { Connector } from '@web3-react/types'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useCallback, useContext } from 'react'
import { ExternalLink as LinkIcon } from 'react-feather'
import { useAppDispatch } from 'state/hooks'
import styled, { ThemeContext } from 'styled-components/macro'
import { Connector } from 'widgets-web3-react/types'
import { AbstractConnector } from 'web3-react-abstract-connector'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { injected, portis, walletlink } from '../../connectors'

View File

@@ -32,6 +32,7 @@ const BLOCKED_ADDRESSES: string[] = [
'0x48549a34ae37b12f6a30566245176994e17c6b4a',
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0',
'0xc455f7fd3e0e12afd51fba5c106909934d8a0e4a',
'0x629e7Da20197a5429d30da36E77d06CdF796b71A',
]
export default function Blocklist({ children }: { children: ReactNode }) {

View File

@@ -51,6 +51,16 @@ const FlyoutHeader = styled.div`
font-weight: 400;
`
const FlyoutMenu = styled.div`
position: absolute;
top: 54px;
width: 272px;
z-index: 99;
padding-top: 10px;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
top: 40px;
}
`
const FlyoutMenuContents = styled.div`
align-items: flex-start;
background-color: ${({ theme }) => theme.bg0};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
@@ -61,16 +71,9 @@ const FlyoutMenu = styled.div`
font-size: 16px;
overflow: auto;
padding: 16px;
position: absolute;
top: 64px;
width: 272px;
z-index: 99;
& > *:not(:last-child) {
margin-bottom: 12px;
}
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
top: 50px;
}
`
const FlyoutRow = styled.div<{ active: boolean }>`
align-items: center;
@@ -305,21 +308,23 @@ export default function NetworkSelector() {
}
return (
<SelectorWrapper ref={node as any}>
<SelectorControls onClick={toggle} interactive>
<SelectorWrapper ref={node as any} onMouseEnter={toggle} onMouseLeave={toggle}>
<SelectorControls interactive>
<SelectorLogo interactive src={info.logoUrl} />
<SelectorLabel>{info.label}</SelectorLabel>
<StyledChevronDown />
</SelectorControls>
{open && (
<FlyoutMenu onMouseLeave={toggle}>
<FlyoutHeader>
<Trans>Select a network</Trans>
</FlyoutHeader>
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.MAINNET} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.POLYGON} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.OPTIMISM} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.ARBITRUM_ONE} />
<FlyoutMenu>
<FlyoutMenuContents>
<FlyoutHeader>
<Trans>Select a network</Trans>
</FlyoutHeader>
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.MAINNET} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.POLYGON} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.OPTIMISM} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.ARBITRUM_ONE} />
</FlyoutMenuContents>
</FlyoutMenu>
)}
</SelectorWrapper>

View File

@@ -264,9 +264,7 @@ export default function Header() {
const {
infoLink,
addNetworkInfo: {
nativeCurrency: { symbol: nativeCurrencySymbol },
},
nativeCurrency: { symbol: nativeCurrencySymbol },
} = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
return (

View File

@@ -1,5 +1,5 @@
import { AbstractConnector } from '@web3-react/abstract-connector'
import { Connector } from 'widgets-web3-react/types'
import { Connector } from '@web3-react/types'
import { AbstractConnector } from 'web3-react-abstract-connector'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'

View File

@@ -0,0 +1,139 @@
import { Trans } from '@lingui/macro'
import FlagImage from 'assets/images/ukraine.png'
import { AutoColumn } from 'components/Column'
import { RowBetween, RowFixed } from 'components/Row'
import { X } from 'react-feather'
import ReactGA from 'react-ga'
import { useDarkModeManager, useShowDonationLink } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { ExternalLink, ThemedText, Z_INDEX } from 'theme'
const darkGradient = `radial-gradient(87.53% 3032.45% at 5.16% 10.13%, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%),
linear-gradient(0deg, rgba(0, 91, 187, 0.35), rgba(0, 91, 187, 0.35)), #000000;`
const lightGradient = `radial-gradient(87.53% 3032.45% at 5.16% 10.13%, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%), linear-gradient(0deg, #CBE4FF, #CBE4FF), linear-gradient(0deg, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.09)), radial-gradient(100% 93.36% at 0% 6.64%, #8BC4FF 0%, #FFF5BF 100%);`
const Wrapper = styled(AutoColumn)<{ darkMode: boolean }>`
background: #edeef2;
position: fixed;
bottom: 40px;
border-radius: 12px;
padding: 18px;
max-width: 360px;
background: ${({ darkMode }) => (darkMode ? darkGradient : lightGradient)};
color: ${({ theme }) => theme.text1};
z-index: ${Z_INDEX.deprecated_content};
:hover {
opacity: 0.8;
}
& > * {
z-index: ${Z_INDEX.fixed};
}
overflow: hidden;
:before {
background-image: url(${FlagImage});
background-repeat: no-repeat;
overflow: hidden;
background-size: 300px;
content: '';
height: 1200px;
width: 400px;
opacity: 0.1;
position: absolute;
transform: rotate(25deg) translate(-140px, -60px);
width: 300px;
z-index: ${Z_INDEX.deprecated_zero};
}
${({ theme }) => theme.mediaWidth.upToSmall`
max-width: 100%;
`}
${({ theme }) => theme.mediaWidth.upToMedium`
position: relative;
bottom: unset;
`}
`
const WrappedCloseIcon = styled(X)`
stroke: ${({ theme }) => theme.text2};
z-index: ${Z_INDEX.tooltip};
:hover {
cursor: pointer;
opacity: 0.8;
}
`
export const StyledFlagImage = styled.div`
margin-right: 12px;
width: 18px;
height: 18px;
border-radius: 100%;
&:before,
&:after {
content: '';
width: 9px;
height: 18px;
}
&:before {
float: left;
border-top-left-radius: 9px;
border-bottom-left-radius: 9px;
background: #005bbb;
}
&:after {
float: right;
border-top-right-radius: 9px;
border-bottom-right-radius: 9px;
background: #ffd500;
}
transform: rotate(90deg);
`
const StyledLink = styled(ExternalLink)`
text-decoration: none !important;
`
export default function DonationLink() {
const [darkMode] = useDarkModeManager()
const [, setVisible] = useShowDonationLink()
return (
<Wrapper
gap="10px"
darkMode={darkMode}
as={StyledLink}
target="https://donate.uniswap.org/#/swap"
href="https://donate.uniswap.org/#/swap"
onClickCapture={() => {
ReactGA.event({
category: 'Donate',
action: 'Link to Ukraine site.',
})
}}
>
<RowBetween>
<RowFixed>
<StyledFlagImage />
<ThemedText.Body fontWeight={600} fontSize={'18px'}>
<Trans>Donate to Ukraine</Trans>
</ThemedText.Body>
</RowFixed>
<WrappedCloseIcon
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setVisible(false)
return false
}}
/>
</RowBetween>
<ThemedText.Body fontWeight={400} fontSize="12px" color="text2">
<Trans>Directly support the Ukrainian government by donating tokens.</Trans>
</ThemedText.Body>
</Wrapper>
)
}

View File

@@ -1,25 +1,27 @@
import { SupportedChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useLocation } from 'react-router-dom'
import styled from 'styled-components/macro'
import { MEDIA_WIDTHS } from 'theme'
import { useActivePopups } from '../../state/application/hooks'
import { useShowSurveyPopup, useURLWarningVisible } from '../../state/user/hooks'
import { useShowDonationLink, useURLWarningVisible } from '../../state/user/hooks'
import { AutoColumn } from '../Column'
import ClaimPopup from './ClaimPopup'
import DonationLink from './DonationLink'
import PopupItem from './PopupItem'
import SurveyPopup from './SurveyPopup'
const MobilePopupWrapper = styled.div<{ height: string | number }>`
position: relative;
max-width: 100%;
height: ${({ height }) => height};
margin: ${({ height }) => (height ? '0 auto;' : 0)};
margin-bottom: ${({ height }) => (height ? '20px' : 0)}};
margin-bottom: ${({ height }) => (height ? '20px' : 0)};
display: none;
${({ theme }) => theme.mediaWidth.upToSmall`
display: block;
padding-top: 20px;
`};
`
@@ -60,27 +62,29 @@ export default function Popups() {
// get all popups
const activePopups = useActivePopups()
// show survey popup if user has not closed
const [showSurveyPopup] = useShowSurveyPopup()
const urlWarningActive = useURLWarningVisible()
// need extra padding if network is not L1 Ethereum
const { chainId } = useActiveWeb3React()
const isNotOnMainnet = Boolean(chainId && chainId !== SupportedChainId.MAINNET)
const location = useLocation()
const isOnSwapPage = location.pathname.includes('swap')
const [donationVisible] = useShowDonationLink()
const showDonation = donationVisible && isOnSwapPage
return (
<>
<FixedPopupColumn gap="20px" extraPadding={urlWarningActive} xlPadding={isNotOnMainnet}>
<ClaimPopup />
<SurveyPopup />
{activePopups.map((item) => (
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
))}
{showDonation ? <DonationLink /> : null}
</FixedPopupColumn>
<MobilePopupWrapper height={activePopups?.length > 0 || showSurveyPopup ? 'fit-content' : 0}>
<MobilePopupWrapper height={activePopups?.length > 0 || showDonation ? 'fit-content' : 0}>
<MobilePopupInner>
<SurveyPopup />
{showDonation ? <DonationLink /> : null}
{activePopups // reverse so new items up front
.slice(0)
.reverse()

View File

@@ -19,7 +19,7 @@ import { PositionDetails } from 'types/position'
import { formatTickPrice } from 'utils/formatTickPrice'
import { unwrappedToken } from 'utils/unwrappedToken'
import { DAI, USDC, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
import { DAI, USDC_MAINNET, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
const LinkRow = styled(Link)`
align-items: center;
@@ -145,7 +145,7 @@ export function getPriceOrderingFromPositionForUI(position?: Position): {
const token1 = position.amount1.currency
// if token0 is a dollar-stable asset, set it as the quote token
const stables = [DAI, USDC, USDT]
const stables = [DAI, USDC_MAINNET, USDT]
if (stables.some((stable) => stable.equals(token0))) {
return {
priceLower: position.token0PriceUpper.invert(),
@@ -157,7 +157,7 @@ export function getPriceOrderingFromPositionForUI(position?: Position): {
// if token1 is an ETH-/BTC-stable asset, set it as the base token
const bases = [...Object.values(WRAPPED_NATIVE_CURRENCY), WBTC]
if (bases.some((base) => base.equals(token1))) {
if (bases.some((base) => base && base.equals(token1))) {
return {
priceLower: position.token0PriceUpper.invert(),
priceUpper: position.token0PriceLower.invert(),

View File

@@ -1,7 +1,7 @@
import { Protocol } from '@uniswap/router-sdk'
import { Currency, Percent } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk'
import { DAI, USDC, WBTC } from 'constants/tokens'
import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens'
import { render } from 'test-utils'
import RoutingDiagram, { RoutingDiagramEntry } from './RoutingDiagram'
@@ -10,16 +10,16 @@ const percent = (strings: TemplateStringsArray) => new Percent(parseInt(strings[
const singleRoute: RoutingDiagramEntry = {
percent: percent`100`,
path: [[USDC, DAI, FeeAmount.LOW]],
path: [[USDC_MAINNET, DAI, FeeAmount.LOW]],
protocol: Protocol.V3,
}
const multiRoute: RoutingDiagramEntry[] = [
{ percent: percent`75`, path: [[USDC, DAI, FeeAmount.LOWEST]], protocol: Protocol.V2 },
{ percent: percent`75`, path: [[USDC_MAINNET, DAI, FeeAmount.LOWEST]], protocol: Protocol.V2 },
{
percent: percent`25`,
path: [
[USDC, WBTC, FeeAmount.MEDIUM],
[USDC_MAINNET, WBTC, FeeAmount.MEDIUM],
[WBTC, DAI, FeeAmount.HIGH],
],
protocol: Protocol.V3,
@@ -47,16 +47,16 @@ jest.mock('hooks/useTokenInfoFromActiveList', () => ({
}))
it('renders when no routes are provided', () => {
const { asFragment } = render(<RoutingDiagram currencyIn={DAI} currencyOut={USDC} routes={[]} />)
const { asFragment } = render(<RoutingDiagram currencyIn={DAI} currencyOut={USDC_MAINNET} routes={[]} />)
expect(asFragment()).toMatchSnapshot()
})
it('renders single route', () => {
const { asFragment } = render(<RoutingDiagram currencyIn={USDC} currencyOut={DAI} routes={[singleRoute]} />)
const { asFragment } = render(<RoutingDiagram currencyIn={USDC_MAINNET} currencyOut={DAI} routes={[singleRoute]} />)
expect(asFragment()).toMatchSnapshot()
})
it('renders multi route', () => {
const { asFragment } = render(<RoutingDiagram currencyIn={USDC} currencyOut={DAI} routes={multiRoute} />)
const { asFragment } = render(<RoutingDiagram currencyIn={USDC_MAINNET} currencyOut={DAI} routes={multiRoute} />)
expect(asFragment()).toMatchSnapshot()
})

View File

@@ -1,12 +1,12 @@
import { Trans } from '@lingui/macro'
import { Protocol } from '@uniswap/router-sdk'
import { Currency, Percent } from '@uniswap/sdk-core'
import { Currency } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk'
import Badge from 'components/Badge'
import CurrencyLogo from 'components/CurrencyLogo'
import DoubleCurrencyLogo from 'components/DoubleLogo'
import Row, { AutoRow } from 'components/Row'
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'
@@ -14,12 +14,6 @@ import { ThemedText, Z_INDEX } from 'theme'
import { ReactComponent as DotLine } from '../../assets/svg/dot_line.svg'
import { MouseoverTooltip } from '../Tooltip'
export interface RoutingDiagramEntry {
percent: Percent
path: [Currency, Currency, FeeAmount][]
protocol: Protocol
}
const Wrapper = styled(Box)`
align-items: center;
width: 100%;

View File

@@ -3,45 +3,45 @@
exports[`renders multi route 1`] = `
<DocumentFragment>
<div
class="RoutingDiagram__Wrapper-sc-o1ook0-0 ePDWDk css-vurnku"
class="RoutingDiagram__Wrapper-sc-i2tbb-0 ivndgC css-vurnku"
>
<div
class="sc-bdnxRM Row-sc-nrd8cx-0 RoutingDiagram__RouteContainerRow-sc-o1ook0-1 lmTMKd itvFNV ibRCpr"
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteContainerRow-sc-i2tbb-1 lmTMKd hLLNig hDkZVB"
>
CurrencyLogo currency=USDC
<div
class="sc-bdnxRM Row-sc-nrd8cx-0 RoutingDiagram__RouteRow-sc-o1ook0-2 lmTMKd itvFNV fzMiot"
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteRow-sc-i2tbb-2 lmTMKd hLLNig hUDqOH"
>
<div
class="RoutingDiagram__DottedLine-sc-o1ook0-4 kkXINS"
class="RoutingDiagram__DottedLine-sc-i2tbb-4 cKqYfU"
>
<svg
class="RoutingDiagram__DotColor-sc-o1ook0-5 kgYqrO"
class="RoutingDiagram__DotColor-sc-i2tbb-5 fhSaBA"
>
dot_line.svg
</svg>
</div>
<div
class="Badge-sc-1mhw5si-0 RoutingDiagram__OpaqueBadge-sc-o1ook0-6 gayll OurGh"
class="Badge-sc-3epor3-0 RoutingDiagram__OpaqueBadge-sc-i2tbb-6 knpfHF gGARxH"
>
<div
class="Badge-sc-1mhw5si-0 RoutingDiagram__ProtocolBadge-sc-o1ook0-7 gayll bNVqMw"
class="Badge-sc-3epor3-0 RoutingDiagram__ProtocolBadge-sc-i2tbb-7 knpfHF lbdUti"
>
<div
class="theme__TextWrapper-sc-18nh1jk-0 cWOfab RoutingDiagram__BadgeText-sc-o1ook0-8 dYpdfO css-15li2d9"
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-15li2d9"
>
V2
</div>
</div>
<div
class="theme__TextWrapper-sc-18nh1jk-0 cWOfab RoutingDiagram__BadgeText-sc-o1ook0-8 dYpdfO css-1aekuku"
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-1aekuku"
style="min-width: auto;"
>
75%
</div>
</div>
<div
class="sc-bdnxRM Row-sc-nrd8cx-0 Row__AutoRow-sc-nrd8cx-3 iqvZFe itvFNV kkMfuq"
class="sc-bdnxRM Row-sc-u7azg8-0 Row__AutoRow-sc-u7azg8-3 iqvZFe hLLNig cUhARX"
style="justify-content: space-evenly; z-index: 2;"
width="100%"
>
@@ -51,42 +51,42 @@ exports[`renders multi route 1`] = `
CurrencyLogo currency=DAI
</div>
<div
class="sc-bdnxRM Row-sc-nrd8cx-0 RoutingDiagram__RouteContainerRow-sc-o1ook0-1 lmTMKd itvFNV ibRCpr"
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteContainerRow-sc-i2tbb-1 lmTMKd hLLNig hDkZVB"
>
CurrencyLogo currency=USDC
<div
class="sc-bdnxRM Row-sc-nrd8cx-0 RoutingDiagram__RouteRow-sc-o1ook0-2 lmTMKd itvFNV fzMiot"
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteRow-sc-i2tbb-2 lmTMKd hLLNig hUDqOH"
>
<div
class="RoutingDiagram__DottedLine-sc-o1ook0-4 kkXINS"
class="RoutingDiagram__DottedLine-sc-i2tbb-4 cKqYfU"
>
<svg
class="RoutingDiagram__DotColor-sc-o1ook0-5 kgYqrO"
class="RoutingDiagram__DotColor-sc-i2tbb-5 fhSaBA"
>
dot_line.svg
</svg>
</div>
<div
class="Badge-sc-1mhw5si-0 RoutingDiagram__OpaqueBadge-sc-o1ook0-6 gayll OurGh"
class="Badge-sc-3epor3-0 RoutingDiagram__OpaqueBadge-sc-i2tbb-6 knpfHF gGARxH"
>
<div
class="Badge-sc-1mhw5si-0 RoutingDiagram__ProtocolBadge-sc-o1ook0-7 gayll bNVqMw"
class="Badge-sc-3epor3-0 RoutingDiagram__ProtocolBadge-sc-i2tbb-7 knpfHF lbdUti"
>
<div
class="theme__TextWrapper-sc-18nh1jk-0 cWOfab RoutingDiagram__BadgeText-sc-o1ook0-8 dYpdfO css-15li2d9"
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-15li2d9"
>
V3
</div>
</div>
<div
class="theme__TextWrapper-sc-18nh1jk-0 cWOfab RoutingDiagram__BadgeText-sc-o1ook0-8 dYpdfO css-1aekuku"
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-1aekuku"
style="min-width: auto;"
>
25%
</div>
</div>
<div
class="sc-bdnxRM Row-sc-nrd8cx-0 Row__AutoRow-sc-nrd8cx-3 iqvZFe itvFNV kkMfuq"
class="sc-bdnxRM Row-sc-u7azg8-0 Row__AutoRow-sc-u7azg8-3 iqvZFe hLLNig cUhARX"
style="justify-content: space-evenly; z-index: 2;"
width="100%"
>
@@ -102,45 +102,45 @@ exports[`renders multi route 1`] = `
exports[`renders single route 1`] = `
<DocumentFragment>
<div
class="RoutingDiagram__Wrapper-sc-o1ook0-0 ePDWDk css-vurnku"
class="RoutingDiagram__Wrapper-sc-i2tbb-0 ivndgC css-vurnku"
>
<div
class="sc-bdnxRM Row-sc-nrd8cx-0 RoutingDiagram__RouteContainerRow-sc-o1ook0-1 lmTMKd itvFNV ibRCpr"
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteContainerRow-sc-i2tbb-1 lmTMKd hLLNig hDkZVB"
>
CurrencyLogo currency=USDC
<div
class="sc-bdnxRM Row-sc-nrd8cx-0 RoutingDiagram__RouteRow-sc-o1ook0-2 lmTMKd itvFNV fzMiot"
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteRow-sc-i2tbb-2 lmTMKd hLLNig hUDqOH"
>
<div
class="RoutingDiagram__DottedLine-sc-o1ook0-4 kkXINS"
class="RoutingDiagram__DottedLine-sc-i2tbb-4 cKqYfU"
>
<svg
class="RoutingDiagram__DotColor-sc-o1ook0-5 kgYqrO"
class="RoutingDiagram__DotColor-sc-i2tbb-5 fhSaBA"
>
dot_line.svg
</svg>
</div>
<div
class="Badge-sc-1mhw5si-0 RoutingDiagram__OpaqueBadge-sc-o1ook0-6 gayll OurGh"
class="Badge-sc-3epor3-0 RoutingDiagram__OpaqueBadge-sc-i2tbb-6 knpfHF gGARxH"
>
<div
class="Badge-sc-1mhw5si-0 RoutingDiagram__ProtocolBadge-sc-o1ook0-7 gayll bNVqMw"
class="Badge-sc-3epor3-0 RoutingDiagram__ProtocolBadge-sc-i2tbb-7 knpfHF lbdUti"
>
<div
class="theme__TextWrapper-sc-18nh1jk-0 cWOfab RoutingDiagram__BadgeText-sc-o1ook0-8 dYpdfO css-15li2d9"
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-15li2d9"
>
V3
</div>
</div>
<div
class="theme__TextWrapper-sc-18nh1jk-0 cWOfab RoutingDiagram__BadgeText-sc-o1ook0-8 dYpdfO css-1aekuku"
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-1aekuku"
style="min-width: auto;"
>
100%
</div>
</div>
<div
class="sc-bdnxRM Row-sc-nrd8cx-0 Row__AutoRow-sc-nrd8cx-3 iqvZFe itvFNV kkMfuq"
class="sc-bdnxRM Row-sc-u7azg8-0 Row__AutoRow-sc-u7azg8-3 iqvZFe hLLNig cUhARX"
style="justify-content: space-evenly; z-index: 2;"
width="100%"
>
@@ -156,7 +156,7 @@ exports[`renders single route 1`] = `
exports[`renders when no routes are provided 1`] = `
<DocumentFragment>
<div
class="RoutingDiagram__Wrapper-sc-o1ook0-0 ePDWDk css-vurnku"
class="RoutingDiagram__Wrapper-sc-i2tbb-0 ivndgC css-vurnku"
/>
</DocumentFragment>
`;

View File

@@ -48,7 +48,7 @@ export function ImportToken(props: ImportProps) {
<RowBetween>
{onBack ? <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} /> : <div />}
<ThemedText.MediumHeader>
<Plural value={tokens.length} one="Import token" other="Import tokens" />
<Plural value={tokens.length} _1="Import token" other="Import tokens" />
</ThemedText.MediumHeader>
{onDismiss ? <CloseIcon onClick={onDismiss} /> : <div />}
</RowBetween>

View File

@@ -2,11 +2,11 @@
import { t, Trans } from '@lingui/macro'
import { Percent } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'lib/hooks/routing/clientSideSmartOrderRouter'
import { useContext, useRef, useState } from 'react'
import { Settings, X } from 'react-feather'
import ReactGA from 'react-ga'
import { Text } from 'rebass'
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'state/routing/clientSideSmartOrderRouter/constants'
import styled, { ThemeContext } from 'styled-components/macro'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'

View File

@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import { AbstractConnector } from '@web3-react/abstract-connector'
import { darken } from 'polished'
import styled from 'styled-components/macro'
import { AbstractConnector } from 'web3-react-abstract-connector'
import { injected } from '../../connectors'
import { SUPPORTED_WALLETS } from '../../constants/wallet'

View File

@@ -1,7 +1,4 @@
import { Trans } from '@lingui/macro'
import { AbstractConnector } from '@web3-react/abstract-connector'
import { UnsupportedChainIdError, useWeb3React } from '@web3-react/core'
import { WalletConnectConnector } from '@web3-react/walletconnect-connector'
import { AutoColumn } from 'components/Column'
import { PrivacyPolicy } from 'components/PrivacyPolicy'
import Row, { AutoRow, RowBetween } from 'components/Row'
@@ -10,6 +7,9 @@ import { useEffect, useState } from 'react'
import { ArrowLeft, ArrowRight, Info } from 'react-feather'
import ReactGA from 'react-ga'
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 { ReactComponent as Close } from '../../assets/images/x.svg'

View File

@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { useEffect } from 'react'
import styled from 'styled-components/macro'
import { useWeb3React } from 'web3-react-core'
import { network } from '../../connectors'
import { NetworkContextName } from '../../constants/misc'

View File

@@ -1,12 +1,12 @@
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
import { AbstractConnector } from '@web3-react/abstract-connector'
import { UnsupportedChainIdError, useWeb3React } from '@web3-react/core'
import { Connector } from '@web3-react/types'
import { darken } from 'polished'
import { useMemo } from 'react'
import { Activity } from 'react-feather'
import styled, { css } from 'styled-components/macro'
import { Connector } from 'widgets-web3-react/types'
import { AbstractConnector } from 'web3-react-abstract-connector'
import { UnsupportedChainIdError, useWeb3React } from 'web3-react-core'
import { NetworkContextName } from '../../constants/misc'
import useENSName from '../../hooks/useENSName'

View File

@@ -4,6 +4,8 @@ import ReactGA from 'react-ga'
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',
@@ -31,5 +33,15 @@ export default function GoogleAnalyticsReporter({ location: { pathname, search }
useEffect(() => {
ReactGA.pageview(`${pathname}${search}`)
}, [pathname, search])
useEffect(() => {
// typed as 'any' in react-ga -.-
ReactGA.ga((tracker: any) => {
if (!tracker) return
const clientId = tracker.get('clientId')
window.localStorage.setItem(GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY, clientId)
})
}, [])
return null
}

View File

@@ -1,12 +1,17 @@
import ReactGA from 'react-ga'
import { isMobile } from 'utils/userAgent'
export const GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY = 'ga_client_id'
const GOOGLE_ANALYTICS_ID: string | undefined = process.env.REACT_APP_GOOGLE_ANALYTICS_ID
const storedClientId = window.localStorage.getItem(GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY)
if (typeof GOOGLE_ANALYTICS_ID === 'string') {
ReactGA.initialize(GOOGLE_ANALYTICS_ID, {
gaOptions: {
storage: 'none',
storeGac: false,
clientId: storedClientId ?? undefined,
},
})
ReactGA.set({

View File

@@ -1,10 +1,11 @@
import { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro'
import StakingRewardsJson from '@uniswap/liquidity-staker/build/StakingRewards.json'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { ReactNode, useState } from 'react'
import styled from 'styled-components/macro'
import { useStakingContract } from '../../hooks/useContract'
import { useContract } from '../../hooks/useContract'
import { StakingInfo } from '../../state/stake/hooks'
import { TransactionType } from '../../state/transactions/actions'
import { useTransactionAdder } from '../../state/transactions/hooks'
@@ -15,6 +16,12 @@ import Modal from '../Modal'
import { LoadingView, SubmittedView } from '../ModalViews'
import { RowBetween } from '../Row'
const { abi: STAKING_REWARDS_ABI } = StakingRewardsJson
function useStakingContract(stakingAddress?: string, withSignerIfPossible?: boolean) {
return useContract(stakingAddress, STAKING_REWARDS_ABI, withSignerIfPossible)
}
const ContentWrapper = styled(AutoColumn)`
width: 100%;
padding: 1rem;

View File

@@ -1,5 +1,6 @@
import { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro'
import StakingRewardsJson from '@uniswap/liquidity-staker/build/StakingRewards.json'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
@@ -8,7 +9,7 @@ import { useCallback, useState } from 'react'
import styled from 'styled-components/macro'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import { usePairContract, useStakingContract, useV2RouterContract } from '../../hooks/useContract'
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'
@@ -24,6 +25,12 @@ import { LoadingView, SubmittedView } from '../ModalViews'
import ProgressCircles from '../ProgressSteps'
import { RowBetween } from '../Row'
const { abi: STAKING_REWARDS_ABI } = StakingRewardsJson
function useStakingContract(stakingAddress?: string, withSignerIfPossible?: boolean) {
return useContract(stakingAddress, STAKING_REWARDS_ABI, withSignerIfPossible)
}
const HypotheticalRewardRate = styled.div<{ dim: boolean }>`
display: flex;
justify-content: space-between;

View File

@@ -1,10 +1,11 @@
import { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro'
import StakingRewardsJson from '@uniswap/liquidity-staker/build/StakingRewards.json'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { ReactNode, useState } from 'react'
import styled from 'styled-components/macro'
import { useStakingContract } from '../../hooks/useContract'
import { useContract } from '../../hooks/useContract'
import { StakingInfo } from '../../state/stake/hooks'
import { TransactionType } from '../../state/transactions/actions'
import { useTransactionAdder } from '../../state/transactions/hooks'
@@ -16,6 +17,12 @@ import Modal from '../Modal'
import { LoadingView, SubmittedView } from '../ModalViews'
import { RowBetween } from '../Row'
const { abi: STAKING_REWARDS_ABI } = StakingRewardsJson
function useStakingContract(stakingAddress?: string, withSignerIfPossible?: boolean) {
return useContract(stakingAddress, STAKING_REWARDS_ABI, withSignerIfPossible)
}
const ContentWrapper = styled(AutoColumn)`
width: 100%;
padding: 1rem;

View File

@@ -0,0 +1,35 @@
import { useRef } from 'react'
let uniqueId = 0
const getUniqueId = () => uniqueId++
export default function AutoRouterIcon({ className, id }: { className?: string; id?: string }) {
const componentIdRef = useRef(id ?? getUniqueId())
const componentId = `AutoRouterIconGradient${componentIdRef.current}`
return (
<svg
width="23"
height="20"
viewBox="0 0 23 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<defs>
<linearGradient id={componentId} x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(95)">
<stop id="stop1" offset="0" stopColor="#2274E2" />
<stop id="stop1" offset="0.5" stopColor="#2274E2" />
<stop id="stop2" offset="1" stopColor="#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"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
stroke={`url(#${componentId})`}
/>
</svg>
)
}

View File

@@ -3,8 +3,8 @@ import useAutoRouterSupported from 'hooks/useAutoRouterSupported'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { ReactComponent as AutoRouterIcon } from '../../assets/svg/auto_router.svg'
import { ReactComponent as StaticRouterIcon } from '../../assets/svg/static_route.svg'
import AutoRouterIcon from './AutoRouterIcon'
const StyledAutoRouterIcon = styled(AutoRouterIcon)`
height: 16px;

View File

@@ -1,15 +1,14 @@
import { Trans } from '@lingui/macro'
import { Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { Currency, TradeType } from '@uniswap/sdk-core'
import AnimatedDropdown from 'components/AnimatedDropdown'
import { AutoColumn } from 'components/Column'
import { LoadingRows } from 'components/Loader/styled'
import RoutingDiagram, { RoutingDiagramEntry } from 'components/RoutingDiagram/RoutingDiagram'
import RoutingDiagram from 'components/RoutingDiagram/RoutingDiagram'
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'
@@ -39,8 +38,6 @@ const OpenCloseIcon = styled(Plus)<{ open?: boolean }>`
}
`
const V2_DEFAULT_FEE_TIER = 3000
interface SwapRouteProps extends React.HTMLAttributes<HTMLDivElement> {
trade: InterfaceTrade<Currency, Currency, TradeType>
syncing: boolean
@@ -109,35 +106,3 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r
</Wrapper>
)
})
function getTokenPath(trade: Trade<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

@@ -1,4 +1,4 @@
import { FortmaticConnector as FortmaticConnectorCore } from '@web3-react/fortmatic-connector'
import { FortmaticConnector as FortmaticConnectorCore } from 'web3-react-fortmatic-connector'
export const OVERLAY_READY = 'OVERLAY_READY'

View File

@@ -1,6 +1,6 @@
import { AbstractConnector } from '@web3-react/abstract-connector'
import { ConnectorUpdate } from '@web3-react/types'
import invariant from 'tiny-invariant'
import { AbstractConnector } from 'web3-react-abstract-connector'
import { ConnectorUpdate } from 'web3-react-types'
interface NetworkConnectorArguments {
urls: { [chainId: number]: string }

View File

@@ -1,11 +1,11 @@
import { Web3Provider } from '@ethersproject/providers'
import { SafeAppConnector } from '@gnosis.pm/safe-apps-web3-react'
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'
import { INFURA_NETWORK_URLS } from 'constants/chainInfo'
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'
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
import getLibrary from '../utils/getLibrary'
@@ -53,5 +53,5 @@ export const walletlink = new WalletLinkConnector({
url: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
appName: 'Uniswap',
appLogoUrl: UNISWAP_LOGO_URL,
supportedChainIds: [SupportedChainId.MAINNET, SupportedChainId.POLYGON],
supportedChainIds: ALL_SUPPORTED_CHAIN_IDS,
})

View File

@@ -1,46 +1,12 @@
import ethereumLogoUrl from 'assets/images/ethereum-logo.png'
import arbitrumLogoUrl from 'assets/svg/arbitrum_logo.svg'
import optimismLogoUrl from 'assets/svg/optimistic_ethereum.svg'
import polygonMaticLogo from 'assets/svg/polygon-matic-logo.svg'
import ms from 'ms.macro'
import ethereumLogoUrl from '../assets/images/ethereum-logo.png'
import arbitrumLogoUrl from '../assets/svg/arbitrum_logo.svg'
import optimismLogoUrl from '../assets/svg/optimistic_ethereum.svg'
import polygonMaticLogo from '../assets/svg/polygon-matic-logo.svg'
import { SupportedChainId, SupportedL1ChainId, SupportedL2ChainId } from './chains'
import { ARBITRUM_LIST, OPTIMISM_LIST } from './lists'
const INFURA_KEY = process.env.REACT_APP_INFURA_KEY
if (typeof INFURA_KEY === 'undefined') {
throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`)
}
/**
* These are the network URLs used by the interface when there is not another available source of chain data
*/
export const INFURA_NETWORK_URLS: { [key in SupportedChainId]: string } = {
[SupportedChainId.MAINNET]: `https://mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.RINKEBY]: `https://rinkeby.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.ROPSTEN]: `https://ropsten.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.GOERLI]: `https://goerli.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.KOVAN]: `https://kovan.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.OPTIMISM]: `https://optimism-mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.OPTIMISTIC_KOVAN]: `https://optimism-kovan.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.ARBITRUM_ONE]: `https://arbitrum-mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.ARBITRUM_RINKEBY]: `https://arbitrum-rinkeby.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.POLYGON]: `https://polygon-mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.POLYGON_MUMBAI]: `https://polygon-mumbai.infura.io/v3/${INFURA_KEY}`,
}
/**
* This is used to call the add network RPC
*/
interface AddNetworkInfo {
readonly rpcUrl: string
readonly nativeCurrency: {
name: string // e.g. 'Goerli ETH',
symbol: string // e.g. 'gorETH',
decimals: number // e.g. 18,
}
}
export enum NetworkType {
L1,
L2,
@@ -56,7 +22,11 @@ interface BaseChainInfo {
readonly logoUrl: string
readonly label: string
readonly helpCenterUrl?: string
readonly addNetworkInfo: AddNetworkInfo
readonly nativeCurrency: {
name: string // e.g. 'Goerli ETH',
symbol: string // e.g. 'gorETH',
decimals: number // e.g. 18,
}
}
export interface L1ChainInfo extends BaseChainInfo {
@@ -83,10 +53,7 @@ export const CHAIN_INFO: ChainInfoMap = {
infoLink: 'https://info.uniswap.org/#/',
label: 'Ethereum',
logoUrl: ethereumLogoUrl,
addNetworkInfo: {
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
},
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
},
[SupportedChainId.RINKEBY]: {
networkType: NetworkType.L1,
@@ -95,10 +62,7 @@ export const CHAIN_INFO: ChainInfoMap = {
infoLink: 'https://info.uniswap.org/#/',
label: 'Rinkeby',
logoUrl: ethereumLogoUrl,
addNetworkInfo: {
nativeCurrency: { name: 'Rinkeby Ether', symbol: 'rETH', decimals: 18 },
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.RINKEBY],
},
nativeCurrency: { name: 'Rinkeby Ether', symbol: 'rETH', decimals: 18 },
},
[SupportedChainId.ROPSTEN]: {
networkType: NetworkType.L1,
@@ -107,10 +71,7 @@ export const CHAIN_INFO: ChainInfoMap = {
infoLink: 'https://info.uniswap.org/#/',
label: 'Ropsten',
logoUrl: ethereumLogoUrl,
addNetworkInfo: {
nativeCurrency: { name: 'Ropsten Ether', symbol: 'ropETH', decimals: 18 },
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.ROPSTEN],
},
nativeCurrency: { name: 'Ropsten Ether', symbol: 'ropETH', decimals: 18 },
},
[SupportedChainId.KOVAN]: {
networkType: NetworkType.L1,
@@ -119,10 +80,7 @@ export const CHAIN_INFO: ChainInfoMap = {
infoLink: 'https://info.uniswap.org/#/',
label: 'Kovan',
logoUrl: ethereumLogoUrl,
addNetworkInfo: {
nativeCurrency: { name: 'Kovan Ether', symbol: 'kovETH', decimals: 18 },
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.KOVAN],
},
nativeCurrency: { name: 'Kovan Ether', symbol: 'kovETH', decimals: 18 },
},
[SupportedChainId.GOERLI]: {
networkType: NetworkType.L1,
@@ -131,10 +89,7 @@ export const CHAIN_INFO: ChainInfoMap = {
infoLink: 'https://info.uniswap.org/#/',
label: 'Görli',
logoUrl: ethereumLogoUrl,
addNetworkInfo: {
nativeCurrency: { name: 'Görli Ether', symbol: 'görETH', decimals: 18 },
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.GOERLI],
},
nativeCurrency: { name: 'Görli Ether', symbol: 'görETH', decimals: 18 },
},
[SupportedChainId.OPTIMISM]: {
networkType: NetworkType.L2,
@@ -148,10 +103,7 @@ export const CHAIN_INFO: ChainInfoMap = {
logoUrl: optimismLogoUrl,
statusPage: 'https://optimism.io/status',
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ',
addNetworkInfo: {
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
rpcUrl: 'https://mainnet.optimism.io',
},
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
},
[SupportedChainId.OPTIMISTIC_KOVAN]: {
networkType: NetworkType.L2,
@@ -165,10 +117,7 @@ export const CHAIN_INFO: ChainInfoMap = {
logoUrl: optimismLogoUrl,
statusPage: 'https://optimism.io/status',
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ',
addNetworkInfo: {
nativeCurrency: { name: 'Optimistic Kovan Ether', symbol: 'kovOpETH', decimals: 18 },
rpcUrl: 'https://kovan.optimism.io',
},
nativeCurrency: { name: 'Optimistic Kovan Ether', symbol: 'kovOpETH', decimals: 18 },
},
[SupportedChainId.ARBITRUM_ONE]: {
networkType: NetworkType.L2,
@@ -181,10 +130,7 @@ export const CHAIN_INFO: ChainInfoMap = {
logoUrl: arbitrumLogoUrl,
defaultListUrl: ARBITRUM_LIST,
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137787-uniswap-on-arbitrum',
addNetworkInfo: {
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
rpcUrl: 'https://arb1.arbitrum.io/rpc',
},
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
},
[SupportedChainId.ARBITRUM_RINKEBY]: {
networkType: NetworkType.L2,
@@ -197,10 +143,7 @@ export const CHAIN_INFO: ChainInfoMap = {
logoUrl: arbitrumLogoUrl,
defaultListUrl: ARBITRUM_LIST,
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137787-uniswap-on-arbitrum',
addNetworkInfo: {
nativeCurrency: { name: 'Rinkeby Arbitrum Ether', symbol: 'rinkArbETH', decimals: 18 },
rpcUrl: 'https://rinkeby.arbitrum.io/rpc',
},
nativeCurrency: { name: 'Rinkeby Arbitrum Ether', symbol: 'rinkArbETH', decimals: 18 },
},
[SupportedChainId.POLYGON]: {
networkType: NetworkType.L1,
@@ -211,10 +154,7 @@ export const CHAIN_INFO: ChainInfoMap = {
infoLink: 'https://info.uniswap.org/#/polygon/',
label: 'Polygon',
logoUrl: polygonMaticLogo,
addNetworkInfo: {
rpcUrl: 'https://polygon-rpc.com/',
nativeCurrency: { name: 'Polygon Matic', symbol: 'MATIC', decimals: 18 },
},
nativeCurrency: { name: 'Polygon Matic', symbol: 'MATIC', decimals: 18 },
},
[SupportedChainId.POLYGON_MUMBAI]: {
networkType: NetworkType.L1,
@@ -225,9 +165,6 @@ export const CHAIN_INFO: ChainInfoMap = {
infoLink: 'https://info.uniswap.org/#/polygon/',
label: 'Polygon Mumbai',
logoUrl: polygonMaticLogo,
addNetworkInfo: {
nativeCurrency: { name: 'Polygon Mumbai Matic', symbol: 'mMATIC', decimals: 18 },
rpcUrl: 'https://rpc-endpoints.superfluid.dev/mumbai',
},
nativeCurrency: { name: 'Polygon Mumbai Matic', symbol: 'mMATIC', decimals: 18 },
},
}

View File

@@ -39,7 +39,12 @@ export const ALL_SUPPORTED_CHAIN_IDS: SupportedChainId[] = Object.values(Support
(id) => typeof id === 'number'
) as SupportedChainId[]
export const SUPPORTED_GAS_ESTIMATE_CHAIN_IDS = [SupportedChainId.MAINNET, SupportedChainId.POLYGON]
export const SUPPORTED_GAS_ESTIMATE_CHAIN_IDS = [
SupportedChainId.MAINNET,
SupportedChainId.POLYGON,
SupportedChainId.OPTIMISM,
SupportedChainId.ARBITRUM_ONE,
]
/**
* All the chain IDs that are running the Ethereum protocol.

View File

@@ -12,6 +12,8 @@ export const COMMON_CONTRACT_NAMES: Record<number, { [address: string]: string }
[TIMELOCK_ADDRESS[SupportedChainId.MAINNET]]: 'Timelock',
[GOVERNANCE_ALPHA_V0_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance (V0)',
[GOVERNANCE_ALPHA_V1_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance',
'0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e': 'ENS Registry',
'0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41': 'ENS Public Resolver',
},
}
@@ -21,3 +23,5 @@ export const DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS = 13
export const AVERAGE_BLOCK_TIME_IN_SECS: { [chainId: number]: number } = {
1: DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS,
}
export const LATEST_GOVERNOR_INDEX = 2

23
src/constants/infura.ts Normal file
View File

@@ -0,0 +1,23 @@
import { SupportedChainId } from './chains'
const INFURA_KEY = process.env.REACT_APP_INFURA_KEY
if (typeof INFURA_KEY === 'undefined') {
throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`)
}
/**
* These are the network URLs used by the interface when there is not another available source of chain data
*/
export const INFURA_NETWORK_URLS: { [key in SupportedChainId]: string } = {
[SupportedChainId.MAINNET]: `https://mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.RINKEBY]: `https://rinkeby.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.ROPSTEN]: `https://ropsten.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.GOERLI]: `https://goerli.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.KOVAN]: `https://kovan.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.OPTIMISM]: `https://optimism-mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.OPTIMISTIC_KOVAN]: `https://optimism-kovan.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.ARBITRUM_ONE]: `https://arbitrum-mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.ARBITRUM_RINKEBY]: `https://arbitrum-rinkeby.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.POLYGON]: `https://polygon-mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.POLYGON_MUMBAI]: `https://polygon-mumbai.infura.io/v3/${INFURA_KEY}`,
}

View File

@@ -32,11 +32,11 @@ export const SUPPORTED_LOCALES = [
'vi-VN',
'zh-CN',
'zh-TW',
] as const
]
export type SupportedLocale = typeof SUPPORTED_LOCALES[number] | 'pseudo'
// eslint-disable-next-line import/first
import * as enUS from '../locales/en-US'
import * as enUS from 'locales/en-US'
export const DEFAULT_LOCALE: SupportedLocale = 'en-US'
export const DEFAULT_CATALOG = enUS

View File

@@ -18,8 +18,8 @@ import {
sETH2,
SWISE,
TRIBE,
USDC,
USDC_ARBITRUM,
USDC_MAINNET,
USDC_OPTIMISM,
USDC_POLYGON,
USDT,
@@ -44,13 +44,21 @@ type ChainCurrencyList = {
}
const WRAPPED_NATIVE_CURRENCIES_ONLY: ChainTokenList = Object.fromEntries(
Object.entries(WRAPPED_NATIVE_CURRENCY).map(([key, value]) => [key, [value]])
Object.entries(WRAPPED_NATIVE_CURRENCY)
.map(([key, value]) => [key, [value]])
.filter(Boolean)
)
// used to construct intermediary pairs for trading
export const BASES_TO_CHECK_TRADES_AGAINST: ChainTokenList = {
...WRAPPED_NATIVE_CURRENCIES_ONLY,
[SupportedChainId.MAINNET]: [...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC],
[SupportedChainId.MAINNET]: [
...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET],
DAI,
USDC_MAINNET,
USDT,
WBTC,
],
[SupportedChainId.OPTIMISM]: [
...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.OPTIMISM],
DAI_OPTIMISM,
@@ -90,7 +98,7 @@ export const ADDITIONAL_BASES: { [chainId: number]: { [tokenAddress: string]: To
*/
export const CUSTOM_BASES: { [chainId: number]: { [tokenAddress: string]: Token[] } } = {
[SupportedChainId.MAINNET]: {
[AMPL.address]: [DAI, WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET]],
[AMPL.address]: [DAI, WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET] as Token],
},
}
@@ -101,32 +109,38 @@ export const COMMON_BASES: ChainCurrencyList = {
[SupportedChainId.MAINNET]: [
nativeOnChain(SupportedChainId.MAINNET),
DAI,
USDC,
USDC_MAINNET,
USDT,
WBTC,
WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET],
WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET] as Token,
],
[SupportedChainId.ROPSTEN]: [
nativeOnChain(SupportedChainId.ROPSTEN),
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ROPSTEN],
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ROPSTEN] as Token,
],
[SupportedChainId.RINKEBY]: [
nativeOnChain(SupportedChainId.RINKEBY),
WRAPPED_NATIVE_CURRENCY[SupportedChainId.RINKEBY],
WRAPPED_NATIVE_CURRENCY[SupportedChainId.RINKEBY] as Token,
],
[SupportedChainId.GOERLI]: [
nativeOnChain(SupportedChainId.GOERLI),
WRAPPED_NATIVE_CURRENCY[SupportedChainId.GOERLI] as Token,
],
[SupportedChainId.KOVAN]: [
nativeOnChain(SupportedChainId.KOVAN),
WRAPPED_NATIVE_CURRENCY[SupportedChainId.KOVAN] as Token,
],
[SupportedChainId.GOERLI]: [nativeOnChain(SupportedChainId.GOERLI), WRAPPED_NATIVE_CURRENCY[SupportedChainId.GOERLI]],
[SupportedChainId.KOVAN]: [nativeOnChain(SupportedChainId.KOVAN), WRAPPED_NATIVE_CURRENCY[SupportedChainId.KOVAN]],
[SupportedChainId.ARBITRUM_ONE]: [
nativeOnChain(SupportedChainId.ARBITRUM_ONE),
DAI_ARBITRUM_ONE,
USDC_ARBITRUM,
USDT_ARBITRUM_ONE,
WBTC_ARBITRUM_ONE,
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_ONE],
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_ONE] as Token,
],
[SupportedChainId.ARBITRUM_RINKEBY]: [
nativeOnChain(SupportedChainId.ARBITRUM_RINKEBY),
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_RINKEBY],
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_RINKEBY] as Token,
],
[SupportedChainId.OPTIMISM]: [
nativeOnChain(SupportedChainId.OPTIMISM),
@@ -146,7 +160,7 @@ export const COMMON_BASES: ChainCurrencyList = {
],
[SupportedChainId.POLYGON_MUMBAI]: [
nativeOnChain(SupportedChainId.POLYGON_MUMBAI),
WRAPPED_NATIVE_CURRENCY[SupportedChainId.POLYGON_MUMBAI],
WRAPPED_NATIVE_CURRENCY[SupportedChainId.POLYGON_MUMBAI] as Token,
WETH_POLYGON_MUMBAI,
],
}
@@ -154,7 +168,13 @@ export const COMMON_BASES: ChainCurrencyList = {
// used to construct the list of all pairs we consider by default in the frontend
export const BASES_TO_TRACK_LIQUIDITY_FOR: ChainTokenList = {
...WRAPPED_NATIVE_CURRENCIES_ONLY,
[SupportedChainId.MAINNET]: [...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC],
[SupportedChainId.MAINNET]: [
...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET],
DAI,
USDC_MAINNET,
USDT,
WBTC,
],
}
export const PINNED_PAIRS: { readonly [chainId: number]: [Token, Token][] } = {
[SupportedChainId.MAINNET]: [
@@ -168,7 +188,7 @@ export const PINNED_PAIRS: { readonly [chainId: number]: [Token, Token][] } = {
'Compound USD Coin'
),
],
[USDC, USDT],
[USDC_MAINNET, USDT],
[DAI, USDT],
],
}

View File

@@ -1101,6 +1101,20 @@
"name": "BMEX",
"symbol": "BMEX",
"decimals": 18
},
{
"chainId": 1,
"address": "0x322A46E88fa3C78F9c9E3DBb0254b61664a06109",
"name": "Ukraine DAO",
"symbol": "Ukraine",
"decimals": 18
},
{
"chainId": 1,
"address": "0x8290D7a64F25e6b5002d98367E8367c1b532b534",
"name": "oneUNI",
"symbol": "oneUNI",
"decimals": 18
}
]
}

View File

@@ -1,8 +1,24 @@
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 AMPL = new Token(
SupportedChainId.MAINNET,
'0xD46bA6D942050d489DBd938a2C909A5d5039A161',
@@ -31,27 +47,19 @@ export const DAI_OPTIMISM = new Token(
'DAI',
'Dai stable coin'
)
export const USDC = new Token(
SupportedChainId.MAINNET,
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
6,
'USDC',
'USD//C'
)
export const USDC_ARBITRUM = new Token(
SupportedChainId.ARBITRUM_ONE,
'0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8',
6,
'USDC',
'USD//C'
)
export const USDC_POLYGON = new Token(
SupportedChainId.POLYGON,
'0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
6,
'USDC',
'USD//C'
)
export const USDC: { [chainId in SupportedChainId]: Token } = {
[SupportedChainId.MAINNET]: USDC_MAINNET,
[SupportedChainId.ARBITRUM_ONE]: USDC_ARBITRUM,
[SupportedChainId.OPTIMISM]: USDC_OPTIMISM,
[SupportedChainId.ARBITRUM_RINKEBY]: USDC_ARBITRUM_RINKEBY,
[SupportedChainId.OPTIMISTIC_KOVAN]: USDC_OPTIMISTIC_KOVAN,
[SupportedChainId.POLYGON]: USDC_POLYGON,
[SupportedChainId.POLYGON_MUMBAI]: USDC_POLYGON_MUMBAI,
[SupportedChainId.GOERLI]: USDC_GÖRLI,
[SupportedChainId.RINKEBY]: USDC_RINKEBY,
[SupportedChainId.KOVAN]: USDC_KOVAN,
[SupportedChainId.ROPSTEN]: USDC_ROPSTEN,
}
export const DAI_POLYGON = new Token(
SupportedChainId.POLYGON,
'0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063',
@@ -73,13 +81,6 @@ export const WBTC_POLYGON = new Token(
'WBTC',
'Wrapped BTC'
)
export const USDC_OPTIMISM = new Token(
SupportedChainId.OPTIMISM,
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
6,
'USDC',
'USD//C'
)
export const USDT = new Token(
SupportedChainId.MAINNET,
'0xdAC17F958D2ee523a2206206994597C13D831ec7',
@@ -208,8 +209,8 @@ export const UNI: { [chainId: number]: Token } = {
[SupportedChainId.KOVAN]: new Token(SupportedChainId.KOVAN, UNI_ADDRESS[42], 18, 'UNI', 'Uniswap'),
}
export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token } = {
...WETH9,
export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token | undefined } = {
...(WETH9 as Record<SupportedChainId, Token>),
[SupportedChainId.OPTIMISM]: new Token(
SupportedChainId.OPTIMISM,
'0x4200000000000000000000000000000000000006',
@@ -265,7 +266,9 @@ class MaticNativeCurrency extends NativeCurrency {
get wrapped(): Token {
if (!isMatic(this.chainId)) throw new Error('Not matic')
return WRAPPED_NATIVE_CURRENCY[this.chainId]
const wrapped = WRAPPED_NATIVE_CURRENCY[this.chainId]
invariant(wrapped instanceof Token)
return wrapped
}
public constructor(chainId: number) {
@@ -276,7 +279,8 @@ class MaticNativeCurrency extends NativeCurrency {
export class ExtendedEther extends Ether {
public get wrapped(): Token {
if (this.chainId in WRAPPED_NATIVE_CURRENCY) return WRAPPED_NATIVE_CURRENCY[this.chainId]
const wrapped = WRAPPED_NATIVE_CURRENCY[this.chainId]
if (wrapped) return wrapped
throw new Error('Unsupported chain ID')
}
@@ -296,3 +300,19 @@ export function nativeOnChain(chainId: number): NativeCurrency {
: ExtendedEther.onChain(chainId))
)
}
export const TOKEN_SHORTHANDS: { [shorthand: string]: { [chainId in SupportedChainId]?: string } } = {
USDC: {
[SupportedChainId.MAINNET]: USDC_MAINNET.address,
[SupportedChainId.ARBITRUM_ONE]: USDC_ARBITRUM.address,
[SupportedChainId.OPTIMISM]: USDC_OPTIMISM.address,
[SupportedChainId.ARBITRUM_RINKEBY]: USDC_ARBITRUM_RINKEBY.address,
[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.RINKEBY]: USDC_RINKEBY.address,
[SupportedChainId.KOVAN]: USDC_KOVAN.address,
[SupportedChainId.ROPSTEN]: USDC_ROPSTEN.address,
},
}

View File

@@ -1,4 +1,4 @@
import { AbstractConnector } from '@web3-react/abstract-connector'
import { AbstractConnector } from 'web3-react-abstract-connector'
import INJECTED_ICON_URL from '../assets/images/arrow-right.svg'
import COINBASE_ICON_URL from '../assets/images/coinbaseWalletIcon.svg'

View File

@@ -2,7 +2,7 @@ import { Currency, Token } from '@uniswap/sdk-core'
import { CHAIN_INFO } from 'constants/chainInfo'
import { L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useCurrencyFromMap, useTokenFromMap } from 'lib/hooks/useCurrency'
import { useCurrencyFromMap, useTokenFromMapOrNetwork } from 'lib/hooks/useCurrency'
import { getTokenFilter } from 'lib/hooks/useTokenList/filtering'
import { useMemo } from 'react'
@@ -159,7 +159,7 @@ export function useIsUserAddedToken(currency: Currency | undefined | null): bool
// otherwise returns the token
export function useToken(tokenAddress?: string | null): Token | null | undefined {
const tokens = useAllTokens()
return useTokenFromMap(tokens, tokenAddress)
return useTokenFromMapOrNetwork(tokens, tokenAddress)
}
export function useCurrency(currencyId?: string | null): Currency | null | undefined {

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { Web3Provider } from '@ethersproject/providers'
import { useWeb3React } from '@web3-react/core'
import { default as useWidgetsWeb3React } from 'lib/hooks/useActiveWeb3React'
import { useWeb3React } from 'web3-react-core'
import { NetworkContextName } from '../constants/misc'

View File

@@ -1,5 +1,5 @@
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'state/routing/clientSideSmartOrderRouter/constants'
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'lib/hooks/routing/clientSideSmartOrderRouter'
export default function useAutoRouterSupported(): boolean {
const { chainId } = useActiveWeb3React()

View File

@@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react-hooks'
import { CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { DAI, USDC } from 'constants/tokens'
import { DAI, USDC_MAINNET } from 'constants/tokens'
import { TradeState } from 'state/routing/types'
import { useRoutingAPITrade } from '../state/routing/useRoutingAPITrade'
@@ -10,7 +10,7 @@ import { useClientSideV3Trade } from './useClientSideV3Trade'
import useDebounce from './useDebounce'
import useIsWindowVisible from './useIsWindowVisible'
const USDCAmount = CurrencyAmount.fromRawAmount(USDC, '10000')
const USDCAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '10000')
const DAIAmount = CurrencyAmount.fromRawAmount(DAI, '10000')
jest.mock('./useAutoRouterSupported')
@@ -126,10 +126,10 @@ describe('#useBestV3Trade ExactOut', () => {
expectRouterMock(TradeState.INVALID)
expectClientSideMock(TradeState.VALID)
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC)
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC)
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC_MAINNET)
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
})
@@ -138,17 +138,17 @@ describe('#useBestV3Trade ExactOut', () => {
expectRouterMock(TradeState.NO_ROUTE_FOUND)
expectClientSideMock(TradeState.VALID)
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC)
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC)
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC_MAINNET)
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
})
describe('when routing api is in non-error state', () => {
it('does not compute client side v3 trade if routing api is LOADING', () => {
expectRouterMock(TradeState.LOADING)
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
expect(result.current).toEqual({ state: TradeState.LOADING, trade: undefined })
@@ -157,7 +157,7 @@ describe('#useBestV3Trade ExactOut', () => {
it('does not compute client side v3 trade if routing api is VALID', () => {
expectRouterMock(TradeState.VALID)
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
@@ -166,7 +166,7 @@ describe('#useBestV3Trade ExactOut', () => {
it('does not compute client side v3 trade if routing api is SYNCING', () => {
expectRouterMock(TradeState.SYNCING)
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
expect(result.current).toEqual({ state: TradeState.SYNCING, trade: undefined })
@@ -178,7 +178,7 @@ describe('#useBestV3Trade ExactOut', () => {
expectRouterMock(TradeState.INVALID)
expectClientSideMock(TradeState.VALID)
renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
})
@@ -187,9 +187,9 @@ describe('#useBestV3Trade ExactOut', () => {
expectRouterMock(TradeState.NO_ROUTE_FOUND)
expectClientSideMock(TradeState.VALID)
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC)
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
})
})

View File

@@ -36,21 +36,8 @@ export function useBestTrade(
debouncedOtherCurrency
)
const isLoading = amountSpecified !== undefined && debouncedAmount === undefined
// consider trade debouncing when inputs/outputs do not match
const debouncing =
routingAPITrade.trade &&
amountSpecified &&
(tradeType === TradeType.EXACT_INPUT
? !routingAPITrade.trade.inputAmount.equalTo(amountSpecified) ||
!amountSpecified.currency.equals(routingAPITrade.trade.inputAmount.currency) ||
!debouncedOtherCurrency?.equals(routingAPITrade.trade.outputAmount.currency)
: !routingAPITrade.trade.outputAmount.equalTo(amountSpecified) ||
!amountSpecified.currency.equals(routingAPITrade.trade.outputAmount.currency) ||
!debouncedOtherCurrency?.equals(routingAPITrade.trade.inputAmount.currency))
const useFallback = !autoRouterSupported || (!debouncing && routingAPITrade.state === TradeState.NO_ROUTE_FOUND)
const isLoading = routingAPITrade.state === TradeState.LOADING
const useFallback = !autoRouterSupported || routingAPITrade.state === TradeState.NO_ROUTE_FOUND
// only use client side router if routing api trade failed or is not supported
const bestV3Trade = useClientSideV3Trade(
@@ -63,9 +50,8 @@ export function useBestTrade(
return useMemo(
() => ({
...(useFallback ? bestV3Trade : routingAPITrade),
...(debouncing ? { state: TradeState.SYNCING } : {}),
...(isLoading ? { state: TradeState.LOADING } : {}),
}),
[bestV3Trade, debouncing, isLoading, routingAPITrade, useFallback]
[bestV3Trade, isLoading, routingAPITrade, useFallback]
)
}

View File

@@ -1,7 +1,7 @@
import { Token } from '@uniswap/sdk-core'
import { SupportedChainId } from 'constants/chains'
import uriToHttp from 'lib/utils/uriToHttp'
import Vibrant from 'node-vibrant/lib/bundle'
import Vibrant from 'node-vibrant/lib/bundle.js'
import { shade } from 'polished'
import { useLayoutEffect, useState } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'

View File

@@ -1,15 +1,11 @@
import { Contract } from '@ethersproject/contracts'
import { abi as GOVERNANCE_ABI } from '@uniswap/governance/build/GovernorAlpha.json'
import { abi as UNI_ABI } from '@uniswap/governance/build/Uni.json'
import { abi as STAKING_REWARDS_ABI } from '@uniswap/liquidity-staker/build/StakingRewards.json'
import { abi as MERKLE_DISTRIBUTOR_ABI } from '@uniswap/merkle-distributor/build/MerkleDistributor.json'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { abi as IUniswapV2Router02ABI } from '@uniswap/v2-periphery/build/IUniswapV2Router02.json'
import { abi as QuoterABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/Quoter.sol/Quoter.json'
import { abi as TickLensABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/TickLens.sol/TickLens.json'
import { abi as MulticallABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/UniswapInterfaceMulticall.sol/UniswapInterfaceMulticall.json'
import { abi as NFTPositionManagerABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
import { abi as V2MigratorABI } from '@uniswap/v3-periphery/artifacts/contracts/V3Migrator.sol/V3Migrator.json'
import IUniswapV2PairJson from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import IUniswapV2Router02Json from '@uniswap/v2-periphery/build/IUniswapV2Router02.json'
import QuoterJson from '@uniswap/v3-periphery/artifacts/contracts/lens/Quoter.sol/Quoter.json'
import TickLensJson from '@uniswap/v3-periphery/artifacts/contracts/lens/TickLens.sol/TickLens.json'
import UniswapInterfaceMulticallJson from '@uniswap/v3-periphery/artifacts/contracts/lens/UniswapInterfaceMulticall.sol/UniswapInterfaceMulticall.json'
import NonfungiblePositionManagerJson from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
import V3MigratorJson from '@uniswap/v3-periphery/artifacts/contracts/V3Migrator.sol/V3Migrator.json'
import ARGENT_WALLET_DETECTOR_ABI from 'abis/argent-wallet-detector.json'
import EIP_2612 from 'abis/eip_2612.json'
import ENS_PUBLIC_RESOLVER_ABI from 'abis/ens-public-resolver.json'
@@ -18,16 +14,11 @@ import ERC20_ABI from 'abis/erc20.json'
import ERC20_BYTES32_ABI from 'abis/erc20_bytes32.json'
import ERC721_ABI from 'abis/erc721.json'
import ERC1155_ABI from 'abis/erc1155.json'
import GOVERNOR_BRAVO_ABI from 'abis/governor-bravo.json'
import { ArgentWalletDetector, EnsPublicResolver, EnsRegistrar, Erc20, Erc721, Erc1155, Weth } from 'abis/types'
import WETH_ABI from 'abis/weth.json'
import {
ARGENT_WALLET_DETECTOR_ADDRESS,
ENS_REGISTRAR_ADDRESSES,
GOVERNANCE_ALPHA_V0_ADDRESSES,
GOVERNANCE_ALPHA_V1_ADDRESSES,
GOVERNANCE_BRAVO_ADDRESSES,
MERKLE_DISTRIBUTOR_ADDRESS,
MULTICALL_ADDRESS,
NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,
QUOTER_ADDRESSES,
@@ -35,7 +26,7 @@ import {
V2_ROUTER_ADDRESS,
V3_MIGRATOR_ADDRESSES,
} from 'constants/addresses'
import { UNI, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
import { WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useMemo } from 'react'
import { NonfungiblePositionManager, Quoter, TickLens, UniswapInterfaceMulticall } from 'types/v3'
@@ -43,6 +34,14 @@ import { V3Migrator } from 'types/v3/V3Migrator'
import { getContract } from '../utils'
const { abi: IUniswapV2PairABI } = IUniswapV2PairJson
const { abi: IUniswapV2Router02ABI } = IUniswapV2Router02Json
const { abi: QuoterABI } = QuoterJson
const { abi: TickLensABI } = TickLensJson
const { abi: MulticallABI } = UniswapInterfaceMulticallJson
const { abi: NFTPositionManagerABI } = NonfungiblePositionManagerJson
const { abi: V2MigratorABI } = V3MigratorJson
// returns null on errors
export function useContract<T extends Contract = Contract>(
addressOrAddressMap: string | { [chainId: number]: string } | undefined,
@@ -123,33 +122,6 @@ export function useInterfaceMulticall() {
return useContract<UniswapInterfaceMulticall>(MULTICALL_ADDRESS, MulticallABI, false) as UniswapInterfaceMulticall
}
export function useMerkleDistributorContract() {
return useContract(MERKLE_DISTRIBUTOR_ADDRESS, MERKLE_DISTRIBUTOR_ABI, true)
}
export function useGovernanceV0Contract(): Contract | null {
return useContract(GOVERNANCE_ALPHA_V0_ADDRESSES, GOVERNANCE_ABI, false)
}
export function useGovernanceV1Contract(): Contract | null {
return useContract(GOVERNANCE_ALPHA_V1_ADDRESSES, GOVERNANCE_ABI, false)
}
export function useGovernanceBravoContract(): Contract | null {
return useContract(GOVERNANCE_BRAVO_ADDRESSES, GOVERNOR_BRAVO_ABI, true)
}
export const useLatestGovernanceContract = useGovernanceBravoContract
export function useUniContract() {
const { chainId } = useActiveWeb3React()
return useContract(chainId ? UNI[chainId]?.address : undefined, UNI_ABI, true)
}
export function useStakingContract(stakingAddress?: string, withSignerIfPossible?: boolean) {
return useContract(stakingAddress, STAKING_REWARDS_ABI, withSignerIfPossible)
}
export function useV3NFTPositionManagerContract(withSignerIfPossible?: boolean): NonfungiblePositionManager | null {
return useContract<NonfungiblePositionManager>(
NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,

View File

@@ -1,10 +1,15 @@
import { BigNumber } from '@ethersproject/bignumber'
import { useSingleCallResult } from 'lib/hooks/multicall'
import { useMemo } from 'react'
import { useInterfaceMulticall } from './useContract'
// gets the current timestamp from the blockchain
export default function useCurrentBlockTimestamp(): BigNumber | undefined {
const multicall = useInterfaceMulticall()
return useSingleCallResult(multicall, 'getCurrentBlockTimestamp')?.result?.[0]
const resultStr: string | undefined = useSingleCallResult(
multicall,
'getCurrentBlockTimestamp'
)?.result?.[0]?.toString()
return useMemo(() => (typeof resultStr === 'string' ? BigNumber.from(resultStr) : undefined), [resultStr])
}

View File

@@ -10,7 +10,7 @@ import { useSingleCallResult } from 'lib/hooks/multicall'
import { useMemo, useState } from 'react'
import { SWAP_ROUTER_ADDRESSES, V3_ROUTER_ADDRESS } from '../constants/addresses'
import { DAI, UNI, USDC } from '../constants/tokens'
import { DAI, UNI, USDC_MAINNET } from '../constants/tokens'
import { useEIP2612Contract } from './useContract'
import useIsArgentWallet from './useIsArgentWallet'
@@ -36,7 +36,7 @@ const PERMITTABLE_TOKENS: {
}
} = {
1: {
[USDC.address]: { type: PermitType.AMOUNT, name: 'USD Coin', version: '2' },
[USDC_MAINNET.address]: { type: PermitType.AMOUNT, name: 'USD Coin', version: '2' },
[DAI.address]: { type: PermitType.ALLOWED, name: 'Dai Stablecoin', version: '1' },
[UNI[1].address]: { type: PermitType.AMOUNT, name: 'Uniswap' },
},

View File

@@ -1,5 +1,6 @@
import JSBI from 'jsbi'
import { useSingleCallResult } from 'lib/hooks/multicall'
import { useMemo } from 'react'
import { useContract } from './useContract'
import useENSAddress from './useENSAddress'
@@ -22,5 +23,5 @@ export default function useGasPrice(): JSBI | undefined {
const contract = useContract(address ?? undefined, CHAIN_DATA_ABI, false)
const resultStr = useSingleCallResult(contract, 'latestAnswer').result?.[0]?.toString()
return typeof resultStr === 'string' ? JSBI.BigInt(resultStr) : undefined
return useMemo(() => (typeof resultStr === 'string' ? JSBI.BigInt(resultStr) : undefined), [resultStr])
}

View File

@@ -9,5 +9,5 @@ export default function useIsArgentWallet(): boolean {
const argentWalletDetector = useArgentWalletDetectorContract()
const inputs = useMemo(() => [account ?? undefined], [account])
const call = useSingleCallResult(argentWalletDetector, 'isArgentWallet', inputs, NEVER_RELOAD)
return call?.result?.[0] ?? false
return Boolean(call?.result?.[0])
}

View File

@@ -1,9 +1,11 @@
import { useCallback, useEffect, useState } from 'react'
const VISIBILITY_STATE_SUPPORTED = 'visibilityState' in document
function isVisibilityStateSupported() {
return 'visibilityState' in document
}
function isWindowVisible() {
return !VISIBILITY_STATE_SUPPORTED || document.visibilityState !== 'hidden'
return !isVisibilityStateSupported() || document.visibilityState !== 'hidden'
}
/**
@@ -16,7 +18,7 @@ export default function useIsWindowVisible(): boolean {
}, [setFocused])
useEffect(() => {
if (!VISIBILITY_STATE_SUPPORTED) return undefined
if (!isVisibilityStateSupported()) return undefined
document.addEventListener('visibilitychange', listener)
return () => {

View File

@@ -1,17 +1,58 @@
import { Interface } from '@ethersproject/abi'
import { Currency, Token } from '@uniswap/sdk-core'
import { abi as IUniswapV3PoolStateABI } from '@uniswap/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState.sol/IUniswapV3PoolState.json'
import { BigintIsh, Currency, Token } from '@uniswap/sdk-core'
import IUniswapV3PoolStateJson 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'
import JSBI from 'jsbi'
import { useMultipleContractSingleData } from 'lib/hooks/multicall'
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.
private static pools: Pool[] = []
static getPool(
tokenA: Token,
tokenB: Token,
fee: FeeAmount,
sqrtPriceX96: BigintIsh,
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)
}
const found = this.pools.find(
(pool) =>
pool.token0 === tokenA &&
pool.token1 === tokenB &&
pool.fee === fee &&
JSBI.EQ(pool.sqrtRatioX96, sqrtPriceX96) &&
JSBI.EQ(pool.liquidity, liquidity) &&
pool.tickCurrent === tick
)
if (found) {
return found
}
const pool = new Pool(tokenA, tokenB, fee, sqrtPriceX96, liquidity, tick)
this.pools.unshift(pool)
return pool
}
}
export enum PoolState {
LOADING,
NOT_EXISTS,
@@ -24,58 +65,66 @@ export function usePools(
): [PoolState, Pool | null][] {
const { chainId } = useActiveWeb3React()
const transformed: ([Token, Token, FeeAmount] | null)[] = useMemo(() => {
return poolKeys.map(([currencyA, currencyB, feeAmount]) => {
if (!chainId || !currencyA || !currencyB || !feeAmount) return null
const poolTokens: ([Token, Token, FeeAmount] | undefined)[] = useMemo(() => {
if (!chainId) return new Array(poolKeys.length)
const tokenA = currencyA?.wrapped
const tokenB = currencyB?.wrapped
if (!tokenA || !tokenB || tokenA.equals(tokenB)) return null
const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
return [token0, token1, feeAmount]
return poolKeys.map(([currencyA, currencyB, feeAmount]) => {
if (currencyA && currencyB && feeAmount) {
const tokenA = currencyA.wrapped
const tokenB = currencyB.wrapped
if (tokenA.equals(tokenB)) return undefined
return tokenA.sortsBefore(tokenB) ? [tokenA, tokenB, feeAmount] : [tokenB, tokenA, feeAmount]
}
return undefined
})
}, [chainId, poolKeys])
const poolAddresses: (string | undefined)[] = useMemo(() => {
const v3CoreFactoryAddress = chainId && V3_CORE_FACTORY_ADDRESSES[chainId]
if (!v3CoreFactoryAddress) return new Array(poolTokens.length)
return transformed.map((value) => {
if (!v3CoreFactoryAddress || !value) return undefined
return computePoolAddress({
factoryAddress: v3CoreFactoryAddress,
tokenA: value[0],
tokenB: value[1],
fee: value[2],
})
})
}, [chainId, transformed])
return poolTokens.map(
(value) =>
value &&
computePoolAddress({
factoryAddress: v3CoreFactoryAddress,
tokenA: value[0],
tokenB: value[1],
fee: value[2],
})
)
}, [chainId, poolTokens])
const slot0s = useMultipleContractSingleData(poolAddresses, POOL_STATE_INTERFACE, 'slot0')
const liquidities = useMultipleContractSingleData(poolAddresses, POOL_STATE_INTERFACE, 'liquidity')
return useMemo(() => {
return poolKeys.map((_key, index) => {
const [token0, token1, fee] = transformed[index] ?? []
if (!token0 || !token1 || !fee) return [PoolState.INVALID, null]
const tokens = poolTokens[index]
if (!tokens) return [PoolState.INVALID, null]
const [token0, token1, fee] = tokens
if (!slot0s[index]) return [PoolState.INVALID, null]
const { result: slot0, loading: slot0Loading, valid: slot0Valid } = slot0s[index]
if (!liquidities[index]) return [PoolState.INVALID, null]
const { result: liquidity, loading: liquidityLoading, valid: liquidityValid } = liquidities[index]
if (!slot0Valid || !liquidityValid) return [PoolState.INVALID, null]
if (!tokens || !slot0Valid || !liquidityValid) return [PoolState.INVALID, null]
if (slot0Loading || liquidityLoading) return [PoolState.LOADING, null]
if (!slot0 || !liquidity) return [PoolState.NOT_EXISTS, null]
if (!slot0.sqrtPriceX96 || slot0.sqrtPriceX96.eq(0)) return [PoolState.NOT_EXISTS, null]
try {
return [PoolState.EXISTS, new Pool(token0, token1, fee, slot0.sqrtPriceX96, liquidity[0], slot0.tick)]
const pool = PoolCache.getPool(token0, token1, fee, slot0.sqrtPriceX96, liquidity[0], slot0.tick)
return [PoolState.EXISTS, pool]
} catch (error) {
console.error('Error when constructing the pool', error)
return [PoolState.NOT_EXISTS, null]
}
})
}, [liquidities, poolKeys, slot0s, transformed])
}, [liquidities, poolKeys, slot0s, poolTokens])
}
export function usePool(

View File

@@ -2,7 +2,7 @@ import { BigNumber } from '@ethersproject/bignumber'
import { SwapRouter, Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Router as V2SwapRouter, Trade as V2Trade } from '@uniswap/v2-sdk'
import { SwapRouter as V3SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
import { FeeOptions, SwapRouter as V3SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
import { SWAP_ROUTER_ADDRESSES, V3_ROUTER_ADDRESS } from 'constants/addresses'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useMemo } from 'react'
@@ -36,7 +36,8 @@ export function useSwapCallArguments(
allowedSlippage: Percent,
recipientAddressOrName: string | null | undefined,
signatureData: SignatureData | null | undefined,
deadline: BigNumber | undefined
deadline: BigNumber | undefined,
feeOptions: FeeOptions | undefined
): SwapCall[] {
const { account, chainId, library } = useActiveWeb3React()
@@ -98,6 +99,7 @@ export function useSwapCallArguments(
} else {
// swap options shared by v3 and v2+v3 swap routers
const sharedSwapOptions = {
fee: feeOptions,
recipient,
slippageTolerance: allowedSlippage,
...(signatureData
@@ -167,15 +169,16 @@ export function useSwapCallArguments(
]
}
}, [
trade,
recipient,
library,
account,
chainId,
deadline,
routerContract,
allowedSlippage,
argentWalletContract,
chainId,
deadline,
feeOptions,
library,
recipient,
routerContract,
signatureData,
trade,
])
}

View File

@@ -33,7 +33,7 @@ export function useSwapCallback(
state,
callback: libCallback,
error,
} = useLibSwapCallBack(trade, allowedSlippage, recipient, signatureData, deadline)
} = useLibSwapCallBack({ trade, allowedSlippage, recipientAddressOrName: recipient, signatureData, deadline })
const callback = useMemo(() => {
if (!libCallback || !trade) {

View File

@@ -1,6 +1,6 @@
import { BigNumber } from '@ethersproject/bignumber'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useSingleCallResult } from 'lib/hooks/multicall'
import { useMemo } from 'react'
import { useTokenContract } from './useContract'
@@ -9,7 +9,10 @@ import { useTokenContract } from './useContract'
export function useTotalSupply(token?: Currency): CurrencyAmount<Token> | undefined {
const contract = useTokenContract(token?.isToken ? token.address : undefined, false)
const totalSupply: BigNumber = useSingleCallResult(contract, 'totalSupply')?.result?.[0]
const totalSupplyStr: string | undefined = useSingleCallResult(contract, 'totalSupply')?.result?.[0]?.toString()
return token?.isToken && totalSupply ? CurrencyAmount.fromRawAmount(token, totalSupply.toString()) : undefined
return useMemo(
() => (token?.isToken && totalSupplyStr ? CurrencyAmount.fromRawAmount(token, totalSupplyStr) : undefined),
[token, totalSupplyStr]
)
}

View File

@@ -4,14 +4,14 @@ import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { useMemo } from 'react'
import { SupportedChainId } from '../constants/chains'
import { DAI_OPTIMISM, USDC, USDC_ARBITRUM, USDC_POLYGON } from '../constants/tokens'
import { DAI_OPTIMISM, USDC_ARBITRUM, USDC_MAINNET, USDC_POLYGON } from '../constants/tokens'
import { useBestV2Trade } from './useBestV2Trade'
import { useClientSideV3Trade } from './useClientSideV3Trade'
// Stablecoin amounts used when calculating spot price for a given currency.
// The amount is large enough to filter low liquidity pairs.
export const STABLECOIN_AMOUNT_OUT: { [chainId: number]: CurrencyAmount<Token> } = {
[SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(USDC, 100_000e6),
[SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(USDC_MAINNET, 100_000e6),
[SupportedChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(USDC_ARBITRUM, 10_000e6),
[SupportedChainId.OPTIMISM]: CurrencyAmount.fromRawAmount(DAI_OPTIMISM, 10_000e18),
[SupportedChainId.POLYGON]: CurrencyAmount.fromRawAmount(USDC_POLYGON, 10_000e6),
@@ -78,17 +78,18 @@ export function useStablecoinAmountFromFiatValue(fiatValue: string | null | unde
const { chainId } = useActiveWeb3React()
const stablecoin = chainId ? STABLECOIN_AMOUNT_OUT[chainId]?.currency : undefined
if (fiatValue === null || fiatValue === undefined || !chainId || !stablecoin) {
return undefined
}
return useMemo(() => {
if (fiatValue === null || fiatValue === undefined || !chainId || !stablecoin) {
return undefined
}
// trim for decimal precision when parsing
const parsedForDecimals = parseFloat(fiatValue).toFixed(stablecoin.decimals).toString()
try {
// parse USD string into CurrencyAmount based on stablecoin decimals
return tryParseCurrencyAmount(parsedForDecimals, stablecoin)
} catch (error) {
return undefined
}
// trim for decimal precision when parsing
const parsedForDecimals = parseFloat(fiatValue).toFixed(stablecoin.decimals).toString()
try {
// parse USD string into CurrencyAmount based on stablecoin decimals
return tryParseCurrencyAmount(parsedForDecimals, stablecoin)
} catch (error) {
return undefined
}
}, [chainId, fiatValue, stablecoin])
}

View File

@@ -1,12 +1,14 @@
import { Interface } from '@ethersproject/abi'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import IUniswapV2PairJson 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

@@ -1,6 +1,6 @@
import { useWeb3React } from '@web3-react/core'
import type { EthereumProvider } from 'lib/ethereum'
import { useEffect, useState } from 'react'
import { useWeb3React } from 'web3-react-core'
import { gnosisSafe, injected } from '../connectors'
import { IS_IN_IFRAME } from '../constants/misc'

View File

@@ -3,13 +3,13 @@ import 'inter-ui'
import 'polyfills'
import 'components/analytics'
import { createWeb3ReactRoot, Web3ReactProvider } from '@web3-react/core'
import { BlockUpdater } from 'lib/hooks/useBlockNumber'
import { MulticallUpdater } from 'lib/state/multicall'
import { StrictMode } from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { HashRouter } from 'react-router-dom'
import { createWeb3ReactRoot, Web3ReactProvider } from 'web3-react-core'
import Blocklist from './components/Blocklist'
import { NetworkContextName } from './constants/misc'
@@ -73,4 +73,3 @@ ReactDOM.render(
if (process.env.REACT_APP_SERVICE_WORKER !== 'false') {
serviceWorkerRegistration.register()
}
export { INFURA_NETWORK_URLS } from 'constants/chainInfo'

View File

@@ -1,225 +0,0 @@
@import "~@fontsource/ibm-plex-mono/400.css";
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 400;
src: url("~@fontsource/inter/files/inter-cyrillic-ext-400-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-400-normal.woff") format("woff");
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 400;
src: url("~@fontsource/inter/files/inter-cyrillic-400-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-400-normal.woff") format("woff");
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 400;
src: url("~@fontsource/inter/files/inter-greek-ext-400-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-400-normal.woff") format("woff");
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 400;
src: url("~@fontsource/inter/files/inter-greek-400-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-400-normal.woff") format("woff");
unicode-range: U+0370-03FF;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 400;
src: url("~@fontsource/inter/files/inter-vietnamese-400-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-400-normal.woff") format("woff");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 400;
src: url("~@fontsource/inter/files/inter-latin-ext-400-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-400-normal.woff") format("woff");
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 400;
src: url("~@fontsource/inter/files/inter-latin-400-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-400-normal.woff") format("woff");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 500;
src: url("~@fontsource/inter/files/inter-cyrillic-ext-500-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-500-normal.woff") format("woff");
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 500;
src: url("~@fontsource/inter/files/inter-cyrillic-500-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-500-normal.woff") format("woff");
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 500;
src: url("~@fontsource/inter/files/inter-greek-ext-500-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-500-normal.woff") format("woff");
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 500;
src: url("~@fontsource/inter/files/inter-greek-500-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-500-normal.woff") format("woff");
unicode-range: U+0370-03FF;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 500;
src: url("~@fontsource/inter/files/inter-vietnamese-500-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-500-normal.woff") format("woff");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 500;
src: url("~@fontsource/inter/files/inter-latin-ext-500-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-500-normal.woff") format("woff");
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 500;
src: url("~@fontsource/inter/files/inter-latin-500-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-500-normal.woff") format("woff");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 600;
src: url("~@fontsource/inter/files/inter-cyrillic-ext-600-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-600-normal.woff") format("woff");
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 600;
src: url("~@fontsource/inter/files/inter-cyrillic-600-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-600-normal.woff") format("woff");
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 600;
src: url("~@fontsource/inter/files/inter-greek-ext-600-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-600-normal.woff") format("woff");
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 600;
src: url("~@fontsource/inter/files/inter-greek-600-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-600-normal.woff") format("woff");
unicode-range: U+0370-03FF;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 600;
src: url("~@fontsource/inter/files/inter-vietnamese-600-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-600-normal.woff") format("woff");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 600;
src: url("~@fontsource/inter/files/inter-latin-ext-600-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-600-normal.woff") format("woff");
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-display: block;
font-weight: 600;
src: url("~@fontsource/inter/files/inter-latin-600-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-600-normal.woff") format("woff");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: "InterVariable";
font-style: normal;
font-display: block;
font-weight: 100 900;
src: url("~@fontsource/inter/files/inter-cyrillic-ext-variable-wghtOnly-normal.woff2") format("woff2");
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
font-family: "InterVariable";
font-style: normal;
font-display: block;
font-weight: 100 900;
src: url("~@fontsource/inter/files/inter-cyrillic-variable-wghtOnly-normal.woff2") format("woff2");
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: "InterVariable";
font-style: normal;
font-display: block;
font-weight: 100 900;
src: url("~@fontsource/inter/files/inter-greek-ext-variable-wghtOnly-normal.woff2") format("woff2");
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: "InterVariable";
font-style: normal;
font-display: block;
font-weight: 100 900;
src: url("~@fontsource/inter/files/inter-greek-variable-wghtOnly-normal.woff2") format("woff2");
unicode-range: U+0370-03FF;
}
@font-face {
font-family: "InterVariable";
font-style: normal;
font-display: block;
font-weight: 100 900;
src: url("~@fontsource/inter/files/inter-vietnamese-variable-wghtOnly-normal.woff2") format("woff2");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: "InterVariable";
font-style: normal;
font-display: block;
font-weight: 100 900;
src: url("~@fontsource/inter/files/inter-latin-ext-variable-wghtOnly-normal.woff2") format("woff2");
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: "InterVariable";
font-style: normal;
font-display: block;
font-weight: 100 900;
src: url("~@fontsource/inter/files/inter-latin-variable-wghtOnly-normal.woff2") format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,12 +0,0 @@
import * as React from 'react'
function SvgCheck(props: React.SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<circle cx={10} cy={10} r={10} />
<path d="M14 7l-5.5 5.5L6 10" stroke="#fff" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
}
export default SvgCheck

View File

@@ -1,19 +0,0 @@
import * as React from 'react'
function SvgExpando(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
strokeWidth={2}
strokeLinecap="round"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path className="expando_svg__left" d="M18 15l-6-6" />
<path className="expando_svg__right" d="M12 9l-6 6" />
</svg>
)
}
export default SvgExpando

View File

@@ -1,18 +0,0 @@
import * as React from 'react'
function SvgLogo(props: React.SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 14 15" xmlns="http://www.w3.org/2000/svg" {...props}>
<path d="M4.152 1.551c-.188-.029-.196-.032-.107-.045.17-.026.57.009.846.074.644.152 1.23.542 1.856 1.235l.166.184.238-.038c1.002-.16 2.02-.033 2.873.358.235.108.605.322.65.377.016.018.043.13.06.251.064.418.033.737-.096.976-.07.13-.074.171-.027.283a.274.274 0 00.246.154c.212 0 .44-.34.545-.814l.042-.189.083.094c.457.514.815 1.214.876 1.712l.016.13-.076-.118a1.462 1.462 0 00-.435-.453c-.306-.201-.63-.27-1.486-.315-.774-.04-1.212-.106-1.646-.247-.739-.24-1.111-.558-1.989-1.702-.39-.509-.63-.79-.87-1.016-.545-.515-1.08-.785-1.765-.89z" />
<path d="M10.85 2.686c.019-.34.065-.565.159-.77a.825.825 0 01.077-.148c.005 0-.011.06-.036.133-.068.2-.08.472-.032.789.06.402.093.46.52.894.201.204.434.46.519.571l.154.2-.154-.143c-.188-.175-.62-.517-.716-.566-.064-.032-.074-.032-.113.007-.037.036-.044.09-.05.346-.007.399-.062.655-.194.91-.071.14-.082.11-.018-.047.048-.116.053-.168.053-.554 0-.775-.094-.962-.637-1.28a5.971 5.971 0 00-.504-.26 1.912 1.912 0 01-.246-.12c.015-.015.545.139.758.22.318.122.37.137.409.123.025-.01.038-.085.05-.305zM4.517 4.013c-.381-.522-.618-1.323-.566-1.922l.015-.185.087.015c.164.03.445.134.577.214.361.218.518.505.677 1.243.047.216.108.46.136.544.045.133.217.444.356.646.1.146.034.215-.188.195-.339-.03-.798-.345-1.094-.75zM10.386 7.9c-1.784-.713-2.412-1.333-2.412-2.378 0-.154.005-.28.012-.28.006 0 .075.05.153.113.362.288.767.411 1.889.574.66.096 1.03.173 1.373.286 1.09.359 1.763 1.087 1.924 2.08.046.288.02.828-.057 1.113-.06.225-.242.63-.29.646-.014.005-.027-.046-.03-.116-.018-.372-.208-.735-.526-1.007-.362-.309-.848-.555-2.036-1.03zM9.134 8.197a3.133 3.133 0 00-.086-.375l-.046-.135.085.095c.117.13.21.297.288.52.06.17.066.22.066.496 0 .271-.008.328-.064.48a1.518 1.518 0 01-.376.596c-.326.33-.745.512-1.35.588-.105.013-.411.035-.68.049-.679.035-1.126.108-1.527.248a.324.324 0 01-.115.027c-.016-.016.258-.178.483-.286.318-.153.635-.236 1.345-.353.35-.058.713-.129.805-.157.868-.264 1.315-.947 1.172-1.793z" />
<path d="M9.952 9.641c-.237-.506-.292-.995-.162-1.451.014-.05.036-.089.05-.089.013 0 .07.03.124.067.11.073.328.196.912.512.728.395 1.144.7 1.426 1.05.247.305.4.654.474 1.078.042.24.017.82-.045 1.062-.196.764-.65 1.364-1.3 1.714-.095.051-.18.093-.19.093-.009 0 .026-.087.077-.194.219-.454.244-.895.079-1.386-.102-.301-.308-.668-.724-1.289-.484-.72-.602-.913-.721-1.167zM3.25 12.374c.663-.556 1.486-.95 2.237-1.072a3.51 3.51 0 011.161.045c.48.122.91.396 1.133.721.218.319.312.596.41 1.214.038.243.08.488.092.543.073.32.216.576.392.704.28.204.764.217 1.239.033a.618.618 0 01.155-.048c.017.017-.222.176-.39.26a1.334 1.334 0 01-.648.156c-.435 0-.796-.22-1.098-.668a5.3 5.3 0 01-.296-.588c-.318-.721-.475-.94-.844-1.181-.322-.21-.737-.247-1.049-.095-.41.2-.524.72-.23 1.05a.911.911 0 00.512.266.545.545 0 00.619-.544c0-.217-.084-.34-.295-.436-.289-.129-.598.022-.597.291 0 .115.051.187.167.24.074.033.076.035.015.023-.264-.055-.326-.372-.114-.582.256-.252.784-.141.965.204.076.145.085.433.019.607-.15.39-.582.595-1.022.483-.3-.076-.421-.158-.782-.527-.627-.642-.87-.767-1.774-.907l-.174-.027.197-.165z" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M.308.884C2.402 3.41 3.845 4.452 4.005 4.672c.132.182.082.346-.144.474a1.381 1.381 0 01-.515.143c-.147 0-.198-.056-.198-.056-.085-.08-.133-.066-.57-.837A132.96 132.96 0 001.45 2.67c-.032-.03-.031-.03 1.067 1.923.177.407.035.556.035.614 0 .118-.033.18-.179.343-.244.27-.353.574-.432 1.203-.088.705-.336 1.203-1.024 2.056-.402.499-.468.59-.57.792-.128.253-.163.395-.177.714-.015.339.014.557.118.88.09.284.186.47.429.844.21.323.33.563.33.657 0 .074.014.074.34.001.776-.174 1.407-.48 1.762-.857.22-.233.271-.361.273-.68.001-.208-.006-.252-.063-.372-.092-.195-.26-.358-.63-.61-.486-.33-.694-.595-.75-.96-.048-.3.007-.511.275-1.07.278-.58.347-.827.394-1.41.03-.377.071-.526.18-.646.114-.124.216-.166.498-.204.459-.063.75-.18.99-.4a.853.853 0 00.31-.652l.01-.21-.117-.134C4.098 4.004.026.5 0 .5-.005.5.133.673.308.884zm.976 9.815a.37.37 0 00-.115-.489c-.15-.1-.385-.052-.385.077 0 .04.022.069.072.094.084.043.09.091.024.19-.067.099-.061.186.015.246.123.095.297.043.389-.118zM4.925 5.999c-.215.065-.424.292-.49.53-.039.145-.016.4.043.478.096.127.188.16.439.159.49-.003.916-.212.966-.474.04-.214-.147-.51-.405-.641a.965.965 0 00-.553-.052zm.574.445c.075-.107.042-.222-.087-.3-.244-.149-.615-.026-.615.204 0 .115.193.24.37.24.118 0 .28-.07.332-.144z"
/>
</svg>
)
}
export default SvgLogo

View File

@@ -1,17 +0,0 @@
import * as React from 'react'
function SvgSpinner(props: React.SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<mask id="spinner_svg__a">
<path fill="#fff" strokeWidth={0} d="M0 0h24v24H0z" />
<path fill="#000" strokeWidth={0} d="M0 0h12v12H0z" />
<circle cx={2} cy={12} r={1} fill="#fff" strokeWidth={0} />
<circle cx={12} cy={2} r={1} fill="#fff" strokeWidth={0} />
</mask>
<circle cx={12} cy={12} r={10} mask="url(#spinner_svg__a)" />
</svg>
)
}
export default SvgSpinner

View File

@@ -1,10 +1,10 @@
<svg width="23" height="20" viewBox="0 0 23 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gradient1" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(95)">
<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(#gradient1)" />
<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: 782 B

After

Width:  |  Height:  |  Size: 780 B

View File

@@ -0,0 +1,35 @@
<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>

After

Width:  |  Height:  |  Size: 931 B

View File

@@ -1,12 +1,11 @@
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask">
<rect width="24" height="24" fill="white" stroke-width="0" />
<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
id="circle"
cx="12"
cy="12"
r="10"

Before

Width:  |  Height:  |  Size: 608 B

After

Width:  |  Height:  |  Size: 592 B

View File

@@ -0,0 +1,5 @@
<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>

After

Width:  |  Height:  |  Size: 743 B

View File

@@ -1,6 +1,6 @@
import { AlertTriangle, Icon, LargeIcon } from 'lib/icons'
import styled, { Color, css, keyframes, ThemedText } from 'lib/theme'
import { ReactNode } from 'react'
import { ReactNode, useMemo } from 'react'
import Button from './Button'
import Row from './Row'
@@ -15,7 +15,7 @@ const StyledButton = styled(Button)`
}
`
const UpdateRow = styled(Row)``
const ActionRow = styled(Row)``
const grow = keyframes`
from {
@@ -28,12 +28,12 @@ const grow = keyframes`
}
`
const updateCss = css`
const actionCss = css`
border: 1px solid ${({ theme }) => theme.outline};
padding: calc(0.25em - 1px);
padding-left: calc(0.75em - 1px);
${UpdateRow} {
${ActionRow} {
animation: ${grow} 0.25s ease-in;
white-space: nowrap;
}
@@ -45,44 +45,43 @@ const updateCss = css`
}
`
export const Overlay = styled(Row)<{ update?: boolean }>`
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;
${({ update }) => update && updateCss}
${({ hasAction }) => hasAction && actionCss}
`
export interface ActionButtonProps {
color?: Color
disabled?: boolean
update?: { message: ReactNode; action: ReactNode; icon?: Icon }
export interface Action {
message: ReactNode
icon?: Icon
onClick: () => void
onUpdate?: () => void
children: ReactNode
}
export default function ActionButton({
color = 'accent',
disabled,
update,
onClick,
onUpdate,
children,
}: ActionButtonProps) {
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 update={Boolean(update)} flex align="stretch">
<StyledButton color={color} disabled={disabled} onClick={update ? onUpdate : onClick}>
<ThemedText.TransitionButton buttonSize={update ? 'medium' : 'large'} color="currentColor">
{update ? update.action : children}
<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>
{update && (
<UpdateRow gap={0.5}>
<LargeIcon color="currentColor" icon={update.icon || AlertTriangle} />
<ThemedText.Subhead2>{update?.message}</ThemedText.Subhead2>
</UpdateRow>
{action && (
<ActionRow gap={0.5}>
<LargeIcon color="currentColor" icon={action.icon || AlertTriangle} />
<ThemedText.Subhead2>{action?.message}</ThemedText.Subhead2>
</ActionRow>
)}
</Overlay>
)

View File

@@ -1,7 +1,8 @@
import { Trans } from '@lingui/macro'
import Row from 'lib/components/Row'
import { Logo } from 'lib/icons'
import styled, { ThemedText } from 'lib/theme'
import styled, { brand, ThemedText } from 'lib/theme'
import { memo } from 'react'
import ExternalLink from './ExternalLink'
@@ -13,26 +14,28 @@ const UniswapA = styled(ExternalLink)`
${Logo} {
fill: ${({ theme }) => theme.secondary};
height: 1em;
transition: transform 0.25s ease;
transition: transform 0.25s ease, fill 0.25s ease;
width: 1em;
will-change: transform;
}
:hover {
fill: ${({ theme }) => theme.onHover(theme.secondary)};
transform: rotate(-5deg);
}
:hover ${Logo} {
fill: ${brand};
transform: rotate(-5deg);
}
`
export default function BrandedFooter() {
export default memo(function BrandedFooter() {
return (
<UniswapA href={`https://app.uniswap.org/`}>
<Row gap={0.4} justify="center">
<Logo />
<ThemedText.Caption color="secondary">
<Trans>Powered by the Uniswap protocol</Trans>
</ThemedText.Caption>
</Row>
</UniswapA>
<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,6 +1,6 @@
import { Icon } from 'lib/icons'
import styled, { Color } from 'lib/theme'
import { ComponentProps } from 'react'
import { ComponentProps, forwardRef } from 'react'
export const BaseButton = styled.button`
background-color: transparent;
@@ -10,6 +10,7 @@ export const BaseButton = styled.button`
cursor: pointer;
font-size: inherit;
font-weight: inherit;
height: inherit;
line-height: inherit;
margin: 0;
padding: 0;
@@ -55,10 +56,12 @@ interface IconButtonProps {
iconProps?: ComponentProps<Icon>
}
export function IconButton({ icon: Icon, iconProps, ...props }: IconButtonProps & ComponentProps<typeof BaseButton>) {
return (
<SecondaryButton {...props}>
<Icon {...iconProps} />
</SecondaryButton>
)
}
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

@@ -40,7 +40,10 @@ export function Provider({ value, children }: ProviderProps) {
}
}, [active])
return (
<div ref={ref}>
<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>
)
@@ -73,13 +76,12 @@ export const Modal = styled.div<{ color: Color }>`
border-radius: ${({ theme }) => theme.borderRadius * 0.75}em;
display: flex;
flex-direction: column;
height: calc(100% - 0.5em);
height: 100%;
left: 0;
margin: 0.25em;
overflow: hidden;
position: absolute;
top: 0;
width: calc(100% - 0.5em);
width: 100%;
z-index: ${Layer.DIALOG};
`
@@ -106,7 +108,7 @@ export default function Dialog({ color, children, onClose = () => void 0 }: Dial
context.element &&
createPortal(
<ThemeProvider>
<Modal className="dialog" color={color} ref={dialog}>
<Modal color={color} ref={dialog}>
<OnCloseContext.Provider value={onClose}>{children}</OnCloseContext.Provider>
</Modal>
</ThemeProvider>,

View File

@@ -36,7 +36,7 @@ export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, E
error={this.state.error}
header={<Trans>Something went wrong.</Trans>}
action={<Trans>Reload the page</Trans>}
onAction={() => window.location.reload()}
onClick={() => window.location.reload()}
/>
</Dialog>
)

View File

@@ -1,17 +1,14 @@
import { Trans } from '@lingui/macro'
import useScrollbar from 'lib/hooks/useScrollbar'
import { AlertTriangle, Expando, Icon, Info, LargeIcon } from 'lib/icons'
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, useState } from 'react'
import ActionButton from '../ActionButton'
import { IconButton } from '../Button'
import Column from '../Column'
import Row from '../Row'
import Rule from '../Rule'
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;
@@ -34,7 +31,6 @@ export function StatusHeader({ icon: Icon, iconColor, iconSize = 4, children }:
{children}
</Column>
</Column>
<Rule />
</>
)
}
@@ -48,52 +44,18 @@ const ErrorHeader = styled(Column)<{ open: boolean }>`
transition: max-height 0.25s;
}
`
const ErrorColumn = styled(Column)``
const ExpandoColumn = styled(Column)<{ open: boolean }>`
flex-grow: ${({ open }) => (open ? 2 : 0)};
transition: flex-grow 0.25s, gap 0.25s;
${Rule} {
margin-bottom: ${({ open }) => (open ? 0 : 0.75)}em;
transition: margin-bottom 0.25s;
}
${ErrorColumn} {
flex-basis: 0;
flex-grow: ${({ open }) => (open ? 1 : 0)};
overflow-y: hidden;
position: relative;
transition: flex-grow 0.25s;
${Column} {
height: 100%;
padding: ${({ open }) => (open ? '0.5em 0' : 0)};
transition: 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);
}
}
}
`
interface ErrorDialogProps {
header?: ReactNode
error: Error
action: ReactNode
onAction: () => void
onClick: () => void
}
export default function ErrorDialog({ header, error, action, onAction }: ErrorDialogProps) {
export default function ErrorDialog({ header, error, action, onClick }: ErrorDialogProps) {
const [open, setOpen] = useState(false)
const [details, setDetails] = useState<HTMLDivElement | null>(null)
const scrollbar = useScrollbar(details)
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}>
@@ -104,27 +66,15 @@ export default function ErrorDialog({ header, error, action, onAction }: ErrorDi
<ThemedText.Body2>{header}</ThemedText.Body2>
</ErrorHeader>
</StatusHeader>
<Row>
<Row gap={0.5}>
<Info color="secondary" />
<ThemedText.Subhead2 color="secondary">
<Trans>Error details</Trans>
</ThemedText.Subhead2>
</Row>
<IconButton color="secondary" onClick={() => setOpen(!open)} icon={Expando} iconProps={{ open }} />
</Row>
<ExpandoColumn flex align="stretch" open={open}>
<Rule />
<ErrorColumn>
<Column gap={0.5} ref={setDetails} css={scrollbar}>
<ThemedText.Code>
{error.name}
{error.message ? `: ${error.message}` : ''}
</ThemedText.Code>
</Column>
</ErrorColumn>
<ActionButton onClick={onAction}>{action}</ActionButton>
</ExpandoColumn>
<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,3 +1,4 @@
import { SUPPORTED_LOCALES } from 'constants/locales'
import { WidgetProps } from 'lib/components/Widget'
import { IntegrationError } from 'lib/errors'
import { PropsWithChildren, useEffect } from 'react'
@@ -18,5 +19,12 @@ export default function WidgetsPropsValidator(props: PropsWithChildren<WidgetPro
}
}, [width])
const { locale } = props
useEffect(() => {
if (locale && locale !== 'pseudo' && !SUPPORTED_LOCALES.includes(locale)) {
console.warn('Unsupported locale: ', locale)
}
}, [locale])
return <>{props.children}</>
}

View File

@@ -0,0 +1,36 @@
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

@@ -0,0 +1,66 @@
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

@@ -8,7 +8,7 @@ export default function ExternalLink({
href,
rel = 'noopener noreferrer',
...rest
}: Omit<HTMLProps<HTMLAnchorElement>, 'as' | 'ref' | 'onClick'> & { href: string }) {
}: Omit<HTMLProps<HTMLAnchorElement>, 'as' | 'ref' | 'onClick'> & { href?: string }) {
return (
<a target={target} rel={rel} href={href} {...rest}>
{rest.children}

View File

@@ -1,4 +1,3 @@
import JSBI from 'jsbi'
import styled, { css } from 'lib/theme'
import { forwardRef, HTMLProps, useCallback, useEffect, useState } from 'react'
@@ -78,12 +77,16 @@ interface EnforcedNumericInputProps extends NumericInputProps {
}
function isNumericallyEqual(a: string, b: string) {
const [aInteger, aDecimal] = a.split('.')
const [bInteger, bDecimal] = b.split('.')
return (
JSBI.equal(JSBI.BigInt(aInteger ?? 0), JSBI.BigInt(bInteger ?? 0)) &&
JSBI.equal(JSBI.BigInt(aDecimal ?? 0), JSBI.BigInt(bDecimal ?? 0))
)
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(

View File

@@ -11,16 +11,19 @@ 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: 8px;
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`
@@ -32,7 +35,6 @@ const Arrow = styled.div`
background: ${({ theme }) => theme.dialog};
border: 1px solid ${({ theme }) => theme.outline};
content: '';
height: 8px;
position: absolute;
transform: rotate(45deg);
@@ -40,32 +42,36 @@ const Arrow = styled.div`
}
&.arrow-top {
bottom: -5px;
bottom: -4px;
::before {
border-radius: 1px;
border-left: none;
border-top: none;
}
}
&.arrow-bottom {
top: -5px;
top: -5px; // includes -1px from border
::before {
border-bottom: none;
border-right: none;
border-radius: 1px;
}
}
&.arrow-left {
right: -5px;
right: -4px;
::before {
border-bottom: none;
border-left: none;
border-radius: 1px;
}
}
&.arrow-right {
left: -5px;
left: -5px; // includes -1px from border
::before {
border-radius: 1px;
border-right: none;
border-top: none;
}
@@ -77,10 +83,11 @@ export interface PopoverProps {
show: boolean
children: React.ReactNode
placement: Placement
offset?: number
contained?: true
}
export default function Popover({ content, show, children, placement, contained }: PopoverProps) {
export default function Popover({ content, show, children, placement, offset, contained }: PopoverProps) {
const boundary = useContext(BoundaryContext)
const reference = useRef<HTMLDivElement>(null)
@@ -90,8 +97,8 @@ export default function Popover({ content, show, children, placement, contained
const options = useMemo((): Options => {
const modifiers: Options['modifiers'] = [
{ name: 'offset', options: { offset: [5, 5] } },
{ name: 'arrow', options: { element: arrow, padding: 6 } },
{ name: 'offset', options: { offset: [4, offset || 4] } },
{ name: 'arrow', options: { element: arrow, padding: 4 } },
]
if (contained) {
modifiers.push(
@@ -118,7 +125,7 @@ export default function Popover({ content, show, children, placement, contained
strategy: 'absolute',
modifiers,
}
}, [arrow, boundary, placement, contained])
}, [offset, arrow, contained, placement, boundary])
const { styles, attributes } = usePopper(reference.current, popover, options)

View File

@@ -6,6 +6,10 @@ const Rule = styled.hr<{ padded?: true; scrollingEdge?: 'top' | 'bottom' }>`
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

View File

@@ -1,17 +1,36 @@
import { Trans } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { useUSDCValue } from 'hooks/useUSDCPrice'
import { useSwapAmount, useSwapCurrency, useSwapInfo } from 'lib/hooks/swap'
import { loadingTransitionCss } from 'lib/css/loading'
import {
useIsSwapFieldIndependent,
useSwapAmount,
useSwapCurrency,
useSwapCurrencyAmount,
useSwapInfo,
} from 'lib/hooks/swap'
import { usePrefetchCurrencyColor } from 'lib/hooks/useCurrencyColor'
import { Field } from 'lib/state/swap'
import styled, { ThemedText } from 'lib/theme'
import { useCallback } from 'react'
import { useMemo } from 'react'
import { TradeState } from 'state/routing/types'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import { maxAmountSpend } from 'utils/maxAmountSpend'
import Column from '../Column'
import Row from '../Row'
import TokenImg from '../TokenImg'
import TokenInput from './TokenInput'
export const USDC = styled(Row)`
${loadingTransitionCss};
`
export const Balance = styled(ThemedText.Body2)<{ focused: boolean }>`
opacity: ${({ focused }) => (focused ? 1 : 0)};
transition: opacity 0.25s ${({ focused }) => (focused ? 'ease-in' : 'ease-out')};
`
const InputColumn = styled(Column)<{ approved?: boolean }>`
margin: 0.75em;
position: relative;
@@ -22,54 +41,94 @@ const InputColumn = styled(Column)<{ approved?: boolean }>`
}
`
interface InputProps {
disabled?: boolean
export interface InputProps {
disabled: boolean
focused: boolean
}
export default function Input({ disabled }: InputProps) {
interface UseFormattedFieldAmountArguments {
disabled: boolean
currencyAmount?: CurrencyAmount<Currency>
fieldAmount?: string
}
export function useFormattedFieldAmount({ disabled, currencyAmount, fieldAmount }: UseFormattedFieldAmountArguments) {
return useMemo(() => {
if (disabled) {
return ''
}
if (fieldAmount !== undefined) {
return fieldAmount
}
if (currencyAmount) {
return currencyAmount.toSignificant(6)
}
return ''
}, [disabled, currencyAmount, fieldAmount])
}
export default function Input({ disabled, focused }: InputProps) {
const { i18n } = useLingui()
const {
currencyBalances: { [Field.INPUT]: balance },
currencyAmounts: { [Field.INPUT]: inputCurrencyAmount },
trade: { state: tradeState },
tradeCurrencyAmounts: { [Field.INPUT]: swapInputCurrencyAmount },
} = useSwapInfo()
const inputUSDC = useUSDCValue(inputCurrencyAmount)
const inputUSDC = useUSDCValue(swapInputCurrencyAmount)
const [swapInputAmount, updateSwapInputAmount] = useSwapAmount(Field.INPUT)
const [swapInputCurrency, updateSwapInputCurrency] = useSwapCurrency(Field.INPUT)
const inputCurrencyAmount = useSwapCurrencyAmount(Field.INPUT)
// extract eagerly in case of reversal
usePrefetchCurrencyColor(swapInputCurrency)
const isRouteLoading = tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
const isDependentField = !useIsSwapFieldIndependent(Field.INPUT)
const isLoading = isRouteLoading && isDependentField
//TODO(ianlapham): mimic logic from app swap page
const mockApproved = true
const onMax = useCallback(() => {
if (balance) {
updateSwapInputAmount(balance.toExact())
}
}, [balance, updateSwapInputAmount])
// account for gas needed if using max on native token
const max = useMemo(() => {
const maxAmount = maxAmountSpend(balance)
return maxAmount?.greaterThan(0) ? maxAmount.toExact() : undefined
}, [balance])
const balanceColor = useMemo(() => {
const insufficientBalance =
balance &&
(inputCurrencyAmount ? inputCurrencyAmount.greaterThan(balance) : swapInputCurrencyAmount?.greaterThan(balance))
return insufficientBalance ? 'error' : undefined
}, [balance, inputCurrencyAmount, swapInputCurrencyAmount])
const amount = useFormattedFieldAmount({
disabled,
currencyAmount: swapInputCurrencyAmount,
fieldAmount: swapInputAmount,
})
return (
<InputColumn gap={0.5} approved={mockApproved}>
<Row>
<ThemedText.Subhead2 color="secondary">
<Trans>Trading</Trans>
</ThemedText.Subhead2>
</Row>
<TokenInput
currency={swapInputCurrency}
amount={(swapInputAmount !== undefined ? swapInputAmount : inputCurrencyAmount?.toSignificant(6)) ?? ''}
amount={amount}
max={max}
disabled={disabled}
onMax={onMax}
onChangeInput={updateSwapInputAmount}
onChangeCurrency={updateSwapInputCurrency}
loading={isLoading}
>
<ThemedText.Body2 color="secondary">
<ThemedText.Body2 color="secondary" userSelect>
<Row>
{inputUSDC ? `$${inputUSDC.toFixed(2)}` : '-'}
<USDC isLoading={isRouteLoading}>
{inputUSDC ? `$${formatCurrencyAmount(inputUSDC, 6, 'en', 2)}` : '-'}
</USDC>
{balance && (
<ThemedText.Body2 color={inputCurrencyAmount?.greaterThan(balance) ? 'error' : undefined}>
Balance: <span style={{ userSelect: 'text' }}>{formatCurrencyAmount(balance, 4)}</span>
</ThemedText.Body2>
<Balance color={balanceColor} focused={focused}>
Balance: <span>{formatCurrencyAmount(balance, 4, i18n.locale)}</span>
</Balance>
)}
</Row>
</ThemedText.Body2>

View File

@@ -1,18 +1,20 @@
import { Trans } from '@lingui/macro'
import { useUSDCValue } from 'hooks/useUSDCPrice'
import { useLingui } from '@lingui/react'
import { atom } from 'jotai'
import { useAtomValue } from 'jotai/utils'
import BrandedFooter from 'lib/components/BrandedFooter'
import { useSwapAmount, useSwapCurrency, useSwapInfo } from 'lib/hooks/swap'
import { useIsSwapFieldIndependent, useSwapAmount, useSwapCurrency, useSwapInfo } from 'lib/hooks/swap'
import useCurrencyColor from 'lib/hooks/useCurrencyColor'
import useUSDCPriceImpact from 'lib/hooks/useUSDCPriceImpact'
import { Field } from 'lib/state/swap'
import styled, { DynamicThemeProvider, ThemedText } from 'lib/theme'
import { ReactNode, useCallback, useMemo } from 'react'
import { computeFiatValuePriceImpact } from 'utils/computeFiatValuePriceImpact'
import { PropsWithChildren } from 'react'
import { TradeState } from 'state/routing/types'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import Column from '../Column'
import Row from '../Row'
import { Balance, InputProps, USDC, useFormattedFieldAmount } from './Input'
import TokenInput from './TokenInput'
export const colorAtom = atom<string | undefined>(undefined)
@@ -21,31 +23,34 @@ const OutputColumn = styled(Column)<{ hasColor: boolean | null }>`
background-color: ${({ theme }) => theme.module};
border-radius: ${({ theme }) => theme.borderRadius - 0.25}em;
padding: 0.75em;
padding-bottom: 0.5em;
position: relative;
// Set transitions to reduce color flashes when switching color/token.
// When color loads, transition the background so that it transitions from the empty or last state, but not _to_ the empty state.
transition: ${({ hasColor }) => (hasColor ? 'background-color 0.25s ease-out' : undefined)};
* {
> {
// When color is loading, delay the color/stroke so that it seems to transition from the last state.
transition: ${({ hasColor }) => (hasColor === null ? 'color 0.25s ease-in, stroke 0.25s ease-in' : undefined)};
}
`
interface OutputProps {
disabled?: boolean
children: ReactNode
}
export default function Output({ disabled, focused, children }: PropsWithChildren<InputProps>) {
const { i18n } = useLingui()
export default function Output({ disabled, children }: OutputProps) {
const {
currencyBalances: { [Field.OUTPUT]: balance },
currencyAmounts: { [Field.INPUT]: inputCurrencyAmount, [Field.OUTPUT]: outputCurrencyAmount },
trade: { state: tradeState },
tradeCurrencyAmounts: { [Field.INPUT]: inputCurrencyAmount, [Field.OUTPUT]: outputCurrencyAmount },
} = useSwapInfo()
const [swapOutputAmount, updateSwapOutputAmount] = useSwapAmount(Field.OUTPUT)
const [swapOutputCurrency, updateSwapOutputCurrency] = useSwapCurrency(Field.OUTPUT)
const isRouteLoading = tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
const isDependentField = !useIsSwapFieldIndependent(Field.OUTPUT)
const isLoading = isRouteLoading && isDependentField
const overrideColor = useAtomValue(colorAtom)
const dynamicColor = useCurrencyColor(swapOutputCurrency)
const color = overrideColor || dynamicColor
@@ -53,50 +58,44 @@ export default function Output({ disabled, children }: OutputProps) {
// different state true/null/false allow smoother color transition
const hasColor = swapOutputCurrency ? Boolean(color) || null : false
const inputUSDC = useUSDCValue(inputCurrencyAmount)
const outputUSDC = useUSDCValue(outputCurrencyAmount)
const {
outputUSDC,
priceImpact,
warning: priceImpactWarning,
} = useUSDCPriceImpact(inputCurrencyAmount, outputCurrencyAmount)
const priceImpact = useMemo(() => {
const computedChange = computeFiatValuePriceImpact(inputUSDC, outputUSDC)
return computedChange ? parseFloat(computedChange.multiply(-1)?.toSignificant(3)) : undefined
}, [inputUSDC, outputUSDC])
const usdc = useMemo(() => {
if (outputUSDC) {
return `$${outputUSDC.toFixed(2)} (${priceImpact && priceImpact > 0 ? '+' : ''}${priceImpact}%)`
}
return ''
}, [priceImpact, outputUSDC])
const onMax = useCallback(() => {
if (balance) {
updateSwapOutputAmount(balance.toExact())
}
}, [balance, updateSwapOutputAmount])
const amount = useFormattedFieldAmount({
disabled,
currencyAmount: outputCurrencyAmount,
fieldAmount: swapOutputAmount,
})
return (
<DynamicThemeProvider color={color}>
<OutputColumn hasColor={hasColor} gap={0.5}>
<Row>
<ThemedText.Subhead2 color="secondary">
<ThemedText.Subhead1 color="secondary">
<Trans>For</Trans>
</ThemedText.Subhead2>
</ThemedText.Subhead1>
</Row>
<TokenInput
currency={swapOutputCurrency}
amount={(swapOutputAmount !== undefined ? swapOutputAmount : outputCurrencyAmount?.toSignificant(6)) ?? ''}
amount={amount}
disabled={disabled}
onMax={onMax}
onChangeInput={updateSwapOutputAmount}
onChangeCurrency={updateSwapOutputCurrency}
loading={isLoading}
>
<ThemedText.Body2 color="secondary">
<ThemedText.Body2 color="secondary" userSelect>
<Row>
{usdc}
<USDC gap={0.5} isLoading={isRouteLoading}>
{outputUSDC ? `$${formatCurrencyAmount(outputUSDC, 6, 'en', 2)}` : '-'}{' '}
{priceImpact && <ThemedText.Body2 color={priceImpactWarning}>({priceImpact})</ThemedText.Body2>}
</USDC>
{balance && (
<span>
Balance: <span style={{ userSelect: 'text' }}>{formatCurrencyAmount(balance, 4)}</span>
</span>
<Balance focused={focused}>
Balance: <span>{formatCurrencyAmount(balance, 4, i18n.locale)}</span>
</Balance>
)}
</Row>
</ThemedText.Body2>

View File

@@ -0,0 +1,54 @@
import { useLingui } from '@lingui/react'
import { Trade } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
import Row from 'lib/components/Row'
import { ThemedText } from 'lib/theme'
import formatLocaleNumber from 'lib/utils/formatLocaleNumber'
import { useCallback, useMemo, useState } from 'react'
import { formatCurrencyAmount, formatPrice } from 'utils/formatCurrencyAmount'
import { TextButton } from '../Button'
interface PriceProps {
trade: Trade<Currency, Currency, TradeType>
outputUSDC?: CurrencyAmount<Token>
}
/** Displays the price of a trade. If outputUSDC is included, also displays the unit price. */
export default function Price({ trade, outputUSDC }: PriceProps) {
const { i18n } = useLingui()
const { inputAmount, outputAmount, executionPrice } = trade
const [base, setBase] = useState<'input' | 'output'>('input')
const onClick = useCallback(() => setBase((base) => (base === 'input' ? 'output' : 'input')), [])
// Compute the usdc price from the output price, so that it aligns with the displayed price.
const { price, usdcPrice } = useMemo(() => {
switch (base) {
case 'input':
return {
price: executionPrice,
usdcPrice: outputUSDC?.multiply(inputAmount.decimalScale).divide(inputAmount),
}
case 'output':
return {
price: executionPrice.invert(),
usdcPrice: outputUSDC?.multiply(outputAmount.decimalScale).divide(outputAmount),
}
}
}, [base, executionPrice, inputAmount, outputAmount, outputUSDC])
return (
<TextButton color="primary" onClick={onClick}>
<ThemedText.Caption>
<Row gap={0.25}>
{formatLocaleNumber({ number: 1, sigFigs: 1, locale: i18n.locale })} {price.baseCurrency.symbol} ={' '}
{formatPrice(price, 6, i18n.locale)} {price.quoteCurrency.symbol}
{usdcPrice && (
<ThemedText.Caption color="secondary">(${formatCurrencyAmount(usdcPrice, 6, 'en', 2)})</ThemedText.Caption>
)}
</Row>
</ThemedText.Caption>
</TextButton>
)
}

View File

@@ -9,8 +9,7 @@ import Row from '../Row'
const ReverseRow = styled(Row)`
left: 50%;
position: absolute;
top: 7.45em;
transform: translateX(-50%);
transform: translate(-50%, -50%);
z-index: ${Layer.OVERLAY};
`

View File

@@ -0,0 +1,139 @@
import { Plural, Trans } from '@lingui/macro'
import { Currency, TradeType } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk'
import { ReactComponent as DotLine } from 'assets/svg/dot_line.svg'
import Column from 'lib/components/Column'
import Row from 'lib/components/Row'
import Rule from 'lib/components/Rule'
import TokenImg from 'lib/components/TokenImg'
import { AutoRouter } from 'lib/icons'
import styled, { Layer, ThemedText } from 'lib/theme'
import { useMemo } from 'react'
import { InterfaceTrade } from 'state/routing/types'
import { getTokenPath, RoutingDiagramEntry } from './utils'
const StyledAutoRouterLabel = styled(ThemedText.ButtonSmall)`
@supports (-webkit-background-clip: text) and (-webkit-text-fill-color: transparent) {
background-image: linear-gradient(90deg, #2172e5 0%, #54e521 163.16%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
`
function Header({ routes }: { routes: RoutingDiagramEntry[] }) {
return (
<Row justify="space-between" gap={1}>
<ThemedText.Subhead2>
<Row gap={0.25}>
<AutoRouter />
<StyledAutoRouterLabel color="primary" lineHeight={'16px'}>
<Trans>Auto Router</Trans>
</StyledAutoRouterLabel>
</Row>
</ThemedText.Subhead2>
<ThemedText.Body2>
<Plural value={routes.length} _1="Best route via 1 hop" other="Best route via # hops" />
</ThemedText.Body2>
</Row>
)
}
const Dots = styled(DotLine)`
color: ${({ theme }) => theme.outline};
position: absolute;
z-index: ${Layer.UNDERLAYER};
`
const RouteRow = styled(Row)`
flex-wrap: nowrap;
`
const RouteNode = styled(Row)`
background-color: ${({ theme }) => theme.interactive};
border-radius: ${({ theme }) => `${(theme.borderRadius ?? 1) * 0.5}em`};
margin-left: 1.625em;
padding: 0.25em 0.375em;
width: max-content;
`
const RouteBadge = styled.div`
background-color: ${({ theme }) => theme.module};
border-radius: ${({ theme }) => `${(theme.borderRadius ?? 1) * 0.25}em`};
padding: 0.125em;
`
function RouteDetail({ route }: { route: RoutingDiagramEntry }) {
const protocol = route.protocol.toUpperCase()
return (
<RouteNode>
<Row gap={0.375}>
<ThemedText.Caption>{route.percent.toSignificant(2)}%</ThemedText.Caption>
<RouteBadge>
<ThemedText.Badge color="secondary">{protocol}</ThemedText.Badge>
</RouteBadge>
</Row>
</RouteNode>
)
}
const RoutePool = styled(RouteNode)`
margin: 0 0.75em;
`
function Pool({
originCurrency,
targetCurrency,
feeAmount,
}: {
originCurrency: Currency
targetCurrency: Currency
feeAmount: FeeAmount
}) {
return (
<RoutePool>
<ThemedText.Caption>
<Row gap={0.25}>
<TokenImg token={originCurrency} />
<TokenImg token={targetCurrency} style={{ marginLeft: '-0.65em' }} />
{feeAmount / 10_000}%
</Row>
</ThemedText.Caption>
</RoutePool>
)
}
function Route({ route }: { route: RoutingDiagramEntry }) {
const [originCurrency] = route.path[0]
const [, targetCurrency] = route.path[route.path.length - 1]
return (
<Row align="center" style={{ gridTemplateColumns: '1em 1fr 1em' }}>
<TokenImg token={originCurrency} />
<RouteRow flex style={{ position: 'relative' }}>
<Dots />
<RouteDetail route={route} />
<RouteRow justify="space-evenly" flex>
{route.path.map(([originCurrency, targetCurrency, feeAmount], index) => (
<Pool key={index} originCurrency={originCurrency} targetCurrency={targetCurrency} feeAmount={feeAmount} />
))}
</RouteRow>
</RouteRow>
<TokenImg token={targetCurrency} />
</Row>
)
}
export default function RoutingDiagram({ trade }: { trade: InterfaceTrade<Currency, Currency, TradeType> }) {
const routes: RoutingDiagramEntry[] = useMemo(() => getTokenPath(trade), [trade])
return (
<Column gap={0.75}>
<Header routes={routes} />
<Rule />
{routes.map((route, index) => (
<Route key={index} route={route} />
))}
</Column>
)
}

View File

@@ -0,0 +1,43 @@
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 { InterfaceTrade } from 'state/routing/types'
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

@@ -1,10 +1,12 @@
import { t, Trans } from '@lingui/macro'
import { Percent } from '@uniswap/sdk-core'
import { Trans } from '@lingui/macro'
import { useAtom } from 'jotai'
import { Check, LargeIcon } from 'lib/icons'
import { maxSlippageAtom } from 'lib/state/settings'
import Popover from 'lib/components/Popover'
import { useTooltip } from 'lib/components/Tooltip'
import { getSlippageWarning, toPercent } from 'lib/hooks/useSlippage'
import { AlertTriangle, Check, Icon, LargeIcon, XOctagon } from 'lib/icons'
import { autoSlippageAtom, maxSlippageAtom } from 'lib/state/settings'
import styled, { ThemedText } from 'lib/theme'
import { PropsWithChildren, useCallback, useRef, useState } from 'react'
import { forwardRef, memo, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { BaseButton, TextButton } from '../../Button'
import Column from '../../Column'
@@ -15,6 +17,10 @@ import { Label, optionCss } from './components'
const tooltip = (
<Trans>Your transaction will revert if the price changes unfavorably by more than this percentage.</Trans>
)
const highSlippage = <Trans>High slippage increases the risk of price movement</Trans>
const invalidSlippage = <Trans>Please enter a valid slippage %</Trans>
const placeholder = '0.10'
const Button = styled(TextButton)<{ selected: boolean }>`
${({ selected }) => optionCss(selected)}
@@ -23,7 +29,6 @@ const Button = styled(TextButton)<{ selected: boolean }>`
const Custom = styled(BaseButton)<{ selected: boolean }>`
${({ selected }) => optionCss(selected)}
${inputCss}
border-color: ${({ selected, theme }) => (selected ? theme.active : 'transparent')} !important;
padding: calc(0.75em - 3px) 0.625em;
`
@@ -31,55 +36,113 @@ interface OptionProps {
wrapper: typeof Button | typeof Custom
selected: boolean
onSelect: () => void
icon?: ReactNode
tabIndex?: number
children: ReactNode
}
function Option({ wrapper: Wrapper, children, selected, onSelect }: PropsWithChildren<OptionProps>) {
const Option = forwardRef<HTMLButtonElement, OptionProps>(function Option(
{ wrapper: Wrapper, children, selected, onSelect, icon, tabIndex }: OptionProps,
ref
) {
return (
<Wrapper selected={selected} onClick={onSelect}>
<Wrapper selected={selected} onClick={onSelect} ref={ref} tabIndex={tabIndex}>
<Row gap={0.5}>
{children}
<span style={{ width: '1.2em' }}>{selected && <LargeIcon icon={Check} />}</span>
{icon ? icon : <LargeIcon icon={selected ? Check : undefined} size={1.25} />}
</Row>
</Wrapper>
)
}
})
const Warning = memo(function Warning({ state, showTooltip }: { state?: 'warning' | 'error'; showTooltip: boolean }) {
let icon: Icon | undefined
let content: ReactNode
let show = showTooltip
switch (state) {
case 'error':
icon = XOctagon
content = invalidSlippage
show = true
break
case 'warning':
icon = AlertTriangle
content = highSlippage
break
}
return (
<Popover
key={state}
content={<ThemedText.Caption>{content}</ThemedText.Caption>}
show={show}
placement="top"
offset={16}
contained
>
<LargeIcon icon={icon} color={state} size={1.25} />
</Popover>
)
})
export default function MaxSlippageSelect() {
const [autoSlippage, setAutoSlippage] = useAtom(autoSlippageAtom)
const [maxSlippage, setMaxSlippage] = useAtom(maxSlippageAtom)
const maxSlippageInput = useMemo(() => maxSlippage?.toString() || '', [maxSlippage])
const option = useRef<HTMLButtonElement>(null)
const showTooltip = useTooltip(option.current)
const [custom, setCustom] = useState('')
const input = useRef<HTMLInputElement>(null)
const focus = useCallback(() => input.current?.focus(), [input])
const onInputChange = useCallback(
(custom: string) => {
setCustom(custom)
const numerator = Math.floor(+custom * 100)
if (numerator) {
setMaxSlippage(new Percent(numerator, 10_000))
} else {
setMaxSlippage('auto')
}
},
[setMaxSlippage]
)
const [warning, setWarning] = useState<'warning' | 'error' | undefined>(getSlippageWarning(toPercent(maxSlippage)))
useEffect(() => {
setWarning(getSlippageWarning(toPercent(maxSlippage)))
}, [maxSlippage])
const onInputSelect = useCallback(() => {
focus()
onInputChange(custom)
}, [custom, focus, onInputChange])
const percent = toPercent(maxSlippage)
const warning = getSlippageWarning(percent)
setAutoSlippage(!percent || warning === 'error')
}, [focus, maxSlippage, setAutoSlippage])
const processValue = useCallback(
(value: number | undefined) => {
const percent = toPercent(value)
const warning = getSlippageWarning(percent)
setMaxSlippage(value)
setAutoSlippage(!percent || warning === 'error')
},
[setAutoSlippage, setMaxSlippage]
)
return (
<Column gap={0.75}>
<Label name={<Trans>Max slippage</Trans>} tooltip={tooltip} />
<Row gap={0.5} grow="last">
<Option wrapper={Button} selected={maxSlippage === 'auto'} onSelect={() => setMaxSlippage('auto')}>
<Option wrapper={Button} selected={autoSlippage} onSelect={() => setAutoSlippage(true)}>
<ThemedText.ButtonMedium>
<Trans>Auto</Trans>
</ThemedText.ButtonMedium>
</Option>
<Option wrapper={Custom} onSelect={onInputSelect} selected={maxSlippage !== 'auto'}>
<Row>
<DecimalInput value={custom} onChange={onInputChange} placeholder={t`Custom`} ref={input} />%
<Option
wrapper={Custom}
selected={!autoSlippage}
onSelect={onInputSelect}
icon={warning && <Warning state={warning} showTooltip={showTooltip} />}
ref={option}
tabIndex={-1}
>
<Row color={warning === 'error' ? 'error' : undefined}>
<DecimalInput
size={Math.max(maxSlippageInput.length, 4)}
value={maxSlippageInput}
onChange={(input) => processValue(+input)}
placeholder={placeholder}
ref={input}
/>
%
</Row>
</Option>
</Row>

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