Compare commits

...

431 Commits

Author SHA1 Message Date
Ian Lapham
9170af888e handle dismiss (#1328) 2021-03-11 18:08:41 -05:00
Ian Lapham
b258f557d1 improvement(lists): add BA SEC tokens to unsupported list (#1327)
* show hidden search results by default

* update break styles

* optimize filter, use debounce on input

* increase debounce time

* add ba association list
2021-02-25 14:08:17 -05:00
Hyperion
9d8c7f8e12 fix: modals stealing focus across frames (#1326) 2021-02-24 14:59:04 -06:00
Jordan Frankfurt
9c44e61e23 unregisters all installed service workers (#1322) 2021-02-18 13:34:31 -05:00
Jordan Frankfurt
71db11b6ac Revert "Revert "feature(service worker): add offline support (#1319)" (#1320)" (#1321)
This reverts commit db3328c8d9.
2021-02-18 13:10:53 -05:00
Jordan Frankfurt
db3328c8d9 Revert "feature(service worker): add offline support (#1319)" (#1320)
This reverts commit 34dfb41a1e.
2021-02-18 13:03:32 -05:00
Jordan Frankfurt
34dfb41a1e feature(service worker): add offline support (#1319) 2021-02-17 14:43:49 -05:00
Moody Salem
e77fcd21dc Update index.ts 2021-02-13 20:03:05 -06:00
Jordan Frankfurt
0b7846ee1d fix(discord): correct links (#1315) 2021-02-10 10:56:42 -05:00
Ian Lapham
f450d34d69 feat(transactions): enable button to add tokens to metamask (#1311)
* start on adding button for watching tokens

* add tokens to metamask

* add confirmation view

* reset modal view
2021-02-09 18:55:38 -05:00
Ian Lapham
76ab349b9e show hidden search results by default (#1310)
* show hidden search results by default

* update break styles

* optimize filter, use debounce on input

* increase debounce time
2021-02-09 17:51:27 -05:00
Jordan Frankfurt
5c3c1c67f5 run npx browserslist@latest --update-db (#1303) 2021-02-09 11:49:51 -05:00
Moody Salem
9efd5da1f7 change branch in workflows 2021-02-03 12:56:02 -06:00
Jordan Frankfurt
8fd894f2d1 fix a prominent instance (#1304) 2021-02-03 12:34:46 -05:00
Ian Lapham
cc22183388 fix(modal cutoff, lists): make modal scrollable on small screens, fix typo, auto update all lists (#1301)
* update all lists, not just active ones

* fix typo
2021-02-01 12:37:18 -05:00
Moody Salem
9175dd10cc remove cloudflare insights 2021-02-01 09:54:06 -06:00
Moody Salem
bbd50f066d fixes https://github.com/Uniswap/uniswap-interface/issues/1214 2021-02-01 09:44:47 -06:00
Ian Lapham
2291e3ec20 improvement(lists): enable Gemini by default (#1276)
* update import flow and style tweaks

* update tests

* Update tsconfig.json

* add gemini

* enable gemini
2021-01-21 17:34:15 -07:00
Ian Lapham
28d8f0b0bb Improvement(token import): update import flow and style tweaks (#1273)
* update import flow and style tweaks

* update tests

* Update tsconfig.json
2021-01-20 23:22:32 -07:00
Ian Lapham
8bed3900ba Improvements(lists): update defaults, style updates (#1270)
* update defaults, style updates

* update icon color
2021-01-18 14:40:42 -05:00
Ian Lapham
a1000c6576 improvement(routing): use WBTC pairs for routing swaps, style updates (#1269)
* 'Added WBTC to default option for routing in main UI"

* update settings button

Co-authored-by: Gismar <thegismar@gmail.com>
2021-01-18 13:29:13 -05:00
Ian Lapham
267204d98e Improvement(lists): Switch to multiple active lists (#1237)
* basic support for multiple active lists

* start search across inactive lists

* store card before list update

* basic import flow for inactive tokens

* update supported lists

* update import flow for address pasting

* basic mvp

* hide filter if no results

* update min heights

* update manage view, index tokens on page load

* start routing fix for multi hops

* switch to input amount comparison on exactOut

* start list import view

* updated list UI, token search updates, list import flow, surpress popups and warnings

* add unsupported tokens

* show warning if logged out

* update to opyn list

* show token details on warning;

* make percent logic more clear

* remove uneeded comaprisons

* move logic to functions for testing

* test updates

* update list reducer tests

* remove unused locals

* code cleanup

* add unsupported local list

* add multi hop disable switch

* add GA

* fix bug to return multihop no single

* update swap details

* copy updates

* Visual refinements

* Further tweaks

* copy updates, actual list order

* Move settings button

* Update all trade views with settings cog

* Add better tips, remove darkmode toggle from dropdown

* Clean up routing UI

* UI tweaks

* minor tweaks

* copy updates

* add local default list, use existing function for trade comparison, disable v1 helper, show inactive/active at once

* updated inactive view

* remove slippage fix

* update output amount return

* center button, update search to character threshold

* reset add state on back navigation

* style tweak on add button

* fix bug on search results

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
2021-01-14 11:20:50 -05:00
vbstreetz
74f50f1b7e fix(typo): toggle (#1260)
Closes #1229
2021-01-13 13:16:41 -06:00
Ian Lapham
a70aa41df2 Routing updates only (#1265)
* start routing fix for multi hops

* switch to input amount comparison on exactOut

* make percent logic more clear

* remove uneeded comaprisons

* move logic to functions for testing

* add multi hop disable switch

* add GA

* fix bug to return multihop no single

* update swap details

* code clean

* routing only
2021-01-12 17:36:57 -05:00
Ian Lapham
587b659816 catch error in bytes32 string parsing (#1253) 2020-12-30 11:50:40 -05:00
Ian Lapham
5388cab779 update vote timestamp estimation (#1242) 2020-12-21 14:49:58 -05:00
Georgios Konstantopoulos
cadd68fb37 feat: allow overriding proposals in the UI (#1239)
* feat: allow overriding proposals in the UI

* Fix code style issues with ESLint

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
2020-12-18 10:12:22 -05:00
Moody Salem
ab8ce37ceb fix(token lists): update to latest token-lists 2020-12-10 16:18:18 -06:00
Moody Salem
5a9a71ae2d improvement(analytics): try out cloudflare analytics 2020-12-10 10:10:50 -06:00
Noah Zinsmeister
b93fd230bd Specially Designated Nationals and Blocked Persons Ethereum addresses 2020-12-08 16:11:42 -05:00
Wei Li
e9a11bb604 fix(vote): make the vote page responsive 2020-12-01 21:40:28 -06:00
Noah Citron
c5afbedb3e fixed bug causing wbtc to have 18 decimals (#1211)
Co-authored-by: Moody Salem <moodysalem@users.noreply.github.com>
2020-11-25 07:59:24 -06:00
Matheus Faria de Alencar
5b2c44522e fix(list popup): big token list updates scroll issue (#1219) 2020-11-20 09:28:25 -06:00
Ian Lapham
7fd4005154 Fix(UNI page): hide inactive pools (#1213)
* Hide rewards

* remove config changes

* move active detection to hook, small changes

* replace string math
2020-11-18 13:43:17 -05:00
Moody Salem
48eab0d0ac bump token lists 2020-11-11 09:40:53 -06:00
Ian Lapham
c9ee1b3b32 Fix(pool page): show deposited liquidity on pool page (#1195)
* Show staked liquidity on pool page

* update button, cleanup

* Show combined balance for staking cards

* hide buttons on position card if no balance
2020-11-09 12:11:58 -05:00
Grzegorz Kućmierz
eb4c305eff fix(ens): support ens names with dashes in them
* fix: parseENSAddress #1200

* fix: lint error #1200

* fix: parseENSAddress - allow to match domains with twice characters; disallow more than once dash next to each other
2020-11-07 08:19:01 -08:00
Anxo Rodriguez
d9825622f1 Allow absolute imports (#1185) 2020-11-03 09:16:38 -06:00
Nadav Hollander
8975086a69 Remove title parsing from description output (#1179)
Co-authored-by: Nadav Hollander <nadavhollander@Nadavs-Work-MacBook-Pro.local>
2020-10-24 19:40:56 -04:00
Moody Salem
ddf88345a9 fix(token lists): stop showing notifications for lists that are not selected (#1174) 2020-10-20 14:03:51 -05:00
Shane Fontaine
32ac25556b improve displayed proposal end time accuracy (#1173) 2020-10-19 10:14:56 -04:00
Ian Lapham
50a599c005 update vote fetching logic, delegate only on global page (#1165)
* update vote fetching logic, delegate only on global page

* Update index.ts

* add helper message on proposal page

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2020-10-12 17:13:27 -04:00
Ian Lapham
9c473270ee fix(governance): modal bugs, redable styles, show common names for contracts (#1164)
* fix modal bugs, reable styles, show common names for contracts

* use theme for blue
2020-10-12 15:45:20 -04:00
_XiaoTian
b650b17563 fix: Fix the style of account transaction record list. (#1104)
Co-authored-by: Moody Salem <moodysalem@users.noreply.github.com>
2020-10-09 20:09:33 -05:00
Nikita Kudryavtsev
fc76177791 fix style of proposals text block in vote page (#1149) 2020-10-09 18:12:49 -05:00
Jay Welsh
69655980db fix(ethereum-logo): centers Ethereum logo (#1157) 2020-10-09 10:52:06 -05:00
TM Lee
e835a34d0d fix(coingecko token list): Replace CoinGecko tokens list with all.json (#1147) 2020-10-01 22:45:51 -05:00
Jordan Frankfurt
33fdf28dec fix(notifications): update margin for EVEN SMALLER mobile devices (#1146) 2020-10-01 14:00:13 -05:00
Jordan Frankfurt
c45c293e82 fix(mobile popups): add right margin to mobile notifications (#1142) 2020-10-01 13:23:48 -05:00
Moody Salem
9e140d1214 chore(release): reenable automatic releases 2020-10-01 11:21:48 -05:00
Paul Razvan Berg
bf8efc9bf6 fix(ro translations): update ro translation (#1143) 2020-10-01 11:01:02 -05:00
Moody Salem
4f922a2824 fix(token lists): bump token list spec version (allowing lists up to 10k tokens) 2020-09-29 11:50:32 -05:00
Moody Salem
b50cac0181 fix(uni): fix uni in circulation calculation and make uni button always show up 2020-09-28 15:13:22 -05:00
Moody Salem
1ac16586ff remove npm token 2020-09-24 14:43:08 -05:00
Moody Salem
e2686e0ed1 pull request target instead 2020-09-24 12:18:40 -05:00
Moody Salem
f64b3c2c8f improve the issues config.yml 2020-09-24 11:55:23 -05:00
Moody Salem
5a579e05e7 add a contact link to discord 2020-09-24 11:52:14 -05:00
Moody Salem
22a7fdc8e7 fix the tests 2020-09-24 11:37:03 -05:00
Moody Salem
8c4fd0a47d Some cleanup of workflows
BREAKING CHANGE: trigger a new major release
2020-09-24 11:35:53 -05:00
Moody Salem
7d526c5855 enable manual trigger 2020-09-24 11:31:18 -05:00
Moody Salem
03913d9c0b UNI 2020-09-24 11:29:31 -05:00
Moody Salem
23c7f4ce38 temporarily disable releases for bugfix 2020-09-09 17:24:35 -05:00
Moody Salem
f33bba2176 chore(token lists): Bump token lists spec version 2020-09-01 11:37:53 -05:00
Moody Salem
fb31c75838 test failed due to randomness. make it less likely to fail. 2020-09-01 11:28:18 -05:00
Moody Salem
b8c383c20e fix(#1083): fix wrapping of tokens added/removed in list update popup 2020-09-01 10:45:26 -05:00
Tran Vinh Quang
fbf39e4932 Update vi.json (#1057) 2020-08-31 15:23:02 -05:00
Ian Lapham
975570fa97 improvement(swap): progress bar and more minimal default UI, also fix custom add/remove (#1069)
* add progress bar and minimal UI updates on swap

* add hook to explicity check user added tokens, fixes add/remove bug

* update with latest

* remove confusing comment

* update styles on loading, update arrow placement, code cleanup

* fix typo on progress import
2020-08-31 15:27:30 -04:00
Moody Salem
d6aa0e98a4 chore(token lists): replace aave token list with ens name 2020-08-28 10:52:21 -05:00
Ian Lapham
4644cd7b0a update missing logo icon (#1070) 2020-08-27 20:49:28 -04:00
Moody Salem
9ddedd8dab fix(pending approves): pending approves that are too old should not cause 'approving' to get stuck 2020-08-27 15:34:04 -05:00
Moody Salem
95030a52c5 fix(remove liquidity): price display in remove liquidity incorrect 2020-08-27 14:26:23 -05:00
Moody Salem
1911f72536 improvement(swap): show better trade link if trade doesn't exist on one version 2020-08-27 13:25:25 -05:00
Moody Salem
85217452db improvement(ts): strict everywhere 2020-08-27 13:10:00 -05:00
Moody Salem
f7a1a2ab58 move noImplicitAny and some type declarations 2020-08-27 12:24:03 -05:00
Moody Salem
66a2006284 more strictness everywhere, fix a pair pricing issue in mint/hooks.ts 2020-08-27 12:05:09 -05:00
Moody Salem
610b7f4464 make integration tests pass more reliably, some reducer refactoring 2020-08-27 10:21:51 -05:00
Callil Capuozzo
ce12635332 Merge branch 'master' of https://github.com/Uniswap/uniswap-interface 2020-08-26 15:15:06 -04:00
Callil Capuozzo
2182e18f85 Add coingecko and tweak list introduction screen 2020-08-26 15:14:48 -04:00
Moody Salem
ad2c7dfdff add 3 more lists 2020-08-26 13:36:55 -05:00
Moody Salem
cb36c9103e fix integration tests, update default list 2020-08-26 11:43:10 -05:00
Moody Salem
0a1459ee83 remove any bias from the list selection 2020-08-26 10:47:55 -05:00
Moody Salem
8896a042f0 title for list URL only on list origin 2020-08-26 10:14:38 -05:00
Moody Salem
61ad07c3f2 add zerion list 2020-08-26 10:10:57 -05:00
Moody Salem
81a5164d99 fix the browse lists link 2020-08-26 09:34:46 -05:00
Moody Salem
467e80a42f improvement(#1043): do not allow swapping to bad addresses 2020-08-26 09:19:59 -05:00
Moody Salem
58f25aa439 another list 2020-08-26 08:54:33 -05:00
Moody Salem
377c71f2e5 bump to latest token lists version 2020-08-26 08:48:59 -05:00
Moody Salem
7cf25ac7c8 feat(lists): allow selecting and adding token lists (#1023)
* more list stuff

Use the selected list instead of the default list, but also use the default list

start list selection code

* move token warning to a modal, fix the install issue

* add/remove/enter key

* handle enter on currency select for ETHER

* change slippage tolerance to be a slider

* make ui closer to the mocks

* commit slider changes

* back to tabs

* copy changes

* bump list version

* some styling for the list select

* bump uniswap default list version

* use contract calls to get ens names and addresses

* show list logo

* fix failing integration test

* .eth.link

* list introduction screen

* remove showSendWithSwap

* fix integration and unit tests

* resolve ENS names

* logos from ens

* fix the lint errors

* some refactoring to better support using a the library provider from the user for resolving ENS names

* load list info from the list url for the introduction page

* make it slightly harder to remove a list

* minor clean up, some help text and links

* remove icon from list update popup

* show added/removed tokens

* add GA everywhere, don't debounce contenthash lookups

* show tags

* fix tag key

* tag display, list rendering, needs optimization

* fix list fetching in firefox, style issue in safari

* sort the lists, clean up styling

* use client provider when possible

* show token warning for url loaded tokens

* improve the warning modal

* some refactoring to fix the list fetching on networks other than mainnet

* fix tests

* some minor improvements

* increase timeout to maybe fix integration tests which pass locally

* build for tests using the dev network url

* reset the lists if we deleted the other two copies

* improve how we handle updating the default list of lists

* fix integration test

* Update token list selection styles

* fix external links, reuse the on click outside code, show add errors

* show the list origin instead of the full url

* fix update list link

* show host instead of hostname
do not automatically dismiss major version upgrades for lists

* fix link to tokenlists.org

* add uma

* clean up styling in list rows

* bump token list version

* bump token list version again

* hover symbol to see currency name

* bump version

* add cmc lists, dharma list

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
2020-08-26 08:46:21 -05:00
Moody Salem
09b54570e1 fix integration test for recipient 2020-08-24 13:52:41 -05:00
Moody Salem
73580de922 improvement(swap): show add a send only for expert mode 2020-08-24 13:33:46 -05:00
Moody Salem
e32fd3a8fc disable blank issues 2020-08-18 10:26:33 -05:00
Moody Salem
057417c666 add a snippet to the issue templates 2020-08-18 10:25:02 -05:00
Moody Salem
f1b300af70 add a hook for getting the USDC price of any currency 2020-08-17 08:36:38 -05:00
Moody Salem
600049bc6e fix(list): change the url of the default token list so we can move the file in the npm package 2020-08-11 14:44:25 -05:00
Moody Salem
6e91311489 remove console.log statement 2020-08-10 15:11:05 -05:00
Moody Salem
f6a464cb3b fix(ampl): do not swap ampl via pairs other than DAI/ETH 2020-08-10 15:05:41 -05:00
Moody Salem
e589c751d7 improvement(swap errors): show more information about the swap error 2020-08-10 12:05:35 -05:00
Moody Salem
0f91af1df2 improvement(swap): Better swap errors for FoT (#1015)
* move the gas estimation stuff into its own hook and report errors from the gas estimation

* fix linter errors

* show the swap callback error separately

* rename some variables

* use a manually specified key for gas estimates

* flip price... thought i did this already

* only show swap callback error if approval state is approved

* some clean up to the swap components

* stop proactively looking for gas estimates

* improve some retry stuff, show errors inline

* add another retry test

* latest ethers

* fix integration tests

* simplify modal and fix jitter on open in mobile

* refactor confirmation modal into pieces before creating the error content

* finish refactoring of transaction confirmation modal

* show error state in the transaction confirmation modal

* fix lint errors

* error not always relevant

* fix lint errors, remove action item

* move a lot of code into ConfirmSwapModal.tsx

* show accept changes flow, not styled

* Adjust styles for slippage error states

* Add styles for updated price prompt

* Add input/output highlighting

* lint errors

* fix link to wallets in modal

* use total supply instead of reserves for `noLiquidity` (fixes #701)

* bump the walletconnect version to the fixed alpha

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
2020-08-06 18:18:43 -05:00
Moody Salem
10ef04510a fix(fonts): font-display in non-font-variation-settings conditioned css 2020-07-31 12:12:42 -05:00
Moody Salem
e3b3d9e825 fix(swaps): band-aid fix for gas estimates to disable multihop for eth-ampl 2020-07-31 11:50:30 -05:00
Moody Salem
3050e967f7 fix(token lists): automatic updates to token lists 2020-07-30 18:32:14 -05:00
Ian Lapham
2150450760 improvement(token warnings): show better warnings for imported tokens (#1005)
* add updated ui warnings for imported tokens

* remove useless styling

* update to surpress on default tokens

* add integration tests for warning cards on token import

* remove callbacks as props in token warning card
2020-07-30 15:21:37 -04:00
Moody Salem
1b07e95885 fix(add liquidity): fix the mint hooks to return a price as well as return the dependent amount in the input currency (#1011) 2020-07-28 17:04:33 -05:00
Moody Salem
9bb50d6a7b unit tests for the uri to http method 2020-07-28 08:10:52 -05:00
Moody Salem
b08bb7eaff add an integration test 2020-07-27 13:29:25 -05:00
Moody Salem
3a36ac5538 chore(release): update dns again 2020-07-27 12:33:31 -05:00
Moody Salem
2962cd0e14 fix(migrate v1): migrate v1 pages and formatting 2020-07-27 12:33:02 -05:00
Moody Salem
6a311aa6d7 fix(v1 swap): exact out swaps not working 2020-07-27 08:45:48 -05:00
Moody Salem
e78b6d61f2 improvement(transactions): some clean up and unit tests
- fetch transaction state less often for old transactions
- fix a bug calling non payable methods with value 0
2020-07-27 08:45:48 -05:00
Moody Salem
365b429c0b feat(token lists): implement the uniswap default list as a token list (#983)
* load tokens from url `useTokenList`

* improve performance of the loading

* move the loading to redux and save loaded lists

* lint error

* move the list fetching code to a separate component

* change how token lists are fetched to use the updater and add unit tests

* fix a crash with currencyEquals

* bump sdk version

* token lists should automatically update for minor/patch changes

* nit

* show popups for list updates

* support pointing at localhost

* spuport ipfs/ipns logos

* use the updater to bump list versions

* save the old/new list in the popup for viewing diffs

* improve the list popup

* fix linter error, make sure visibility checking is working

* show list update notifications

* address a couple metamask warnings, linter error

* fix the custom added/default tokens

* refactor some popup stuff to reuse the fader

* linter error

* Revert: refactor some popup stuff to reuse the fader (a7b0f752)

* style improvements, linter

* add to the readme, drop the token-request template

* back to the beta that works with wallet connect

* get the dependencies to a state that works with wallet connect and passes integration tests
2020-07-25 10:41:03 -05:00
Moody Salem
32d300009e just bump the polling interval, aiming to have same # of blockNumber requests as calls or less 2020-07-21 15:57:09 -05:00
Moody Salem
806623c602 save some calls on the redundant chain id requests 2020-07-21 15:43:52 -05:00
Moody Salem
3272f8e9db chore(infura): rotate keys (complete) 2020-07-21 11:07:10 -05:00
Moody Salem
010ef108eb chore(infura): rotate keys 2020-07-21 11:05:55 -05:00
Moody Salem
19b1e9e399 feat(weth): support WETH across the site and use sdk 3.0 (#947)
* first pass of sdk 3.0

* second pass using weth

* kill unused pool popup

* get it compiling again

* first pass of sdk 3.0

* switch to currencies

* get it compiling after the big move merge

* restore margin

* clean up add liquidity more

* fix a bunch of bugs

* todo trade on v1

* show eth in currency list

* allow selecting eth in the swap page

* fix unit tests for swap page

* test lint errors

* fix failing integration tests

* fix another couple of failing unit tests

* handle selecting currency b when no currency a

* improve the import pool page

* clean up add liquidity for invalid pairs

* bold

* first pass at swap arguments for v1, some unit tests

* fix some bugs in add liquidity, burn hook

* fix last of ts errors in remove liquidity

* support wrapping/unwrapping weth

* kill a bunch of code including the dummy pairs

* required pair prop in the position card

* tests for the v1 swap arguments

* do not say estimated on the wrap ui

* show ETH instead of WETH in the pool summaries

* small size socks

* fix lint error

* in burn, use currencies from the URL

* fix some integration tests

* both contain weth

* receive eth/weth link

* fix empty row

* show wrapped only if one currency is weth

* currency selects in the remove liquidity page
2020-07-20 06:48:42 -05:00
Moody Salem
6287b95b92 fix(#961): change send copy 2020-07-17 09:05:20 -05:00
Ian Lapham
4e8a6e2a4c change copy on confirm view (#969) 2020-07-16 11:59:37 -04:00
Moody Salem
848c7b418b skip dns update while we work out cloudflare caching issues 2020-07-14 19:37:03 -04:00
Moody Salem
f619cf4353 fix the cf-ipfs url 2020-07-14 11:06:52 -04:00
Moody Salem
877db71e2a improvement(analytics): add exception reporting 2020-07-14 10:57:28 -04:00
Moody Salem
f4b5727fdb longer wait between retries 2020-07-13 18:03:12 -04:00
Moody Salem
1fd6b1e659 tweaking the slippage tabs for mobile again 2020-07-13 10:49:04 -04:00
Moody Salem
6570beef32 add BZRX token 2020-07-13 10:46:32 -04:00
Moody Salem
b57f58ab35 fix(title): link to relative path 2020-07-13 10:21:46 -04:00
Moody Salem
2f40c4f614 fix(settings): smaller slippage tabs for small screens 2020-07-12 12:57:06 -04:00
Moody Salem
3f9c34d37d always render the wordmark in the header 2020-07-12 12:53:05 -04:00
Moody Salem
1d5c6530e3 fix(header): some responsive style changes to the header 2020-07-12 12:50:53 -04:00
Moody Salem
78f294c340 more retries since metamask nodes often return old data 2020-07-12 12:43:36 -04:00
Moody Salem
90d24a26f3 retry tests 2020-07-11 15:25:33 -04:00
Moody Salem
7a3a5bd546 nit 2020-07-11 11:51:22 -04:00
Moody Salem
081ae15aa8 retry failed requests up to 3 times 2020-07-11 11:42:27 -04:00
Moody Salem
f5a5c5e70d fix(rpc spam): retries while remote node is out of sync 2020-07-11 11:09:45 -04:00
Moody Salem
e05e0206b7 fix a warning with add liquidity button 2020-07-10 15:31:58 -04:00
Moody Salem
344b4340ae improvement(pool): simplify pool flow, remove pool search modal (#941)
* deleting some code first

* strict, some refactoring

* denser common bases

* more add liquidity refactoring

* add liquidity paths working

* show common bases in the token selects

* fix the ability to select duplicate tokens

* useless rename

* try to handle alllll the duplicate token edge cases

* think i got them all lol

* remove common bases header

* Revert "remove common bases header"

This reverts commit 6ac4565d

* fix and add integration tests

* make gap between rows smaller

* get integration tests actually running again

* try another format of the command, upgrade serve

* frozen lockfile on install

* try the cypress github action

* install cypress in ci

* remove redundant ignore-scripts command

* use a specific github commit for the pinata action

* fix a bug in the multicall reducer, improve token list rendering performance

* improve the enter key on the token search modal

* stop using history.push

* fix linting errors

* position card cleanup before updating to match mock
2020-07-10 15:25:15 -04:00
Moody Salem
eeef306bdd fix(🧦): 🧦 2020-07-10 12:42:24 -04:00
Moody Salem
63a491d4b1 improvement(approval): show approval state approved if allowance exceeds amount to approve, even when pending 2020-07-09 13:34:29 -04:00
Moody Salem
6831a73fdf fix(swap): revert the change to reload query parameters on every url change 2020-07-09 10:35:14 -04:00
Moody Salem
a4aef02747 nit(swap): add "(optional)" to add recipient button 2020-07-09 10:28:58 -04:00
Moody Salem
c26716047f chore(release): allow (new) manual trigger of release 2020-07-09 09:57:17 -04:00
Moody Salem
0fa238af0b fix(swap): swap to account if recipient is null (#940)
* fix(swap): swap to account if recipient is null

* fix naming and strict ts error
2020-07-09 09:55:20 -04:00
Moody Salem
21c1484c0e feat(send page): remove send page, implement recipient feature in swap page (#934)
* quick poc for removing swap page

* accidental import

* error for recipient field

* query parameter working

* undo id change

* tweaks to match mocks better

* tweaks to match mocks better

* some extra integration tests

* clean up nav tabs a bit

* clean up nav tabs a bit

* space swap/pool better

* stop selecting button text when double clicking

* remove unused transfer modal header

* add info to swap confirm modal

* shorten address

* improve summary message, remove unused send callback, fix react ga event

* fix lint errors

* arrow color
2020-07-08 23:06:29 -04:00
Antonio Savage
8a845ee0e9 fix(discord invite link): working discord invite link (#929) 2020-07-06 22:59:14 -04:00
Moody Salem
f5229ca838 linter error 2020-07-06 21:31:08 -04:00
Moody Salem
875203f0ef fix(responsiveness): small tweaks for mobile 2020-07-06 21:26:38 -04:00
Moody Salem
91a8202737 fix(send page): support swap + send query parameters on send page (#921)
* support swap + send query parameters on send page

* revert the unfinished portis logic

Co-authored-by: ianlapham <ianlapham@gmail.com>
2020-07-05 22:32:54 -04:00
Moody Salem
0b4819d165 fix(#899): Add PieDAO USD++ 2020-07-05 22:29:49 -04:00
Jonathan Diep
e7d3289754 improvement(token warning card): link to the token page on etherscan instead of the address page (#914) 2020-07-02 08:27:19 -04:00
Moody Salem
0698e0f82a Update README.md 2020-07-01 13:09:37 -04:00
Micah Zoltu
0350cc4701 fix(REP token): renames REP to REPv1 (#915)
https://www.augur.net/blog/v2-launch/

TL;DR: Augur v2 launch is coming up and will introduce a new REP token.  FF has requested all exchanges rename REP to REPv1 to avoid confusion.

Going forward, REP tokens will contain versioning in their name/symbol on chain so this should be a one-time "fix" for Augur v1 REP.
2020-07-01 10:19:28 -04:00
Moody Salem
997052869d fix(lint): linter error 2020-06-30 16:50:19 -04:00
Moody Salem
9ec16c2ba8 actually add the inter-ui dependency 2020-06-30 16:47:16 -04:00
Moody Salem
e2cf8f1642 fix(font): do not load font from remote 2020-06-30 16:43:21 -04:00
Moody Salem
ed6952d1f7 readme cleanup 2020-06-30 14:13:27 -04:00
Moody Salem
3277d70e93 fix all tests 2020-06-30 14:02:09 -04:00
Moody Salem
d1a31fe763 old link 2020-06-30 13:53:18 -04:00
Moody Salem
f88af029ae chore(tests): fix integration tests 2020-06-30 13:51:20 -04:00
Moody Salem
9f3e49b4d8 chore(ipfs migration): point at master branch instead of v2 branch 2020-06-30 13:49:38 -04:00
Moody Salem
d4911d1054 chore(ipfs migration): changes for ipfs url migration
- remove netlify stuff
- update rename to uniswap-interface
- always use hash router
2020-06-30 13:41:51 -04:00
Moody Salem
90df9c4ced improvement(layout): move header version switch, drop footer for mobile (#910)
* version switch tweaks

* Mobile layout and toggle tweaks

* Remove the entire footer

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
2020-06-29 16:55:33 -04:00
Callil Capuozzo
14f15d1fd6 fix(i18n): Fix return characters and remove uneeded file (#912) 2020-06-29 14:15:45 -04:00
Moody Salem
69818ace1f fix(popover): animation getting stuck open on firefox 2020-06-28 13:55:54 -04:00
Moody Salem
42906d6709 add BAL 2020-06-27 12:27:10 -04:00
Moody Salem
2f8936a980 unused keys 2020-06-26 14:46:22 -04:00
Moody Salem
f5c4468c3c fix(token logo): fix persistent error state in token logo, clean up swap route code 2020-06-26 14:44:33 -04:00
Moody Salem
852e8f749f fix(swap routing): max hops back to 3 2020-06-26 14:12:12 -04:00
Moody Salem
6694e5e398 improvement(swap routing): consider more bases in the swap (#909)
* consider more bases in the swap

* all match type

* max hops 2, only 1 result
2020-06-26 13:27:38 -04:00
Noah Zinsmeister
2c9a50a372 remove trust deep link 2020-06-25 10:20:27 -04:00
Noah Zinsmeister
0fc0cba6de bump walletconnect 2020-06-25 10:17:49 -04:00
Moody Salem
041c86c04d fix dns variable 2020-06-24 20:07:50 -05:00
Moody Salem
123373e671 docs in release, trigger another release 2020-06-24 19:33:26 -05:00
Moody Salem
eb1732deee release text 2020-06-24 19:15:18 -05:00
Moody Salem
3c13321a71 point at a specific audited commit for the cloudflare update action 2020-06-24 19:12:26 -05:00
Moody Salem
58703f31a0 chore(release): update cloudflare's DNS instead of vercel's DNS 2020-06-24 19:09:02 -05:00
Moody Salem
58721fb191 improvement(remove liquidity): fix width of buttons on small screens 2020-06-24 11:55:19 -05:00
Noah Zinsmeister
678cd1a06f upgrade to walletconnect v1 (#903) 2020-06-23 16:18:04 -04:00
Moody Salem
a5ff3beb92 add a comment for the previous change 2020-06-22 16:12:02 -05:00
Moody Salem
35ccf425f6 improvement(modals): do not focus inputs automatically on mobile 2020-06-22 16:11:32 -05:00
Moody Salem
fe030412cd improvement(pair search modal): show exact symbol match pairs first, filter before sorting 2020-06-22 14:37:52 -05:00
Moody Salem
4d5a43351f fix(trustwallet confirm modal): fix the confirmation modal to not be obscured by the trustwallet bar 2020-06-15 23:09:33 -04:00
Moody Salem
ac1bc3b3a6 cleanup(modal): clean up modal code a bit 2020-06-15 16:46:38 -04:00
Noah Zinsmeister
d1063d50ed add COMP, mUSD, STAKE 2020-06-15 16:24:39 -04:00
Moody Salem
46fc74e90f chore(deploy): trigger a deploy and also trigger deploys on .env.production changes 2020-06-15 10:19:46 -04:00
Moody Salem
2c4f4092d8 improvement(advanced): always show advanced (#890)
* always show advanced

* fix test

* always show the price, less jitter

* show tokens in price field

* fix the dropdown sticking around when switching between swap/send

* lint

* fix ios scrolling the modal body into the viewport bug

* don't use react-spring for simple slide animation

* safer price impact constants
2020-06-15 10:13:12 -04:00
Moody Salem
aac7268dc8 just drop the git commit hash (more trouble than it's worth) 2020-06-15 09:36:39 -04:00
Callil Capuozzo
fd162a72ff feat(expert mode): Add expert mode (#828)
* Add expert mode scaffolding

* move advanced settings to settings tab, add expert mode

* update settings modal

* update font weight

* fix text

* update with modal
;

* add null checks

* update with input checking

* merge and add fixes

* update language and bg

Co-authored-by: ianlapham <ianlapham@gmail.com>
2020-06-12 15:26:28 -04:00
Noah Zinsmeister
e20936709c improvement(migration): improve v1 migration flow (#885)
* first stab at improving v1 migration flow

* lint errors

* improve UI

* fix loading indicator

* switch back to dedicated migration UI

* address comments

* make migrate consistent with new token behavior

* hooks -> utils
2020-06-12 14:04:42 -04:00
Moody Salem
2fda2c8c15 fix(background image): cuts off at the bottom when scrolling 2020-06-12 13:42:35 -04:00
Moody Salem
1f09757c49 fix(lp fee): correct the computation of the realized LP fee 2020-06-12 12:07:28 -04:00
Moody Salem
7e49babff7 improvement(token search): No automatic add (#888)
* no automatic add, major refactors

* fix nit with version link

* add/remove links in the token search modal

* close tooltip when user types

* remove skip
2020-06-12 11:15:18 -04:00
Noah Zinsmeister
b35653ade1 fix approval bugs (#887)
clear signature on input in remove
2020-06-11 19:34:53 -04:00
Ian Lapham
57b53013d1 improvement(add/remove liquidity): update approve flow on add + remove (#879)
* update approve flow on add + remove

* add confirm to remove page
2020-06-11 17:01:39 -04:00
Moody Salem
bafd3f3c05 feat(v1-support): Enable executing swaps on v1 or v2 (#883)
* swap-v1

* toggle the version switch based on the search query parameter

* rework some of the query parameter stuff in send/swap

* hide the url when they click it

* allow switching back to v2 via the toggle

* represent the v1 trade in the UI if they toggle it on

* show trade link in both directions (5% threshold for v1 link)

* input amounts should reflect v1/v2

* perform the approve on v1 exchange for v1 trades

* get swap on v1 working

* move some code around to reduce duplication

* fix ts error

* correct input allowance

* fix exact token to token on v1

* fix pending approvals to be specific to the spender

* google analytics for swap version

* disable the version switch on pages other than swap and send
2020-06-11 15:56:28 -04:00
Moody Salem
29db0a50b3 fix(multicall): reduce spam in error case 2020-06-10 16:14:49 -04:00
Moody Salem
9566fb888e - do not deploy on weekends
- update text in release body
2020-06-10 13:55:35 -04:00
Ian Lapham
13c8903e8f add border to QR scanner (#877) 2020-06-08 11:53:40 -04:00
Moody Salem
1d06b47e8d workaround so tests are less flaky 2020-06-08 10:51:14 -04:00
CryptoCatVC
0089c2ee43 Create iw.json (#868) 2020-06-08 10:06:44 -04:00
Moody Salem
9869a9fcb7 fix(swap): if swap fails in FoT router but not in regular router, throw an error (#875)
this is an alternative solution to hard coding forced FoT router tokens
2020-06-08 10:06:37 -04:00
dependabot[bot]
631f29d66d chore(deps): bump websocket-extensions from 0.1.3 to 0.1.4 (#873)
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-06-08 10:04:57 -04:00
Moody Salem
97deebad37 temporary workaround for tokens that have transfer methods that succeed but do not transfer tokens 2020-06-06 08:41:31 -04:00
Noah Zinsmeister
e667615449 support proposed new FoT-capable methods (#866)
* support new FoT-capable methods

short-circuit modal footer rendering if no trade

improve clarity of tx modal flow

* update router address + ABI
2020-06-05 18:40:02 -04:00
CryptoCatVC
4ab61faeae Create he.json (#864) 2020-06-05 18:30:45 -04:00
Ian Lapham
0004db3d4a Style updates, Approve UI updates (#841)
improvements(approvals): match approve flow to add/remove, update UI styles
2020-06-05 13:20:47 -04:00
Moody Salem
c133c472be fix(search): fix searching tokens to be more tolerant, and sort the exact matched symbols first 2020-06-05 11:20:04 -04:00
Noah Zinsmeister
0019ccdf51 add ren* tokens 2020-06-04 20:58:14 -04:00
Moody Salem
5a1a469f35 perf(multicall): remove the validation that caused app to feel sluggish 2020-06-03 23:19:19 -04:00
Moody Salem
4c28f34803 remove vercel.json to put it in another repo 2020-06-03 22:35:56 -04:00
Moody Salem
104be830fc perf(multicall): use single call to get token information (#855)
* use single call to get token information

* delete the bytes32 overload

* console log statement

* add a bunch of tests to actions.ts for multicall

* fix to work with bytes32 symbols/names

* only include name/symbol

* enforce lowercase calldata
2020-06-03 22:15:26 -04:00
Moody Salem
24c70791cd lint error 2020-06-03 20:23:01 -04:00
Moody Salem
216fdea290 fix(instrumentation): event for migration v1->v2 2020-06-03 20:22:28 -04:00
Moody Salem
40e4ce2ed3 fix(style): add missing padding 2020-06-03 17:23:21 -04:00
Moody Salem
b2508fc6f2 Add missing capture group 2020-06-03 14:52:38 -04:00
Moody Salem
f73b37287f Remove the invalid redirect 2020-06-03 14:48:25 -04:00
Moody Salem
c09eb738c3 Add vercel.json for redirects in vercel deployment 2020-06-03 14:46:02 -04:00
Moody Salem
6de3a6ec28 Avoid duplicate popups for mined transactions 2020-06-03 14:30:07 -04:00
Moody Salem
c1d35cc8b3 feat(migrate): adds the migrate flow to the uniswap exchange site
* links and page

* print all the details of the liquidity

* show working approve/migrate buttons

* testnet v1 factory addresses

* split code up into two pages

* getting closer to styled

* compute min amount out of eth and token

* compute min amount out of eth and token

* add a back button to the list page

* Improve empty states

* Improve the state management

* change the display of the migrate page to be more similar to original

* style fix, pending transaction hook fix

* add forwarding to netlify.toml

* handle case where v2 spot price does not exist because pair does not exist

* make ternaries more accurate

* handle first liquidity provider situation

* Style tweaks for migrate

* merge

* Address feedback
- show pool token amount
- show success when migration complete
- show price warning only if price difference is large

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
2020-06-03 14:07:01 -04:00
Moody Salem
f279b2bea2 fix(misc): migrate link and gradient cut off 2020-06-03 13:36:31 -04:00
Noah Zinsmeister
6ffbf756f8 memoize common bases (#853)
* memoize common bases

* improve memoization
2020-06-02 15:43:10 -04:00
Moody Salem
10837d7ba1 fix(scrolling): too much scrolling 2020-06-02 12:28:35 -04:00
Noah Zinsmeister
2d6eddf9d4 introduce batched usePairs (#851)
centralize all lists of bases

pin some pairs
2020-06-01 16:55:31 -04:00
Moody Salem
aadf43efc3 chore(v1): add ipfs links for v1 to the v1 release 2020-06-01 11:20:05 -04:00
Noah Zinsmeister
227f729ecd add AST and EBASE 2020-06-01 10:53:40 -04:00
Moody Salem
a5b15e37f6 fix(application state): fix block number updates after changing networks 2020-05-31 15:33:31 -04:00
Moody Salem
2408b2966e fix(misc): fix styling on the having trouble link 2020-05-31 00:24:03 -04:00
Moody Salem
dc391d1bea fix(misc): fix the blinking ethereum logo on ios safari and temporarily remove the overflow settings to fix double scrolling situation 2020-05-31 00:15:17 -04:00
Moody Salem
e2d0514344 chore(cleanup): separate the components for 3 kinds of links we use 2020-05-30 23:33:16 -04:00
Moody Salem
98d25dd2af perf(cleanup): remove the material ui dependency for the input range 2020-05-30 21:22:59 -04:00
Moody Salem
f289dec684 fix(multicall reducer): add test and fix update multicall results 2020-05-30 03:10:49 -04:00
Moody Salem
73d3df05f2 perf(network connector): use an inline network connector until improved connector is pushed upstream 2020-05-30 02:03:02 -04:00
Moody Salem
83554f44f8 perf(multicall): add unit tests and fix a bug (#845)
* start with the migrate page

* Add a bunch of tests and bump up the call size

* Show a link to the old portal, disable the WIP page

* Fix lint error
2020-05-29 20:07:18 -04:00
Moody Salem
320b2e384b chore(constant): update token list 2020-05-29 15:20:23 -04:00
Moody Salem
9492e7375a chore(multicall/migrate): move some v1 stuff around for migrate 2020-05-29 15:08:07 -04:00
Moody Salem
8a6a10be9d chore(multicall): lint error 2020-05-29 13:25:55 -04:00
Moody Salem
5e486fca7f perf(multicall): improve fetching code to allow for fetching immutable data like token symbols/names/decimals 2020-05-29 13:23:49 -04:00
Moody Salem
87d24c404b fix(multicall): v1 pair lookup 2020-05-29 11:52:20 -04:00
Moody Salem
d4011f73d1 fix(multicall): return loading states from the multicall hooks #842 2020-05-29 11:48:33 -04:00
Moody Salem
6fc3157977 chore(strict): strict connectors directory 2020-05-29 11:05:54 -04:00
Moody Salem
9c1fe53e4b perf(ethereum): reduce number of calls by batching all polling node calls (#840)
* initial refactoring

* rebase lint error

* start implementing reducer

* multicall reducer

* working multicall!

* clean up performance, re-fix annoying error

* use multicall everywhere

* use multicall for balances

* fix lint warning

* Use checksummed address

* Fix strict warning

* get it to a working state with the more generic form

* convert useETHBalances

* Remove the eth-scan contract completely

* Remove the eth-scan contract completely more

* Default export

* Put the encoding/decoding in the methods that can do it most efficiently

* Avoid duplicate fetches via debounce

* Reduce delay to something less noticeable

* Return null if pair reserves are undefined to indicate it does not exist
2020-05-28 21:17:45 -04:00
Noah Zinsmeister
28c916ff45 Remove liquidity callback (#837)
* give add liquidity the reducer treatment

rename setDefaultsFromURL to setDefaultsFromURLSearch

* fix tests and crash

* rework DOM structure to make flow more natural

* allow slippage + deadline setting in add liquidity

* migrate burn

* disable token selection in mint

clear input between pairs

* reset fields between pairs

* tweak helper text

* address review comments
2020-05-27 13:13:31 -04:00
Moody Salem
7adb4b6bd6 chore(release): fix changelog commit hashes 2020-05-27 12:29:49 -04:00
Noah Zinsmeister
b2f0236ee8 Add liquidity callback (#830)
* give add liquidity the reducer treatment

rename setDefaultsFromURL to setDefaultsFromURLSearch

* fix tests and crash

* rework DOM structure to make flow more natural

* allow slippage + deadline setting in add liquidity

* disable token selection in mint

clear input between pairs
2020-05-27 11:42:25 -04:00
Moody Salem
4b57059353 fix(release): fix the dns record update 2020-05-27 09:58:28 -04:00
Moody Salem
6926f9a4ae fix(flatMap): don't use the native flatMap function, bump release plugin version 2020-05-27 09:29:53 -04:00
Moody Salem
7dec580944 chore(readme): prepare for more ipfs, use the latest version of github tag action 2020-05-27 09:14:12 -04:00
Moody Salem
5cf95680ef chore(release): fix cf-ipfs url 2020-05-27 09:01:20 -04:00
Moody Salem
f8d6bab4ae chore(release): limit release to release.yaml changes on v2 branch 2020-05-27 09:00:57 -04:00
Moody Salem
c9721c42bf perf(reduce call volume): save a bunch of calls to infura when the tab is not focused, change infura IDs for v2 2020-05-26 13:36:09 -04:00
Moody Salem
4414134bb2 chore(release): Fix release links 2020-05-26 13:17:53 -04:00
Moody Salem
44ba54e44a chore(release): add cf-ipfs.com 2020-05-26 13:03:50 -04:00
Moody Salem
9ec3109f72 chore(release): release text, convert cidv0 2020-05-26 13:01:38 -04:00
Moody Salem
e75793676a fix(release): include release changelog 2020-05-26 12:16:00 -04:00
Moody Salem
32006ded21 chore(release): trigger release on changes to release.yaml 2020-05-26 12:06:40 -04:00
Moody Salem
d4f1c579d8 chore(release): remove cancel action because it's confusing 2020-05-26 11:03:52 -04:00
Moody Salem
95f3541807 fix release.yaml script again 2020-05-26 10:30:12 -04:00
Moody Salem
da4ca73a1d post install scripts are needed for cypress 2020-05-26 10:07:32 -04:00
Moody Salem
e75bf8d003 typo in action name 2020-05-26 10:05:45 -04:00
Moody Salem
236f68a459 Missed a spot in the yarn tests 2020-05-26 09:59:59 -04:00
Moody Salem
9f07baaad2 Move the github action to its own repo 2020-05-26 09:54:33 -04:00
Moody Salem
c75464e1aa chore(release): only trigger release on tag 2020-05-26 09:43:48 -04:00
Moody Salem
bc80585bb4 chore(release): frozen lockfiles 2020-05-26 09:41:54 -04:00
Moody Salem
ad45b2b7bb chore(release): speed up install significantly 2020-05-26 09:38:43 -04:00
Moody Salem
63ac89e9f3 chore(release): clean up release.yaml 2020-05-26 09:28:47 -04:00
Moody Salem
1b6ae0d3db chore(release): trigger a release on tagged commits 2020-05-26 09:16:22 -04:00
Moody Salem
7d67819604 chore(release): Add an action to replace Vercel DNS records 2020-05-26 09:07:09 -04:00
Moody Salem
7b9b332c42 fix(popover): ios safari not showing the popover 2020-05-25 01:39:50 -04:00
Moody Salem
01feae978a add a discord link (#833) 2020-05-24 04:47:41 -04:00
Moody Salem
2452d51e14 fix(remove swap button): weirdness on firefox 2020-05-23 22:21:20 -04:00
Moody Salem
bbdc258083 fix(search modal): prevent overlapping tooltips, always render lists 2020-05-23 20:14:41 -04:00
Moody Salem
27b103e3f7 fix(search modal): restore style on sort button 2020-05-23 20:10:16 -04:00
Moody Salem
2a751b9892 Workaround for popover placement on elements that move, styling for small amounts of search results 2020-05-23 20:08:12 -04:00
Moody Salem
175e93fbba chore(release): Update the release text 2020-05-23 16:07:23 -04:00
Moody Salem
0b5fc07ee5 fix(popover): use the same offset 2020-05-23 12:17:39 -04:00
Moody Salem
a0d4710a11 perf(Search modal): performance improvements (#829)
* Search modal performance improvements

* Move more code out of the search modal

* Add the question helper to the search

* Fix a couple lint errors

* Fix the tests, duplicate import text

* Hide the token info on small screens, flex the list

* Fix token sorting, have a link that focuses and shows a tooltip for the search input

* Remove reach tooltip css

* Fix pair balances in the search modal

* Get the arrow working

* Only clear the input when they re-open

* Better way to exclude props

* More small performance tweaks
2020-05-23 12:06:16 -04:00
Moody Salem
63af1a160d Fix lint errors 2020-05-22 09:43:11 -04:00
Moody Salem
85d52b3480 perf(search modal): refactor before more dramatic changes 2020-05-22 09:36:09 -04:00
Moody Salem
219de1f471 fix(release): remove console.log statement 2020-05-22 08:24:16 -04:00
Moody Salem
f110fa7732 chore(release): update DNS on release 2020-05-22 08:08:11 -04:00
Ian Lapham
513a1b0c4b add default text on create flow (#825) 2020-05-21 19:16:42 -04:00
Moody Salem
96c9eede18 chore(release): automatically publish daily releases 2020-05-21 15:55:12 -04:00
Moody Salem
f4a97501e5 chore(readme): update the README.md 2020-05-21 15:54:28 -04:00
Moody Salem
2ff5ce62db chore(package.json): every dependency is a dev dependency, clean up generated release description 2020-05-21 15:47:52 -04:00
Moody Salem
79176dfe79 chore(release): Change the release schedule to daily 2020-05-21 15:38:30 -04:00
Moody Salem
4880e0fb02 chore(release): Deploy v2 to IPFS
BREAKING CHANGE: Trigger a v2.0.0 tag
2020-05-21 15:36:56 -04:00
Moody Salem
ceea563af5 Try again, fix upload hash output variable, temporarily create release on every push 2020-05-21 14:30:31 -04:00
Moody Salem
992c5a8e54 Try without the if statement 2020-05-21 14:20:09 -04:00
Moody Salem
850e9bb2bd Try a different syntax 2020-05-21 14:12:49 -04:00
Moody Salem
e5bf2d0823 Pin to pinata instead 2020-05-21 14:08:34 -04:00
Moody Salem
2708d26495 Cancel release job if no new commits 2020-05-21 14:01:43 -04:00
Moody Salem
ccab6f01cf More tests for add/remove liquidity and fix the automatic loading of tokens in those pages 2020-05-20 23:11:49 -05:00
Moody Salem
384c3cada4 Add integration test, change ipfs deploy into daily release workflow 2020-05-20 22:31:05 -05:00
Moody Salem
c94e8ff6e5 Remove the WETH token from the user added tokens 2020-05-20 21:22:57 -05:00
Moody Salem
04584cc4ac Fix the add liquidity link from uniswap.info 2020-05-20 21:09:36 -05:00
Moody Salem
7d20dd976b Fix a crash related to WETH being loaded from add liquidity 2020-05-20 20:51:25 -05:00
Moody Salem
2b4f511b55 Bump release action to a version that has the body parameter, and add logic to whether it's a prerelease 2020-05-20 18:58:19 -05:00
Moody Salem
7d379b796c yaml error 2020-05-20 18:45:58 -05:00
Moody Salem
4e8777c728 Create a draft release with the IPFS URLs in the body 2020-05-20 18:45:04 -05:00
Moody Salem
9916c84e5d Fix i18n loading, manifest.json loading 2020-05-20 18:39:16 -05:00
Moody Salem
462f2482e9 Fix the shifting between send/swap tabs 2020-05-20 16:16:32 -05:00
Moody Salem
2ff8f5a7ff Master instead of v1 2020-05-20 16:11:56 -05:00
Moody Salem
0b8d05b696 Add a new target for deploying to IPFS (#819)
* Add a new target for deploying to IPFS

* verbose command

* Try the github action
2020-05-20 16:10:26 -05:00
Noah Zinsmeister
425a23774d bump v1 trade threshold 2020-05-20 16:18:18 -04:00
Noah Zinsmeister
fea11871ae add USDT 2020-05-20 16:17:59 -04:00
Moody Salem
b616c7b50d Default box-sizing to border-box everywhere 2020-05-20 14:59:12 -05:00
Moody Salem
9ac1bcf008 Make the token warning card not huge 2020-05-20 14:43:16 -05:00
Moody Salem
1bb4db5414 Make the hooks directory strict (#817)
* Make the hooks directory strict

* Move using the v1 factory contract to hooks

* Add a comment to why we copy the token map

* Fix another lint error and make AllTokens deep readonly

* Move ALL_TOKENS to constants directory
2020-05-20 14:39:28 -05:00
Noah Zinsmeister
536de89a38 replace commas with periods (#815)
* replace commas with periods

* add pattern attribute
2020-05-20 15:00:47 -04:00
Moody Salem
199eb8bf50 Refactor pages directory (#809)
* Revert "Temporarily disable the token warning for style fixes"

This reverts commit 70722b5e

* Move pages around and refactor to better support rendering content outside the app body

* Automatically add tokens in the add and remove liquidity pages

* Put the warning above the send/swap pages

* Add a margin below the token warning cards

* Save token warning dismissal state in user state

* Linting errors

* style fix
2020-05-20 14:00:33 -05:00
Ian Lapham
f1c6119f8b change results height on search modal (#816) 2020-05-20 14:30:56 -04:00
Moody Salem
a10b22a382 - Remove the environment variables from the netlify.toml in favor of create-react-app env configuration
- Remove the polling interval override in the index.tsx which is redundant with the connectors file overrides
2020-05-20 12:12:06 -05:00
Noah Zinsmeister
206e845731 add route visualization (#808)
* add route visualization

* add border, only show if >2 tokens in the path

* fix flex styles

* more flex tweaks
2020-05-20 12:23:04 -04:00
Moody Salem
1e7072d5f2 Fix swap + send not going to recipient (#810) 2020-05-19 18:28:26 -05:00
Moody Salem
70722b5e30 Temporarily disable the token warning for style fixes 2020-05-19 16:19:35 -05:00
Moody Salem
cba76c43f6 Token warning a la #789 and #790 (#807)
* Token warning a la #789 and #790

* Clean up the duplicate name or symbol implementation

* Fix the spacing

* Change wording and add to send page

* Wording changes

* Refactor the common code to a component

* Fix lint error
2020-05-19 14:18:24 -05:00
Noah Zinsmeister
373b3180d3 manual approve fallback for remove liquidity (#806)
* generalize useApproveCallback

* show proper slippageAdjustedAmounts in dialog

* make confirmed button text readable in light mode

* default timeout from 15 -> 20 minutes

* add manual approve fallback to remove liquidity

* remove console log

* fix bad web3-react import introduced by refactor

* rename our wrapped version of useWeb3React

* return an enum from useApproveCallback

* fix comment
2020-05-19 15:08:58 -04:00
Moody Salem
c35d8fc702 Quick fix to #800 2020-05-19 09:50:31 -05:00
Moody Salem
e6a043cdc5 Fix #798 2020-05-19 09:34:24 -05:00
Moody Salem
5afddb2ede Reduce the spam of redux events 2020-05-18 23:56:32 -05:00
Moody Salem
3b2c21e26d Fix issue where tokens did not disappear after removed 2020-05-18 22:46:06 -05:00
Moody Salem
0e72f1c5d3 Remove unnecessary memoization that causes a caching issue 2020-05-18 22:40:46 -05:00
Moody Salem
81b063351d Properly label the pure send transactions 2020-05-18 22:06:45 -05:00
Moody Salem
bfb1992e45 Add strict to data folder 2020-05-18 21:22:09 -05:00
Moody Salem
690a121218 Strict typescript (#792)
* Make typescript compilation strict in some new code

* Fix other errors, change how we set dark mode to always respond to url parameter

* Fix bug in block number
2020-05-18 18:23:58 -05:00
Noah Zinsmeister
3fe0a5a9b0 catch possible errors when constructing v1 route (#794) 2020-05-18 19:10:34 -04:00
Moody Salem
f0e1e92178 Move trade price formatting to its own component 2020-05-18 15:03:34 -05:00
Ian Lapham
fcb012cb1a set token length (#786)
* set token length

* safe check on symbol reference
2020-05-18 15:06:54 -04:00
Moody Salem
1aea452f3b Add a test and improve our redirects 2020-05-18 13:47:52 -05:00
Noah Zinsmeister
e311e2fcf9 mainnet (#782)
* mainnet config

* fix token + pair sorting

* standardize wrapped useWeb3React imports

* add final return to pair sorting

* add connecting pairs

* break tokens out into separate files

revert isAddress change

* filter out duplicate pairs

* remove add liquidity prompts

* link to v1 trades that are invalid on v2

* forward v2 subdomain to apex

* update blog link

* get rid of smart quotes

* link to uniswap info in position card

* improve pair sorting/searching

break out token identification

fix crash on early pool position clicks

change pair token separator from : to /
2020-05-18 13:20:42 -04:00
Moody Salem
9c4f63f444 Match behavior query params (#785)
* Match the behavior of v1 interface for old query parameters in swap/send

* Handle matching input/output currencies better

* Another test
2020-05-18 11:28:51 -05:00
Moody Salem
f261675602 Remove extra console logging statement! 2020-05-16 20:56:57 -05:00
Moody Salem
67df4959fb Fix balance forever loading issue, ensure that all balances in the store are keyed by checksummed addresses 2020-05-16 17:23:27 -05:00
Moody Salem
095beae0c2 Refactor ExchangePage into Swap and Send pages (#774)
* Part 1, separate swap and send, move duplicate code into separate files

* Move some more constants out of the swap/send

* Support defaults from URL parameters

* Implement query string parsing via a redux action

* Finish merging the changes

* Fix the slippage warnings

* Clean up some other files

* More refactoring

* Move the price bar out of the component

* Move advanced details and some computations into utilities

* Make same improvements to send

* Approval hook

* Swap page functional with swap callback hook

* Swap/send page functional with swap callback hook

* Fix lint

* Move styleds.ts

* Fix integration test

* Fix error state in swap and move some things around

* Move send callback out of send page

* Make send behave more like current behavior

* Differentiate swap w/o send on send page from swap w/o send on swap page

* Remove userAdvanced which was always false

* Remove the price bar which is not used in the new UI

* Delete the file

* Fix null in the send dialog and move another component out of send

* Move the swap modal header out to another file

* Include change after merge

* Add recipient to swap message

* Keep input token selected if user has no balance and clicks send with swap

* Move the modal footer out of send component

* Remove the hard coded estimated time

* Fix the label/action for swap/sends

* Share the swap modal footer between swap and send

* Fix integration test

* remove margin from popper to suppress warnings

fix missing ENS name recipient link

default deadline to 15 minutes

* ensure useApproveCallback returns accurate data

* clean up callbacks

* extra space

* Re-apply ignored changes from v2 branch ExchangePage file

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2020-05-16 16:55:22 -05:00
Moody Salem
51e929bd1e Clear all transactions (#778) 2020-05-16 16:23:46 -04:00
Ian Lapham
af892c1fec copy updates (#779) 2020-05-16 14:50:03 -04:00
Moody Salem
1380f865a9 Place tooltips better on smaller screens 2020-05-16 10:44:26 -04:00
Moody Salem
487b5ceccc Don't allow the transaction status icon shrink 2020-05-15 15:55:38 -04:00
Ian Lapham
6c9eeb4691 fixes from bug hunting (#773)
* fix small bugs , token icons

* remove mainnet

* style changes

* fix nav pointer issues on desktop

* more style changes

* remove mainnet
2020-05-15 15:17:19 -04:00
Moody Salem
a34dc73c38 Don't always redirect for links 2020-05-15 10:12:43 -04:00
Moody Salem
07fee04526 Lint error 2020-05-14 23:15:57 -04:00
Moody Salem
5a9b7a382e Fix bug with balance listeners not being reattached 2020-05-14 23:15:28 -04:00
Noah Zinsmeister
7bffea0692 update swr hooks when block number changes (#771)
* key per-block SWR data with block number

* fix + tweak copy and decrease sig figs for lp fee

* don't use blocknumber as a key

instead mutate when it changes

* fix totalsupply + user liquidity sync issues

* fix v1 trade checker logic

* rough fix allowing removal of user added tokens

probably needs a more comprehensive overhaul...

* refactor SWR block updates to custom hook

* typo

* fix import error

* fix footer css to work cross-broswer

* disallow \ to be typing into amount inputs

add test case for this

add value assertions to all input integration tests

* fix import error

* remove console.log

* clean up address input a bit

trim whitespace from pasted addresses

* fix input maxing

remove spurious max output logic
2020-05-14 17:12:59 -04:00
Moody Salem
b1ffab1890 Refactors (#772)
* Bunch of refactoring

* Add an integration test that gets as far as swapping

* Drop the nonce code that isn't doing anything right now

* Undo one of the accidental changes to the reducer
2020-05-14 15:56:40 -04:00
Moody Salem
60582de4d6 Use the new v2 GA ID 2020-05-14 14:13:16 -04:00
Moody Salem
4ef3746a09 Add GA instrumentation to v2 (#769) 2020-05-14 13:18:08 -04:00
Moody Salem
d2462af821 Address flashy first loading behavior 2020-05-14 12:57:19 -04:00
Moody Salem
5405648a54 Improve the transaction render style (#767) 2020-05-14 10:32:48 -04:00
Noah Zinsmeister
4ba7dd9535 fix invariant bug due to dummy ETH (#766)
* fix invariant bug due to dummy ETH

* fix lint issue
2020-05-13 20:25:07 -04:00
Moody Salem
0733cdd63c Fix trade routing to not throw 2020-05-13 19:57:12 -04:00
Noah Zinsmeister
8a61b4e4ad support browsers which use old match API (safari) (#765) 2020-05-13 19:22:36 -04:00
Noah Zinsmeister
e0cbee79a6 fix bug for single-hop fees (#764)
fix incorrect feeTimesInputFormatted logic
2020-05-13 18:43:17 -04:00
Noah Zinsmeister
141e605c17 v2 slippage fix (#763)
* appropriately discount for multi-hop fee slippage

* fix comment

* make calculation more explicit with a ternary
2020-05-13 17:58:22 -04:00
Moody Salem
3a426fa36b Fix token balance fetching in the position card 2020-05-13 16:55:14 -04:00
Moody Salem
52d40949a6 Missed a spot 2020-05-13 16:45:08 -04:00
Moody Salem
685bb1745a Improve rendering of failed transactions 2020-05-13 16:42:47 -04:00
Moody Salem
9006acb4c5 Balances rewrite (#761)
* Create the wallet store

* Get the updater completed

* Code complete

* Fix token balance bug

* Fix another bug in the hooks

* Final bug fix, blockNumber can be undefined

* Formalize the fact that block number can be undefined

* Woops add package

* Add more info to errors

* Replace balances in the v1 methods with the new ones

* Only return a balance value if it's present

* Address comments

* Trigger updateVersion before anything else
2020-05-13 16:36:45 -04:00
Moody Salem
1e1a049990 Add more unit tests to utils 2020-05-13 14:24:03 -04:00
Ian Lapham
3a2566b4e7 Changes to useReserves, escapreRegex moved to utils, code cleanup (#762)
* bug fixes on non-existent vs no reserves, fix scanning on pair search, small ui tweaks

* remove unused vars

* remove mainnet in injected connector

* code cleanup for pr

* update pair value check
2020-05-13 14:16:52 -04:00
Moody Salem
c9db5fb276 Change slippage calculation in trade page as discussed (#756)
* Change slippage calculation in trade page as discussed

* Revert test change

* Fix the missing import
2020-05-13 13:59:54 -04:00
Moody Salem
1cbf67872b Remove pair that is no longer used from the exchange page 2020-05-13 13:37:06 -04:00
Moody Salem
38947fe760 Remove trade errors about insufficient liquidity that only show up if there's a route (best trade will not return a trade if there is insufficient liquidity) 2020-05-13 13:24:55 -04:00
Moody Salem
31f72a541b Clean up transaction modal behavior with new store 2020-05-13 12:58:20 -04:00
Moody Salem
287f64cf5c Fix a redux error from passing a BigNumber to the store 2020-05-13 10:59:51 -04:00
Moody Salem
822f9e5fe2 Connect integration tests (#757)
* Connect a testing wallet

* Replace with a funded address

* Fix the balance checking and adding logging
2020-05-13 08:29:54 -04:00
Moody Salem
6781fce58e Fix linting error again! 2020-05-12 18:17:26 -04:00
Moody Salem
4398fef38f Use yarn cache plugin to speed up GitHub test actions 2020-05-12 18:14:39 -04:00
Moody Salem
ef65943659 Rewrite tx store (#754)
* Rewrite the transaction store

* Working state

* Fix lint errors

* Just always call getSigner
2020-05-12 18:11:10 -04:00
Moody Salem
19a53cd999 Rewrite localstorage context (#749)
* Rewrite the local storage context into a redux store for user data

* Separate out the mega methods

* Fix infinite loop

* Missing dependency

* Missing dependency, rename version field
2020-05-12 14:46:58 -04:00
Noah Zinsmeister
b28ad2865d add v1 slippage checks (only for mainnet) (#746)
* add v1 slippage checks (only for mainnet)

* Add v1 trade notification UI

* Design tweaks

* add minimum delta

* remove dark theme toggle from menu

* only render view on etherscan if an address exists

* fix weird spacing on logged-in header

* lint error

* remove mainnet

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
2020-05-12 12:20:05 -04:00
Ian Lapham
0d4108937f Ui changes (#747)
* copy changes

* remove unused input on toggle

* comparison fix
2020-05-12 11:48:42 -04:00
Moody Salem
6da8e2c84d Introduce redux for one of the state stores (#742)
* Introduce redux for one of the state stores

* Remove unused state

* Clean up hooks

* Add types for react-redux and fix error from any type on useSelector

* Strongly type the web3 provider

* Make the popup content into a POJO

* Lint errors

* Clean up method call

* Fix lint error

* Fix lint error

* Lint
2020-05-11 18:23:01 -04:00
Moody Salem
ea015d16f2 Stop reloading fonts when light mode/dark mode switch 2020-05-11 17:02:10 -04:00
Noah Zinsmeister
d40d81ca6a noah changes (#739)
* remove duplicated prettier from dependencies

* use swr for allowances

* slight improvements to error cascading

* fetch totalsupply with SWR

fetch pairs with SWR (except persistent pairs, will be refactored later)

* fix lint error

* stop using pair context, instead store per-user pairs in local storage

* add more pairs to the default list

remove unnecessary isWETH

fix lint error

* add more bases to trade consideration

* fix lint warnings

* get SWR data on same schedule

* don't pass value if it's 0

fix WETH equality checks

* clean up fixed/significant formatting

* fix slippage calculation bug in swap

add slippage to remove

* fix token logos

* show fewer sig figs

* lint error

* remove unused signer

* add calculateSlippageAmount safety check and tests

* fix useAllBalances type

stop spuriously sorting token amounts of different tokens

use slightly better comparisons for slippage calculations

* improve priceSlippage comparison logic

* useReserves -> usePair

* use checksum for DAI logo address
2020-05-11 16:39:05 -04:00
Callil Capuozzo
dba6abadf5 UI/UX tweaks (#740)
* Prepare for V2 testing

* Bug fixes and tweaks

* Add form link

* Design tweaks

* higher quality favicon

* Favicon... again.

* Start dark mode

* Add dark toggle

* New dark theme unicorn

* fix connect wallet ui

* Titles for fields && small UI/UX tweaks

* Fix ts linter errors

* UI/UX tweaks

* rebass and change color names

* v1/v2 toggle and color tweaks

* Small header tweaks

* fix account input mobile padding

* remove unused imports

* Fix header link

* Theme toggle for mobile & fix

* no acount? hide maxes

* Use system theme unless toggled

* Remove unused styles

* Fix missing hooks dependencies

* Fix linting errors

* improve system dark mode listener

* add listener

* fix hook dependency

Co-authored-by: Ian Lapham <ianlapham@gmail.com>
Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2020-05-11 15:12:38 -04:00
Moody Salem
4131268b8e Icons (#744)
* Manifest icons

* Add touch icons
2020-05-11 14:02:49 -04:00
Moody Salem
c365a5ec33 Add some integration tests (#743) 2020-05-11 09:03:44 -04:00
Moody Salem
ee92df1554 Handle insufficient reserves/input errors without crashing 2020-05-09 12:41:21 -04:00
Moody Salem
f1ccd7a166 Fix horizontal scrolling on small screens & add some small padding 2020-05-09 10:55:41 -04:00
Ian Lapham
7c43023860 fix DOM warnings (#741)
* fix DOM warnings

* Update index.tsx

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2020-05-08 23:58:40 -04:00
Moody Salem
843d5e790a Done with linter errors! 2020-05-08 18:32:36 -04:00
Ian Lapham
40a7cea6f6 Bug fixes, UI tweaks (#738)
* linking bug fixes, search UI tweaks

* search style tweaks

* organize imports
2020-05-08 17:22:19 -04:00
Moody Salem
e023a02037 Use eslint/prettier for typescript files (#737)
* Use eslint/prettier for typescript files

* Fix more linting errors

* Turn linting errors off so CI passes
2020-05-08 16:15:45 -04:00
Moody Salem
7add2a916c remove a couple more state constants 2020-05-08 16:13:08 -04:00
Moody Salem
5f36437c3f Turn linting errors off so CI passes 2020-05-08 15:57:51 -04:00
Moody Salem
152e84bc25 Fix more linting errors 2020-05-08 15:46:42 -04:00
Moody Salem
3b3f281319 Use eslint/prettier for typescript files 2020-05-08 14:50:27 -04:00
Moody Salem
17eceebcf5 Final typescript migration push 2020-05-08 14:15:47 -04:00
Moody Salem
6112c8a2a2 a word 2020-05-08 13:51:47 -04:00
Moody Salem
2adb0a3dfb Ts almost everywhere (#736)
* First 2

* More typescript

* More and more

* Almost there

* More

* More typescript

* Fix remove liquidity button

* Make the error chain more readable

* Slippage tabs
2020-05-08 13:48:05 -04:00
Moody Salem
86fa969d6b Types for react-router-dom, stop lazy loading each route (#735)
* Types for react-router-dom, stop lazy loading each route

* Couple small fixes to README.md
2020-05-08 12:05:52 -04:00
Moody Salem
28b24036c6 Cypress (#734)
* Add some basic integration tests and CI in GitHub

* Add push trigger

* Add badge for tests to readme

* Unit tests

* Just use the development infura URL in the tests

* Remove unused webpack config

* Make integration test run on the same port as yarn start

* make test a little bit better

* rename describe
2020-05-08 11:38:33 -04:00
Moody Salem
a1a9d9f041 Type safety for styled components (#733) 2020-05-08 11:24:47 -04:00
Moody Salem
9b2fe0bdca Use best trade to get the best route (#731) 2020-05-08 11:13:16 -04:00
Noah Zinsmeister
cb00e0baa5 migrate full away from ethers v4 to v5 (#728)
move some pacakges from dependencies to dev dependencies

bump versions
2020-05-07 19:53:08 -04:00
Moody Salem
4ccbd0c3ba Fix the crash by importing text 2020-05-07 19:50:12 -04:00
Moody Salem
7fcecc22d9 TS migrations and commit hash in code link (#730)
* TypeScript in the utils directory, README fixes

* Remove unused methods

* Added unnecessary space

* Some env variable cleanup

* Bug in change

* Another file to ts

* App.tsx

* Header and Footer to tsx

* Few more TS migrations and add a commit hash title to the uni icon

* Few more TS migrations and put commit hash in code link

* Splitting up files

* Splitting up files (merged dark mode changes)
2020-05-07 18:52:26 -04:00
Ian Lapham
404d7a60e5 bug fixes, style updates dark mode (#729)
* Prepare for V2 testing

* Bug fixes and tweaks

* Add form link

* Design tweaks

* higher quality favicon

* Favicon... again.

* Start dark mode

* Add dark toggle

* New dark theme unicorn

* header style changes

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
2020-05-07 16:46:45 -04:00
Moody Salem
b6b094acb3 remove unused functions, typescript (#725)
* TypeScript in the utils directory, README fixes

* Remove unused methods

* Added unnecessary space

* Some env variable cleanup

* Bug in change

* Another file to ts
2020-05-07 14:36:26 -04:00
Ian Lapham
49462fdb3b bug fixes (#727) 2020-05-07 14:01:34 -04:00
Ian Lapham
3c112b5746 bug fixes (#722) 2020-05-06 22:16:00 -04:00
Ian Lapham
e5c5bad7ab reset WETH on network change (#720)
* reset WETH on network change

* cleaner version

* undo typo

* fix indexing
2020-05-06 19:03:13 -04:00
Ian Lapham
9eee45a8f1 copy updates (#719) 2020-05-06 17:34:53 -04:00
Callil Capuozzo
e5ced44d75 Design tweaks (#717)
* Prepare for V2 testing

* Bug fixes and tweaks

* Add form link

* Design tweaks

* higher quality favicon

* Favicon... again.

Co-authored-by: Ian Lapham <ianlapham@gmail.com>
2020-05-06 15:34:06 -04:00
Ian Lapham
cdbf440e9c add slider, big fixes (#715) 2020-05-05 23:20:31 -04:00
Ian Lapham
5426e48872 popup styles, slippage language and amounts, min max copy (#714)
* Prepare for V2 testing

* popup styles, slippage language and amounts, min max copy

* Bug fixes and tweaks

* Add form link

* remove other imports

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
2020-05-05 20:40:59 -04:00
Noah Zinsmeister
64dd09f2cc update to latest deploy (#712) 2020-05-05 16:36:22 -04:00
Ian Lapham
6fd9808e7d Style updates merge with storage updates (#710)
* Add advanced user toggle

* UX stuff

* Remove unused imported checks

* Design tweaks and selection card

* remove unused imports

* Cleanup

* fix merge

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
2020-05-04 20:21:55 -04:00
Noah Zinsmeister
c3d8bc7ed5 Localstorage and routing improvements (#704)
* remove tokens context in favor of localstorage

refactor localstorage

improve adding custom token flow

* drop exchange language

* ensure url tokens are added to localstorage

clean up routing

remove unnecessary output approval checks

* fix bad import

* Remove unused imported checks

* remove unused imports

Co-authored-by: Ian Lapham <ianlapham@gmail.com>
2020-05-04 17:56:10 -04:00
Ian Lapham
3f1d7ab310 add basic structure for advanced mode (#703)
* add basic structure for advanced mode

* auto dismiss popup

* Remove test code

* remove shadow

* Update advanced section, ui tweaks, balances back inline

* fix memory leak

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
2020-04-30 21:14:29 -04:00
Ian Lapham
60d7bc4532 bug fix on local storage balance reference (#702) 2020-04-29 14:26:35 -04:00
Ian Lapham
76dbc9fa18 Multiple UI changes (#700)
* small layout and text changes, bug fixes

* UI changes, add summary to txns

* Remove updates, small changes on send, common bases
2020-04-28 18:46:55 -04:00
Ian Lapham
006fe9b325 Updated context keys, remove logs (#680)
* updated keys in pair context

* fix bug on imported liquidity

* Add price bar and spacing tweaks

* Header tweaks

* Base for token import, query params, styling updates

* remove unused imports

* query params, create flow

* set ETH as default for create

* Style tweaks and price bar location

* add mobile popups, mobile styling on header

* updated mobile styles

* style updates mobile, fix build

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
2020-04-22 17:22:13 -04:00
Ian Lapham
50c9d9973a UI Updates, Route Context Updates (#679)
* UI updates, big fixes on route hook

* fix bug on all pair list

* logout fix and  add mainnet WETH
2020-04-15 20:56:59 -04:00
Ian Lapham
af6add09a0 Radio button edits, theme updates (#668) 2020-03-30 17:07:12 -04:00
Ian Lapham
cf5c67ec88 fix rerender bugs (#666) 2020-03-26 12:58:45 -04:00
Ian Lapham
eaf8d5a39c Add routes through ETH (#665)
* add fix for ETH balance fetching

* add tokens to other testnets

* update with Exchange -> Pair

* routes throuh ETH

* fix logs

* fix conflict
2020-03-26 12:42:06 -04:00
Ian Lapham
02dedcbc6e Update message for testnet, bug fixes (#663) 2020-03-24 16:46:56 -04:00
Ian Lapham
d8c4ebc243 add fix for ETH balance fetching (#662) 2020-03-23 16:01:26 -04:00
Ian Lapham
2b15979e93 Hide dark and advanced, fix bug on empty balances (#660)
* Hide dark and advanced, fix bug on empty balances

* remove unused imports

* change default dark mode

* add warning on main net

* remove unused imports
2020-03-23 15:20:03 -04:00
ianlapham
6211dff044 typed pages 2020-03-20 16:03:23 -04:00
ianlapham
655b79569b basic send added 2020-03-17 16:23:27 -04:00
ianlapham
4e2c5c1e84 slippage without micro warnings on swap 2020-03-13 16:26:29 -04:00
ianlapham
e43d9e03f1 add popups to context 2020-03-11 10:44:39 -04:00
ianlapham
35d398406d add import to token search 2020-03-10 15:55:31 -04:00
ianlapham
217a700832 error warnings on remove and pool finder added 2020-03-10 14:55:33 -04:00
ianlapham
55eb03c237 remove version with derived percent 2020-03-09 17:20:41 -04:00
ianlapham
a6d8613bdd updated position cards 2020-03-06 14:45:11 -05:00
ianlapham
2ca3bf8da3 add slider to remove 2020-03-05 22:57:41 -05:00
ianlapham
236c3030e1 stable state for swaps, add, and remove eth (no remove token token yet) 2020-03-04 13:48:43 -05:00
ianlapham
753e5f3423 stable version with updated balances, add liquidity using SDK, pair menu search 2020-02-26 12:00:59 -05:00
ianlapham
3091ebc158 update comments 2020-02-13 11:31:12 -05:00
ianlapham
912a19d66d fix imports 2020-02-13 11:04:17 -05:00
ianlapham
4890ad1172 update logic for txn eth value on GA 2020-02-13 00:12:58 -05:00
371 changed files with 34801 additions and 15308 deletions

2
.env Normal file
View File

@@ -0,0 +1,2 @@
REACT_APP_CHAIN_ID="1"
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"

View File

@@ -1,5 +0,0 @@
REACT_APP_CHAIN_ID="1"
REACT_APP_NETWORK_URL=""
REACT_APP_PORTIS_ID=""
REACT_APP_FORTMATIC_KEY=""
REACT_APP_IS_PRODUCTION_DEPLOY="false"

5
.env.production Normal file
View File

@@ -0,0 +1,5 @@
REACT_APP_CHAIN_ID="1"
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/099fc58e0de9451d80b18d7c74caa7c1"
REACT_APP_PORTIS_ID="c0e2bf01-4b08-4fd5-ac7b-8e26b58cd236"
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
REACT_APP_GOOGLE_ANALYTICS_ID="UA-128182339-4"

31
.eslintrc.json Normal file
View File

@@ -0,0 +1,31 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"ecmaFeatures": {
// Allows for the parsing of JSX
"jsx": true
}
},
"ignorePatterns": [
"node_modules/**/*"
],
"settings": {
"react": {
"version": "detect"
}
},
"extends": [
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"prettier/@typescript-eslint",
"plugin:prettier/recommended"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"prettier/prettier": "error",
"@typescript-eslint/no-explicit-any": "off"
}
}

View File

@@ -1,14 +1,13 @@
---
name: Bug Report
about: Create a report to help us improve
about: Describe an issue in the Uniswap Interface
title: ''
labels: bug
assignees: ''
---
**Bug Description**
A clear and concise description of what the bug is.
A clear and concise description of the bug.
**Steps to Reproduce**
1. Go to ...

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Support
url: https://discord.gg/FCfyBSbCU5
about: Please ask and answer questions here
- name: List a token
url: https://github.com/Uniswap/default-token-list#adding-a-token
about: Any requests to add a token to Uniswap should go here

View File

@@ -1,10 +1,9 @@
---
name: Feature Request
about: Suggest an idea for this project
about: Suggest an idea for improving the UX of the Uniswap Interface
title: ''
labels: ''
labels: 'improvement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**

View File

@@ -1,10 +0,0 @@
---
name: Something Else
about: Tell us something else
title: ''
labels: ''
assignees: ''
---

View File

@@ -1,27 +0,0 @@
---
name: Token Request
about: Request a token addition
title: ''
labels: token request
assignees: ''
---
**Please provide the following information for your token.**
Token Address:
Token Name (from contract):
Token Decimals (from contract):
Token Symbol (from contract):
Uniswap Exchange Address of Token:
Link to the official homepage of token:
Link to CoinMarketCap or CoinGecko page of token:
Some tokens (e.g. BNB) do not work with Uniswap v1. In order to assess if your token works correctly, please complete small-value transactions of each of the types below, and submit the Etherscan transaction links for our review.
Test `addLiquidity` transaction:
Test `swap` transaction:
Test `removeLiquidity` transaction:
Are you willing to add liquidity to the liquidity pool for this token? (Y/N):
If so, how much liquidity are you willing to add?:

View File

@@ -1,3 +0,0 @@
**PLEASE DO NOT SUBMIT TOKEN ADDITIONS AS PULL REQUESTS**
All token requests should be made via an issue.

48
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Lint
on:
push:
branches:
- main
pull_request_target:
branches:
- main
jobs:
run-linters:
name: Run linters
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Set up node
uses: actions/setup-node@v1
with:
node-version: 12
always-auth: true
registry-url: https://registry.npmjs.org
- name: Set output of cache
id: yarn-cache
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Node dependency cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache.outputs.dir }}
key: yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
yarn-
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run linters
uses: wearerequired/lint-action@77d70b9a07ecb93bc98dc46dc27d96c4f004d035
with:
github_token: ${{ secrets.github_token }}
eslint: true
eslint_extensions: js,jsx,ts,tsx,json
auto_fix: true

View File

@@ -1,52 +1,104 @@
name: Release
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*'
name: Build and Release
schedule:
- cron: '0 12 * * 1-4' # every day 12:00 UTC Monday-Thursday
# manual trigger
workflow_dispatch:
jobs:
build:
name: Global Job
strategy:
matrix:
node: ['10.x']
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
bump_version:
name: Bump Version
runs-on: ubuntu-latest
outputs:
new_tag: ${{ steps.github_tag_action.outputs.new_tag }}
changelog: ${{ steps.github_tag_action.outputs.changelog }}
steps:
- name: Checkout code
uses: actions/checkout@master
- name: Create production build
uses: actions/setup-node@master
- name: Checkout
uses: actions/checkout@v1
- name: Bump version and push tag
id: github_tag_action
uses: mathieudutour/github-tag-action@v4.5
with:
node-version: ${{ matrix.node }}
- run: npm install -g yarn
- run: yarn
- run: yarn build
env:
REACT_APP_NETWORK_ID: ${{ secrets.REACT_APP_NETWORK_ID }}
- name: Create Release
id: create_release
uses: actions/create-release@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
release_branches: .*
create_release:
name: Create Release
runs-on: ubuntu-latest
needs: bump_version
if: ${{ needs.bump_version.outputs.new_tag != null }}
steps:
- name: Checkout
uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
- name: Zip the build
uses: thedoctor0/zip-release@master
node-version: '12'
always-auth: true
registry-url: https://registry.npmjs.org
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build the IPFS bundle
run: yarn build
- name: Pin to IPFS
id: upload
uses: anantaramdas/ipfs-pinata-deploy-action@39bbda1ce1fe24c69c6f57861b8038278d53688d
with:
filename: 'build.zip'
pin-name: Uniswap ${{ needs.bump_version.outputs.new_tag }}
path: './build'
exclusions: 'x'
- name: Upload Build
id: build-asset
uses: actions/upload-release-asset@v1.0.1
pinata-api-key: ${{ secrets.PINATA_API_KEY }}
pinata-secret-api-key: ${{ secrets.PINATA_API_SECRET_KEY }}
- name: Convert CIDv0 to CIDv1
id: convert_cidv0
uses: uniswap/convert-cidv0-cidv1@v1.0.0
with:
cidv0: ${{ steps.upload.outputs.hash }}
- name: Update DNS with new IPFS hash
env:
CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}
RECORD_DOMAIN: 'uniswap.org'
RECORD_NAME: '_dnslink.app'
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
uses: textileio/cloudflare-update-dnslink@0fe7b7a1ffc865db3a4da9773f0f987447ad5848
with:
cid: ${{ steps.upload.outputs.hash }}
- name: Create GitHub Release
id: create_release
uses: actions/create-release@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: build.zip
asset_name: build.zip
asset_content_type: application/zip
tag_name: ${{ needs.bump_version.outputs.new_tag }}
release_name: Release ${{ needs.bump_version.outputs.new_tag }}
body: |
IPFS hash of the deployment:
- CIDv0: `${{ steps.upload.outputs.hash }}`
- CIDv1: `${{ steps.convert_cidv0.outputs.cidv1 }}`
The latest release is always accessible via our alias to the Cloudflare IPFS gateway at [app.uniswap.org](https://app.uniswap.org).
You can also access the Uniswap Interface directly from an IPFS gateway.
The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to store your settings.
**Beware** that other sites you access via the _same_ IPFS gateway can read and modify your settings on the Uniswap interface without your permission.
You can avoid this issue by using a subdomain IPFS gateway, or our alias to the latest release at [app.uniswap.org](https://app.uniswap.org).
The preferred URLs below are safe to use to access this specific release.
Preferred URLs:
- https://${{ steps.convert_cidv0.outputs.cidv1 }}.ipfs.dweb.link/
- https://${{ steps.convert_cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
- [ipfs://${{ steps.upload.outputs.hash }}/](ipfs://${{ steps.upload.outputs.hash }}/)
Other IPFS gateways:
- https://cloudflare-ipfs.com/ipfs/${{ steps.upload.outputs.hash }}/
- https://ipfs.infura.io/ipfs/${{ steps.upload.outputs.hash }}/
- https://ipfs.io/ipfs/${{ steps.upload.outputs.hash }}/
${{ needs.bump_version.outputs.changelog }}

65
.github/workflows/tests.yaml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: Tests
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
integration-tests:
name: Integration tests
runs-on: ubuntu-16.04
steps:
- name: Checkout
uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: '12'
always-auth: true
registry-url: https://registry.npmjs.org
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- 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"
- run: yarn integration-test
unit-tests:
name: Unit tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: '12'
always-auth: true
registry-url: https://registry.npmjs.org
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --frozen-lockfile
- run: yarn test

7
.gitignore vendored
View File

@@ -11,7 +11,6 @@
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
@@ -28,4 +27,8 @@ notes.txt
.vscode/
package-lock.json
package-lock.json
cypress/videos
cypress/screenshots
cypress/fixtures/example.json

View File

@@ -1,13 +0,0 @@
branches:
except:
- master
language: node_js
node_js:
- '10'
cache:
directories:
- node_modules
install: yarn
script:
- yarn check:all
- yarn build

1
.yarnrc Normal file
View File

@@ -0,0 +1 @@
ignore-scripts true

View File

@@ -1,28 +1,33 @@
# Uniswap Frontend
# Uniswap Interface
[![Netlify Status](https://api.netlify.com/api/v1/badges/fa110555-b3c7-4eeb-b840-88a835009c62/deploy-status)](https://app.netlify.com/sites/uniswap/deploys)
[![Build Status](https://travis-ci.org/Uniswap/uniswap-frontend.svg)](https://travis-ci.org/Uniswap/uniswap-frontend)
[![Lint](https://github.com/Uniswap/uniswap-interface/workflows/Lint/badge.svg)](https://github.com/Uniswap/uniswap-interface/actions?query=workflow%3ALint)
[![Tests](https://github.com/Uniswap/uniswap-interface/workflows/Tests/badge.svg)](https://github.com/Uniswap/uniswap-interface/actions?query=workflow%3ATests)
[![Styled With Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://prettier.io/)
This an an open source interface for Uniswap - a protocol for decentralized exchange of Ethereum tokens.
An open source interface for Uniswap -- a protocol for decentralized exchange of Ethereum tokens.
- Website: [uniswap.io](https://uniswap.io/)
- Docs: [docs.uniswap.io](https://docs.uniswap.io/)
- Twitter: [@UniswapExchange](https://twitter.com/UniswapExchange)
- Reddit: [/r/Uniswap](https://www.reddit.com/r/UniSwap/)
- Email: [contact@uniswap.io](mailto:contact@uniswap.io)
- Discord: [Uniswap](https://discord.gg/Y7TF6QA)
- Website: [uniswap.org](https://uniswap.org/)
- Interface: [app.uniswap.org](https://app.uniswap.org)
- Docs: [uniswap.org/docs/](https://uniswap.org/docs/)
- Twitter: [@UniswapProtocol](https://twitter.com/UniswapProtocol)
- Reddit: [/r/Uniswap](https://www.reddit.com/r/Uniswap/)
- Email: [contact@uniswap.org](mailto:contact@uniswap.org)
- Discord: [Uniswap](https://discord.gg/FCfyBSbCU5)
- Whitepaper: [Link](https://hackmd.io/C-DvwDSfSxuh-Gd4WKE_ig)
## Run Uniswap Locally
1. Download and unzip the `build.zip` file from the latest release in the [Releases tab](https://github.com/Uniswap/uniswap-frontend/releases/latest).
2. Serve the `build/` folder locally, and access the application via a browser.
## Accessing the Uniswap Interface
For more information on running a local server see [https://developer.mozilla.org/en-US/docs/Learn/Common_questions/set_up_a_local_testing_server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/set_up_a_local_testing_server). This simple approach has one downside: refreshing the page will give a `404` because of how React handles client-side routing. To fix this issue, consider running `serve -s` courtesy of the [serve](https://github.com/zeit/serve) package.
## Develop Uniswap Locally
To access the Uniswap Interface, use an IPFS gateway link from the
[latest release](https://github.com/Uniswap/uniswap-interface/releases/latest),
or visit [app.uniswap.org](https://app.uniswap.org).
## Listing a token
Please see the
[@uniswap/default-token-list](https://github.com/uniswap/default-token-list)
repository.
## Development
### Install Dependencies
@@ -30,20 +35,32 @@ For more information on running a local server see [https://developer.mozilla.or
yarn
```
### Configure Environment
Rename `.env.local.example` to `.env.local` and fill in the appropriate variables.
### Run
```bash
yarn start
```
To run on a testnet, make a copy of `.env.local.example` named `.env.local`, change `REACT_APP_NETWORK_ID` to `"{yourNetworkId}"`, and change `REACT_APP_NETWORK_URL` to e.g. `"https://{yourNetwork}.infura.io/v3/{yourKey}"`.
### Configuring the environment (optional)
If deploying with Github Pages, be aware that there's some [tricky client-side routing behavior with `create-react-app`](https://create-react-app.dev/docs/deployment#notes-on-client-side-routing).
To have the interface default to a different network when a wallet is not connected:
1. Make a copy of `.env` named `.env.local`
2. Change `REACT_APP_NETWORK_ID` to `"{YOUR_NETWORK_ID}"`
3. Change `REACT_APP_NETWORK_URL` to e.g. `"https://{YOUR_NETWORK_ID}.infura.io/v3/{YOUR_INFURA_KEY}"`
Note that the interface only works on testnets where both
[Uniswap V2](https://uniswap.org/docs/v2/smart-contracts/factory/) and
[multicall](https://github.com/makerdao/multicall) are deployed.
The interface will not work on other networks.
## Contributions
**Please open all pull requests against the `beta` branch.** CI checks will run against all PRs. To ensure that your changes will pass, run `yarn check:all` before pushing. If this command fails, you can try to automatically fix problems with `yarn fix:all`, or do it manually.
**Please open all pull requests against the `master` branch.**
CI checks will run against all PRs.
## Accessing Uniswap Interface V1
The Uniswap Interface supports swapping against, and migrating or removing liquidity from Uniswap V1. However,
if you would like to use Uniswap V1, the Uniswap V1 interface for mainnet and testnets is accessible via IPFS gateways
linked from the [v1.0.0 release](https://github.com/Uniswap/uniswap-interface/releases/tag/v1.0.0).

8
cypress.json Normal file
View File

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

View File

@@ -0,0 +1,50 @@
describe('Add Liquidity', () => {
it('loads the two correct tokens', () => {
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85-0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'ETH')
})
it('does not crash if ETH is duplicated', () => {
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'ETH')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('not.contain.text', 'ETH')
})
it('token not in storage is loaded', () => {
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'MKR')
})
it('single token can be selected', () => {
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
})
it('redirects /add/token-token to add/token/token', () => {
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.url().should(
'contain',
'/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
)
})
it('redirects /add/WETH-token to /add/WETH-address/token', () => {
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.url().should(
'contain',
'/add/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
)
})
it('redirects /add/token-WETH to /add/token/WETH-address', () => {
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85-0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.url().should(
'contain',
'/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab'
)
})
})

View File

@@ -0,0 +1,22 @@
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/commands'
describe('Landing Page', () => {
beforeEach(() => cy.visit('/'))
it('loads swap page', () => {
cy.get('#swap-page')
})
it('redirects to url /swap', () => {
cy.url().should('include', '/swap')
})
it('allows navigation to pool', () => {
cy.get('#pool-nav-link').click()
cy.url().should('include', '/pool')
})
it('is connected', () => {
cy.get('#web3-status-connected').click()
cy.get('#web3-account-identifier-row').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
})
})

View File

@@ -0,0 +1,11 @@
describe('Lists', () => {
beforeEach(() => {
cy.visit('/swap')
})
// @TODO check if default lists are active when we have them
it('change list', () => {
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('.list-token-manage-button').click()
})
})

View File

@@ -0,0 +1,8 @@
describe('Migrate V1 Liquidity', () => {
describe('Remove V1 liquidity', () => {
it('renders the correct page', () => {
cy.visit('/remove/v1/0x93bB63aFe1E0180d0eF100D774B473034fd60C36')
cy.get('#remove-v1-exchange').should('contain', 'MKR/ETH')
})
})
})

View File

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

View File

@@ -0,0 +1,39 @@
describe('Remove Liquidity', () => {
it('redirects', () => {
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.url().should(
'contain',
'/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
)
})
it('eth remove', () => {
cy.visit('/remove/ETH/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'ETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
})
it('eth remove swap order', () => {
cy.visit('/remove/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/ETH')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'MKR')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'ETH')
})
it('loads the two correct tokens', () => {
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
})
it('does not crash if ETH is duplicated', () => {
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH')
})
it('token not in storage is loaded', () => {
cy.visit('/remove/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'SKL')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
})
})

View File

@@ -0,0 +1,11 @@
describe('Send', () => {
it('should redirect', () => {
cy.visit('/send')
cy.url().should('include', '/swap')
})
it('should redirect with url params', () => {
cy.visit('/send?outputCurrency=ETH&recipient=bob.argent.xyz')
cy.url().should('contain', '/swap?outputCurrency=ETH&recipient=bob.argent.xyz')
})
})

View File

@@ -0,0 +1,75 @@
describe('Swap', () => {
beforeEach(() => {
cy.visit('/swap')
})
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')
})
it('zero swap amount', () => {
cy.get('#swap-currency-input .token-amount-input')
.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', '')
})
it('can enter an amount into output', () => {
cy.get('#swap-currency-output .token-amount-input')
.type('0.001', { delay: 200 })
.should('have.value', '0.001')
})
it('zero output amount', () => {
cy.get('#swap-currency-output .token-amount-input')
.type('0.0', { delay: 200 })
.should('have.value', '0.0')
})
it('can swap ETH for DAI', () => {
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').should('be.visible')
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').click({ force: true })
cy.get('#swap-currency-input .token-amount-input').should('be.visible')
cy.get('#swap-currency-input .token-amount-input').type('0.001', { force: true, delay: 200 })
cy.get('#swap-currency-output .token-amount-input').should('not.equal', '')
cy.get('#swap-button').click()
cy.get('#confirm-swap-or-send').should('contain', 'Confirm Swap')
})
it('add a recipient does not exist unless in expert mode', () => {
cy.get('#add-recipient-button').should('not.exist')
})
describe('expert mode', () => {
beforeEach(() => {
cy.window().then(win => {
cy.stub(win, 'prompt').returns('confirm')
})
cy.get('#open-settings-dialog-button').click()
cy.get('#toggle-expert-mode-button').click()
cy.get('#confirm-expert-mode').click()
})
it('add a recipient is visible', () => {
cy.get('#add-recipient-button').should('be.visible')
})
it('add a recipient', () => {
cy.get('#add-recipient-button').click()
cy.get('#recipient').should('exist')
})
it('remove recipient', () => {
cy.get('#add-recipient-button').click()
cy.get('#remove-recipient-button').click()
cy.get('#recipient').should('not.exist')
})
})
})

10
cypress/support/commands.d.ts vendored Normal file
View File

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

View File

@@ -0,0 +1,82 @@
// ***********************************************
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
import { JsonRpcProvider } from '@ethersproject/providers'
import { Wallet } from '@ethersproject/wallet'
import { _Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
// never send real ether to this, obviously
const PRIVATE_KEY_TEST_NEVER_USE = '0xad20c82497421e9784f18460ad2fe84f73569068e98e270b3e63743268af5763'
// address of the above key
export const TEST_ADDRESS_NEVER_USE = '0x0fF2D1eFd7A57B7562b2bf27F3f37899dB27F4a5'
export const TEST_ADDRESS_NEVER_USE_SHORTENED = '0x0fF2...F4a5'
class CustomizedBridge extends _Eip1193Bridge {
async sendAsync(...args) {
console.debug('sendAsync called', ...args)
return this.send(...args)
}
async send(...args) {
console.debug('send called', ...args)
const isCallbackForm = typeof args[0] === 'object' && typeof args[1] === 'function'
let callback
let method
let params
if (isCallbackForm) {
callback = args[1]
method = args[0].method
params = args[0].params
} else {
method = args[0]
params = args[1]
}
if (method === 'eth_requestAccounts' || method === 'eth_accounts') {
if (isCallbackForm) {
callback({ result: [TEST_ADDRESS_NEVER_USE] })
} else {
return Promise.resolve([TEST_ADDRESS_NEVER_USE])
}
}
if (method === 'eth_chainId') {
if (isCallbackForm) {
callback(null, { result: '0x4' })
} else {
return Promise.resolve('0x4')
}
}
try {
const result = await super.send(method, params)
console.debug('result received', method, params, result)
if (isCallbackForm) {
callback(null, { result })
} else {
return result
}
} catch (error) {
if (isCallbackForm) {
callback(error, null)
} else {
throw error
}
}
}
}
// sets up the injected provider to be a mock ethereum provider with the given mnemonic/index
Cypress.Commands.overwrite('visit', (original, url, options) => {
return original(url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url, {
...options,
onBeforeLoad(win) {
options && options.onBeforeLoad && options.onBeforeLoad(win)
win.localStorage.clear()
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
const signer = new Wallet(PRIVATE_KEY_TEST_NEVER_USE, provider)
win.ethereum = new CustomizedBridge(signer, provider)
}
})
})

9
cypress/support/index.js Normal file
View File

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

10
cypress/tsconfig.json Normal file
View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"strict": true,
"baseUrl": "../node_modules",
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress"]
},
"include": ["**/*.ts"]
}

View File

@@ -1,20 +0,0 @@
# block some countries
[[redirects]]
from = "/*"
to = "/451.html"
status = 451
force = true
conditions = {Country=["BY","CU","IR","IQ","CI","LR","KP","SD","SY","ZW"]}
headers = {Link="<https://uniswap.exchange>"}
# support SPA setup
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[build.environment]
REACT_APP_IS_PRODUCTION_DEPLOY = "false"
[context.production.environment]
REACT_APP_IS_PRODUCTION_DEPLOY = "true"

View File

@@ -1,59 +1,115 @@
{
"name": "uniswap",
"description": "Uniswap Exchange Protocol",
"version": "0.1.0",
"homepage": "https://uniswap.exchange",
"name": "@uniswap/interface",
"description": "Uniswap Interface",
"homepage": ".",
"private": true,
"dependencies": {
"@reach/dialog": "^0.2.8",
"@reach/tooltip": "^0.2.0",
"@uniswap/sdk": "^1.0.0-beta.4",
"@web3-react/core": "^6.0.2",
"@web3-react/fortmatic-connector": "^6.0.2",
"@web3-react/injected-connector": "^6.0.3",
"@web3-react/network-connector": "^6.0.4",
"@web3-react/portis-connector": "^6.0.2",
"@web3-react/walletconnect-connector": "^6.0.2",
"@web3-react/walletlink-connector": "^6.0.2",
"devDependencies": {
"@ethersproject/experimental": "^5.0.1",
"@popperjs/core": "^2.4.4",
"@reach/dialog": "^0.10.3",
"@reach/portal": "^0.10.3",
"@reduxjs/toolkit": "^1.3.5",
"@types/jest": "^25.2.1",
"@types/lodash.flatmap": "^4.5.6",
"@types/luxon": "^1.24.4",
"@types/multicodec": "^1.0.0",
"@types/node": "^13.13.5",
"@types/qs": "^6.9.2",
"@types/react": "^16.9.34",
"@types/react-dom": "^16.9.7",
"@types/react-redux": "^7.1.8",
"@types/react-router-dom": "^5.0.0",
"@types/react-virtualized-auto-sizer": "^1.0.0",
"@types/react-window": "^1.8.2",
"@types/rebass": "^4.0.5",
"@types/styled-components": "^5.1.0",
"@types/testing-library__cypress": "^5.0.5",
"@types/wcag-contrast": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^2.31.0",
"@typescript-eslint/parser": "^2.31.0",
"@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "1.0.1",
"@uniswap/sdk": "3.0.3",
"@uniswap/token-lists": "^1.0.0-beta.19",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@web3-react/core": "^6.0.9",
"@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": "^6.1.1",
"@web3-react/walletlink-connector": "^6.0.9",
"ajv": "^6.12.3",
"cids": "^1.0.0",
"copy-to-clipboard": "^3.2.0",
"escape-string-regexp": "^2.0.0",
"ethers": "^4.0.44",
"history": "^4.9.0",
"cross-env": "^7.0.2",
"cypress": "^4.11.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-react": "^7.19.0",
"eslint-plugin-react-hooks": "^4.0.0",
"ethers": "^5.0.7",
"i18next": "^15.0.9",
"i18next-browser-languagedetector": "^3.0.1",
"i18next-xhr-backend": "^2.0.1",
"inter-ui": "^3.13.1",
"jazzicon": "^1.5.0",
"lodash.flatmap": "^4.5.0",
"luxon": "^1.25.0",
"multicodec": "^2.0.0",
"multihashes": "^3.0.1",
"node-vibrant": "^3.1.5",
"polished": "^3.3.2",
"qrcode.react": "^0.9.3",
"react": "^16.8.6",
"prettier": "^1.17.0",
"qs": "^6.9.4",
"react": "^16.13.1",
"react-confetti": "^6.0.0",
"react-device-detect": "^1.6.2",
"react-dom": "^16.8.6",
"react-feather": "^1.1.6",
"react-dom": "^16.13.1",
"react-feather": "^2.0.8",
"react-ga": "^2.5.7",
"react-i18next": "^10.7.0",
"react-markdown": "^4.3.1",
"react-popper": "^2.2.3",
"react-redux": "^7.2.0",
"react-router-dom": "^5.0.0",
"react-scripts": "^3.0.1",
"react-scripts": "^3.4.1",
"react-spring": "^8.0.27",
"react-switch": "^5.0.1",
"react-use-gesture": "^6.0.14",
"styled-components": "^4.2.0"
"react-virtualized-auto-sizer": "^1.0.2",
"react-window": "^1.8.5",
"rebass": "^4.0.7",
"redux-localstorage-simple": "^2.3.1",
"serve": "^11.3.2",
"start-server-and-test": "^1.11.0",
"styled-components": "^4.2.0",
"typescript": "^3.8.3",
"use-count-up": "^2.2.5",
"wcag-contrast": "^3.0.0",
"workbox-core": "^6.1.0",
"workbox-expiration": "^6.1.0",
"workbox-precaching": "^6.1.0",
"workbox-routing": "^6.1.0",
"workbox-strategies": "^6.1.0"
},
"resolutions": {
"@walletconnect/web3-provider": "1.1.1-alpha.0"
},
"scripts": {
"start": "react-scripts start",
"start:service-worker": "yarn build && yarn serve -s build",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"lint:base": "yarn eslint './src/**/*.{js,jsx}'",
"format:base": "yarn prettier './src/**/*.{js,jsx,scss}'",
"fix:lint": "yarn lint:base --fix",
"fix:format": "yarn format:base --write",
"fix:all": "yarn fix:lint && yarn fix:format",
"check:lint": "yarn lint:base",
"check:format": "yarn format:base --check",
"check:all": "yarn check:lint && yarn check:format"
"integration-test": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run'"
},
"eslintConfig": {
"extends": "react-app"
"extends": "react-app",
"ignorePatterns": [
"node_modules"
]
},
"browserslist": {
"production": [
@@ -68,7 +124,7 @@
]
},
"license": "GPL-3.0-or-later",
"devDependencies": {
"prettier": "^1.17.0"
"dependencies": {
"@uniswap/default-token-list": "^2.0.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

View File

@@ -2,9 +2,12 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="shortcut icon" type="image/png" href="%PUBLIC_URL%/favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="%PUBLIC_URL%/images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="%PUBLIC_URL%/images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="theme-color" content="#ff007a" />
<meta name="fortmatic-site-verification" content="j93LgcVZk79qcgyo" />
<!--
manifest.json provides metadata used when your web app is installed on a
@@ -20,11 +23,12 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Uniswap Exchange</title>
<title>Uniswap Interface</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" />
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

View File

@@ -21,6 +21,7 @@
"searchOrPaste": "Search Token Name, Symbol, or Address",
"searchOrPasteMobile": "Name, Symbol, or Address",
"noExchange": "No Exchange Found",
"noToken": "No Token Found",
"exchangeRate": "Exchange Rate",
"unknownError": "Oops! An unknown error occurred. Please refresh the page, or visit from another browser or device.",
"enterValueCont": "Enter a {{ missingCurrencyValue }} value to continue.",
@@ -84,5 +85,7 @@
"enterTokenCont": "Enter a token address to continue",
"priceChange": "Expected price slippage",
"forAtLeast": "for at least ",
"brokenToken": "The selected token is not compatible with Uniswap V1. Adding liquidity will result in locked funds."
"brokenToken": "The selected token is not compatible with Uniswap V1. Adding liquidity will result in locked funds.",
"toleranceExplanation": "Lowering this limit decreases your risk of frontrunning. However, this makes more likely that your transaction will fail due to normal price movements.",
"tokenSearchPlaceholder": "Search name or paste address"
}

View File

@@ -1,75 +1,75 @@
{
"noWallet": "No se encontró billetera de Ethereum",
"wrongNetwork": "Te encontrás en la red equivocada",
"switchNetwork": "Por favor cambia a {{ correctNetwork }}",
"installWeb3MobileBrowser": "Por favor ingresá desde un navegador móvil con web3 habilitado como Trust Wallet o Coinbase Wallet.",
"installMetamask": "Por favor visítanos nuevamente luego de instalar Metamask en Chrome o Brave.",
"disconnected": "Desconectado",
"swap": "Intercambiar",
"send": "Enviar",
"pool": "Pool",
"betaWarning": "Este proyecto se encuentra en beta. Usalo bajo tu propio riesgo.",
"input": "Entrada",
"output": "Salida",
"estimated": "estimado",
"balance": "Saldo: {{ balanceInput }}",
"unlock": "Desbloquear",
"pending": "Pendiente",
"selectToken": "Seleccioná un token",
"searchOrPaste": "Buscar Token o Pegar Dirección",
"noExchange": "No se encontró la divisa",
"exchangeRate": "Tasa de Cambio",
"enterValueCont": "Ingresá un valor en {{ missingCurrencyValue }} para continuar.",
"selectTokenCont": "Seleccioná un token para continuar.",
"noLiquidity": "Sin liquidez.",
"unlockTokenCont": "Por favor desbloqueá un token para continuar.",
"transactionDetails": "Detalles de la transacción",
"hideDetails": "Ocultar detalles",
"youAreSelling": "Estás vendiendo",
"orTransFail": "o la transacción fallará.",
"youWillReceive": "Vas a recibir al menos",
"youAreBuying": "Estás comprando",
"itWillCost": "Costará a lo sumo",
"insufficientBalance": "Saldo insuficiente",
"inputNotValid": "No es un valor de entrada válido",
"differentToken": "Debe ser un token distinto.",
"noRecipient": "Ingresá una dirección de billetera para enviar.",
"invalidRecipient": "Por favor ingrese una billetera de destino válida.",
"recipientAddress": "Dirección del recipiente",
"youAreSending": "Estás enviando",
"willReceive": "recibirá al menos",
"to": "a",
"addLiquidity": "Agregar liquidez",
"deposit": "Depositar",
"currentPoolSize": "Tamaño del Pool Actual",
"yourPoolShare": "Tu parte del Pool",
"noZero": "El monto no puede ser cero.",
"mustBeETH": "Una de las entradas debe ser ETH.",
"enterCurrencyOrLabelCont": "Ingresá un valor de {{ inputCurrency }} o de {{ label }} para continuar.",
"youAreAdding": "Estás agregando entre",
"and": "y",
"intoPool": "en el pool de liquidez.",
"outPool": "en el pool de liquidez.",
"youWillMint": "Vas a acuñar",
"liquidityTokens": "tokens de liquidez.",
"totalSupplyIs": "El actual suministro total de tokens de liquidez es",
"youAreSettingExRate": "Está configurando el tipo de cambio inicial a",
"totalSupplyIs0": "El actual suministro total de tokens de liquidez es 0.",
"tokenWorth": "Al tipo de cambio actual, cada token del pool vale",
"firstLiquidity": "Sos la primer persona en agregar liquidez!",
"initialExchangeRate": "El tipo de cambio inicial se establecerá en función de tus depósitos. Por favor, asegúrate de que tus depósitos en ETH y {{ label }} tengan el mismo valor fíat.",
"removeLiquidity": "Remover Liquidez",
"poolTokens": "Pool de Tokens",
"enterLabelCont": "Ingresá un valor de {{ label }} para continuar.",
"youAreRemoving": "Estás quitando entre",
"youWillRemove": "Vas a remover",
"createExchange": "Crear divisa",
"invalidTokenAddress": "No es una dirección de token válida",
"exchangeExists": "La divisa {{ label }} ya existe!",
"invalidSymbol": "Símbolo inválido",
"invalidDecimals": "Decimales inválidos",
"tokenAddress": "Dirección de Token",
"label": "Etiqueta",
"decimals": "Decimales",
"enterTokenCont": "Ingresá una dirección de token para continuar"
"noWallet": "No se encontró billetera de Ethereum",
"wrongNetwork": "Te encontrás en la red equivocada",
"switchNetwork": "Por favor cambia a {{ correctNetwork }}",
"installWeb3MobileBrowser": "Por favor ingresá desde un navegador móvil con web3 habilitado como Trust Wallet o Coinbase Wallet.",
"installMetamask": "Por favor visítanos nuevamente luego de instalar Metamask en Chrome o Brave.",
"disconnected": "Desconectado",
"swap": "Intercambiar",
"send": "Enviar",
"pool": "Pool",
"betaWarning": "Este proyecto se encuentra en beta. Usalo bajo tu propio riesgo.",
"input": "Entrada",
"output": "Salida",
"estimated": "estimado",
"balance": "Saldo: {{ balanceInput }}",
"unlock": "Desbloquear",
"pending": "Pendiente",
"selectToken": "Seleccioná un token",
"searchOrPaste": "Buscar Token o Pegar Dirección",
"noExchange": "No se encontró la divisa",
"exchangeRate": "Tasa de Cambio",
"enterValueCont": "Ingresá un valor en {{ missingCurrencyValue }} para continuar.",
"selectTokenCont": "Seleccioná un token para continuar.",
"noLiquidity": "Sin liquidez.",
"unlockTokenCont": "Por favor desbloqueá un token para continuar.",
"transactionDetails": "Detalles de la transacción",
"hideDetails": "Ocultar detalles",
"youAreSelling": "Estás vendiendo",
"orTransFail": "o la transacción fallará.",
"youWillReceive": "Vas a recibir al menos",
"youAreBuying": "Estás comprando",
"itWillCost": "Costará a lo sumo",
"insufficientBalance": "Saldo insuficiente",
"inputNotValid": "No es un valor de entrada válido",
"differentToken": "Debe ser un token distinto.",
"noRecipient": "Ingresá una dirección de billetera para enviar.",
"invalidRecipient": "Por favor ingrese una billetera de destino válida.",
"recipientAddress": "Dirección del recipiente",
"youAreSending": "Estás enviando",
"willReceive": "recibirá al menos",
"to": "a",
"addLiquidity": "Agregar liquidez",
"deposit": "Depositar",
"currentPoolSize": "Tamaño del Pool Actual",
"yourPoolShare": "Tu parte del Pool",
"noZero": "El monto no puede ser cero.",
"mustBeETH": "Una de las entradas debe ser ETH.",
"enterCurrencyOrLabelCont": "Ingresá un valor de {{ inputCurrency }} o de {{ label }} para continuar.",
"youAreAdding": "Estás agregando entre",
"and": "y",
"intoPool": "en el pool de liquidez.",
"outPool": "en el pool de liquidez.",
"youWillMint": "Vas a acuñar",
"liquidityTokens": "tokens de liquidez.",
"totalSupplyIs": "El actual suministro total de tokens de liquidez es",
"youAreSettingExRate": "Está configurando el tipo de cambio inicial a",
"totalSupplyIs0": "El actual suministro total de tokens de liquidez es 0.",
"tokenWorth": "Al tipo de cambio actual, cada token del pool vale",
"firstLiquidity": "Sos la primer persona en agregar liquidez!",
"initialExchangeRate": "El tipo de cambio inicial se establecerá en función de tus depósitos. Por favor, asegúrate de que tus depósitos en ETH y {{ label }} tengan el mismo valor fíat.",
"removeLiquidity": "Remover Liquidez",
"poolTokens": "Pool de Tokens",
"enterLabelCont": "Ingresá un valor de {{ label }} para continuar.",
"youAreRemoving": "Estás quitando entre",
"youWillRemove": "Vas a remover",
"createExchange": "Crear divisa",
"invalidTokenAddress": "No es una dirección de token válida",
"exchangeExists": "La divisa {{ label }} ya existe!",
"invalidSymbol": "Símbolo inválido",
"invalidDecimals": "Decimales inválidos",
"tokenAddress": "Dirección de Token",
"label": "Etiqueta",
"decimals": "Decimales",
"enterTokenCont": "Ingresá una dirección de token para continuar"
}

View File

@@ -1,75 +1,75 @@
{
"noWallet": "No se ha encontrado billetera de Ethereum",
"wrongNetwork": "Se encuentra en la red equivocada",
"switchNetwork": "Por favor cambie a {{ correctNetwork }}",
"installWeb3MobileBrowser": "Por favor ingrese desde un navegador móvil con web3 habilitado como Trust Wallet o Coinbase Wallet.",
"installMetamask": "Por favor visítenos nuevamente luego de instalar Metamask en Chrome o Brave.",
"disconnected": "Desconectado",
"swap": "Intercambiar",
"send": "Enviar",
"pool": "Pool",
"betaWarning": "Este proyecto se encuentra en beta. Úselo bajo tu propio riesgo.",
"input": "Entrada",
"output": "Salida",
"estimated": "estimado",
"balance": "Saldo: {{ balanceInput }}",
"unlock": "Desbloquear",
"pending": "Pendiente",
"selectToken": "Seleccione un token",
"searchOrPaste": "Buscar Token o Pegar Dirección",
"noExchange": "No se ha encontrado la divisa",
"exchangeRate": "Tasa de Cambio",
"enterValueCont": "Ingrese un valor en {{ missingCurrencyValue }} para continuar.",
"selectTokenCont": "Seleccione un token para continuar.",
"noLiquidity": "Sin liquidez.",
"unlockTokenCont": "Por favor desbloquea un token para continuar.",
"transactionDetails": "Detalles de la transacción",
"hideDetails": "Ocultar detalles",
"youAreSelling": "Está vendiendo",
"orTransFail": "o la transacción fallará.",
"youWillReceive": "Va a recibir al menos",
"youAreBuying": "Está comprando",
"itWillCost": "Costará a lo sumo",
"insufficientBalance": "Saldo insuficiente",
"inputNotValid": "No es un valor de entrada válido",
"differentToken": "Debe ser un token distinto.",
"noRecipient": "Ingrese una dirección de billetera para enviar.",
"invalidRecipient": "Por favor ingrese una billetera de destino válida.",
"recipientAddress": "Dirección del recipiente",
"youAreSending": "Está enviando",
"willReceive": "recibirá al menos",
"to": "a",
"addLiquidity": "Agregar liquidez",
"deposit": "Depositar",
"currentPoolSize": "Tamaño del Pool Actual",
"yourPoolShare": "Su parte del Pool",
"noZero": "El monto no puede ser cero.",
"mustBeETH": "Una de las entradas debe ser ETH.",
"enterCurrencyOrLabelCont": "Ingrese un valor de {{ inputCurrency }} o de {{ label }} para continuar.",
"youAreAdding": "Está agregando entre",
"and": "y",
"intoPool": "en el pool de liquidez.",
"outPool": "en el pool de liquidez.",
"youWillMint": "Va a acuñar",
"liquidityTokens": "tokens de liquidez.",
"totalSupplyIs": "El actual suministro total de tokens de liquidez es",
"youAreSettingExRate": "Está configurando el tipo de cambio inicial a",
"totalSupplyIs0": "El actual suministro total de tokens de liquidez es 0.",
"tokenWorth": "Al tipo de cambio actual, cada token del pool vale",
"firstLiquidity": "Es la primer persona en agregar liquidez!",
"initialExchangeRate": "El tipo de cambio inicial se establecerá en función de sus depósitos. Por favor, asegúrese de que sus depósitos en ETH y {{ label }} tengan el mismo valor fíat.",
"removeLiquidity": "Remover Liquidez",
"poolTokens": "Pool de Tokens",
"enterLabelCont": "Ingresa un valor de {{ label }} para continuar.",
"youAreRemoving": "Está quitando entre",
"youWillRemove": "Va a remover",
"createExchange": "Crear tipo de cambio",
"invalidTokenAddress": "No es una dirección de token válida",
"exchangeExists": "El tipo de cambio {{ label }} ya existe!",
"invalidSymbol": "Símbolo inválido",
"invalidDecimals": "Decimales inválidos",
"tokenAddress": "Dirección de Token",
"label": "Etiqueta",
"decimals": "Decimales",
"enterTokenCont": "Ingrese una dirección de token para continuar"
"noWallet": "No se ha encontrado billetera de Ethereum",
"wrongNetwork": "Se encuentra en la red equivocada",
"switchNetwork": "Por favor cambie a {{ correctNetwork }}",
"installWeb3MobileBrowser": "Por favor ingrese desde un navegador móvil con web3 habilitado como Trust Wallet o Coinbase Wallet.",
"installMetamask": "Por favor visítenos nuevamente luego de instalar Metamask en Chrome o Brave.",
"disconnected": "Desconectado",
"swap": "Intercambiar",
"send": "Enviar",
"pool": "Pool",
"betaWarning": "Este proyecto se encuentra en beta. Úselo bajo tu propio riesgo.",
"input": "Entrada",
"output": "Salida",
"estimated": "estimado",
"balance": "Saldo: {{ balanceInput }}",
"unlock": "Desbloquear",
"pending": "Pendiente",
"selectToken": "Seleccione un token",
"searchOrPaste": "Buscar Token o Pegar Dirección",
"noExchange": "No se ha encontrado la divisa",
"exchangeRate": "Tasa de Cambio",
"enterValueCont": "Ingrese un valor en {{ missingCurrencyValue }} para continuar.",
"selectTokenCont": "Seleccione un token para continuar.",
"noLiquidity": "Sin liquidez.",
"unlockTokenCont": "Por favor desbloquea un token para continuar.",
"transactionDetails": "Detalles de la transacción",
"hideDetails": "Ocultar detalles",
"youAreSelling": "Está vendiendo",
"orTransFail": "o la transacción fallará.",
"youWillReceive": "Va a recibir al menos",
"youAreBuying": "Está comprando",
"itWillCost": "Costará a lo sumo",
"insufficientBalance": "Saldo insuficiente",
"inputNotValid": "No es un valor de entrada válido",
"differentToken": "Debe ser un token distinto.",
"noRecipient": "Ingrese una dirección de billetera para enviar.",
"invalidRecipient": "Por favor ingrese una billetera de destino válida.",
"recipientAddress": "Dirección del recipiente",
"youAreSending": "Está enviando",
"willReceive": "recibirá al menos",
"to": "a",
"addLiquidity": "Agregar liquidez",
"deposit": "Depositar",
"currentPoolSize": "Tamaño del Pool Actual",
"yourPoolShare": "Su parte del Pool",
"noZero": "El monto no puede ser cero.",
"mustBeETH": "Una de las entradas debe ser ETH.",
"enterCurrencyOrLabelCont": "Ingrese un valor de {{ inputCurrency }} o de {{ label }} para continuar.",
"youAreAdding": "Está agregando entre",
"and": "y",
"intoPool": "en el pool de liquidez.",
"outPool": "en el pool de liquidez.",
"youWillMint": "Va a acuñar",
"liquidityTokens": "tokens de liquidez.",
"totalSupplyIs": "El actual suministro total de tokens de liquidez es",
"youAreSettingExRate": "Está configurando el tipo de cambio inicial a",
"totalSupplyIs0": "El actual suministro total de tokens de liquidez es 0.",
"tokenWorth": "Al tipo de cambio actual, cada token del pool vale",
"firstLiquidity": "Es la primer persona en agregar liquidez!",
"initialExchangeRate": "El tipo de cambio inicial se establecerá en función de sus depósitos. Por favor, asegúrese de que sus depósitos en ETH y {{ label }} tengan el mismo valor fíat.",
"removeLiquidity": "Remover Liquidez",
"poolTokens": "Pool de Tokens",
"enterLabelCont": "Ingresa un valor de {{ label }} para continuar.",
"youAreRemoving": "Está quitando entre",
"youWillRemove": "Va a remover",
"createExchange": "Crear tipo de cambio",
"invalidTokenAddress": "No es una dirección de token válida",
"exchangeExists": "El tipo de cambio {{ label }} ya existe!",
"invalidSymbol": "Símbolo inválido",
"invalidDecimals": "Decimales inválidos",
"tokenAddress": "Dirección de Token",
"label": "Etiqueta",
"decimals": "Decimales",
"enterTokenCont": "Ingrese una dirección de token para continuar"
}

75
public/locales/iw.json Normal file
View File

@@ -0,0 +1,75 @@
{
"noWallet": "לא נמצא ארנק",
"wrongNetwork": "נבחרה רשת לא נכונה",
"switchNetwork": "{{ correctNetwork }} יש צורך לשנות את הרשת ל",
"installWeb3MobileBrowser": "יש צורך בארנק ווב3.0, תתקין מטאמאסק או ארנק דומה",
"installMetamask": " Metamask יש צורך להתקין תוסף מטאמאסק לדפדפן, חפשו בגוגל ",
"disconnected": "מנותק",
"swap": "המרה",
"send": "שליחה",
"pool": "להפקיד",
"betaWarning": "הפרויקט נמצא בשלב בטא, השתמשו באחריות",
"input": "מוכר",
"output": "אקבל",
"estimated": "הערכה",
"balance": "בארנק שלי {{ balanceInput }}",
"unlock": "שחרור נעילת ארנק",
"pending": "ממתין לאישור",
"selectToken": "בחרו את הטוקן להמרה",
"searchOrPaste": "הכניסו שם או כתובת של טוקן לחיפוש",
"noExchange": "לא מתאפשרת המרה",
"exchangeRate": "שער המרה",
"enterValueCont": "כדי להמשיך {{ missingCurrencyValue }} הזינו ",
"selectTokenCont": "בחרו טוקן כדי להמשיך",
"noLiquidity": "אין נזילות",
"unlockTokenCont": "יש צורך לאשר את הטוקן למסחר",
"transactionDetails": "פרטי הטרנזקציה",
"hideDetails": "הסתר פרטים נוספים",
"youAreSelling": "למכירה",
"orTransFail": "או שהטרנזקציה תיכשל",
"youWillReceive": "תוצר המרה מינימלי",
"youAreBuying": "קונה",
"itWillCost": "זה יעלה",
"insufficientBalance": "אין בחשבון מספיק מטבעות",
"inputNotValid": "קלט לא תקין",
"differentToken": "יש צורך בטוקנים שונים",
"noRecipient": "לא הוכנסה כתובת ארנק יעד",
"invalidRecipient": "לא הוכנסה כתובת תקינה",
"recipientAddress": "כתובת יעד",
"youAreSending": "כמות לשליחה",
"willReceive": "יתקבל לכל הפחות",
"to": "אל",
"addLiquidity": "להוספת נזילות למאגר",
"deposit": "הפקדה",
"currentPoolSize": "גודל מאגר הנזילות הכולל",
"yourPoolShare": "חלקך במאגר הנזילות",
"noZero": "אפס אינו ערך תקין",
"mustBeETH": "ETH חייב להופיע באחד מהצדדים",
"enterCurrencyOrLabelCont": "כדי להמשיך {{ inputCurrency }} או {{ label }} הכנס",
"youAreAdding": "מתווספים למאגר",
"and": "וגם",
"intoPool": "לתוך הנזילות",
"outPool": "מתוך",
"youWillMint": "יונפקו לכם",
"liquidityTokens": "טוקנים של נזילות",
"totalSupplyIs": "חלקך במאגר הנזילות",
"youAreSettingExRate": "שער ההמרה יקבע על ידך",
"totalSupplyIs0": "אין לך טוקנים של נזילות",
"tokenWorth": "שווי כל טוקן נזילות הינו",
"firstLiquidity": "אתה הראשוןה שמזרים נזילות למאגר",
"initialExchangeRate": "ושל האית'ר הינן בערך שווה {{ label }} תוודאו שההפקדה של הטוקן",
"removeLiquidity": "הוצאה של נזילות",
"poolTokens": "טוקנים של מאגר הנזילות",
"enterLabelCont": "כדי להמשיך {{ label }} הכנס ",
"youAreRemoving": "יוסרו",
"youWillRemove": "יוסרו",
"createExchange": "ליצירת זוג מסחר",
"invalidTokenAddress": "כתובת טוקן לא נכונה",
"exchangeExists": "{{ label }} כבר קיים זוג המרה עבור",
"invalidSymbol": "תו שגוי",
"invalidDecimals": "ספרות עשרוניות שגויות",
"tokenAddress": "כתובת הטוקן",
"label": "שם",
"decimals": "ספרות עשרויות",
"enterTokenCont": "הכניסו כתובת טוקן כדי להמשיך"
}

View File

@@ -5,71 +5,87 @@
"installWeb3MobileBrowser": "Incearcă să vizitezi această pagina folosind un browser precum Trust Wallet sau Coinbase Wallet.",
"installMetamask": "Vizitează această pagină din nou după ce instalezi MetaMask în Chrome sau Brave",
"disconnected": "Deconectat",
"swap": "Schimba",
"swap": "Schimbă",
"swapAnyway": "Schimbă Oricum",
"send": "Trimite",
"pool": "Extrage",
"sendAnyway": "Trimite Oricum",
"pool": "Depune Lichiditate",
"betaWarning": "Proiectul este încă în versiunea beta. Folosește-l cu grijă, riscul este al tău.",
"input": "Input",
"output": "Output",
"estimated": "estimat",
"balance": "Balanță: {{ balanceInput }}",
"unlock": "Deschide",
"unlock": "Deblochează",
"pending": "În Așteptare",
"selectToken": "Selectează un token",
"searchOrPaste": "Caută Token sau Setează Adresă",
"noExchange": "Niciun Exchange Găsit",
"exchangeRate": "Curs Valutar",
"selectToken": "Selectează un jeton",
"searchOrPaste": "Caută după Numele, Simbolul sau Adresa Jetonului",
"searchOrPasteMobile": "Nume, Simbol sau Adresă",
"noExchange": "Nicio Piață de Schimb Găsită",
"noToken": "Nicin Jeton Găsit",
"exchangeRate": "Curs de Schimb",
"unknownError": "Ups! A intervenit o eroare tehnică. Te rog reîncarcă pagina, sau acceseaz-o de pe alt navigator sau dispozitiv.",
"enterValueCont": "Setează o valoare pentru {{ missingCurrencyValue }} pentru a continua.",
"selectTokenCont": "Selectează un token pentru a continua.",
"noLiquidity": "Lichiditate Inexistentă.",
"unlockTokenCont": "Te rog deblochează tokenul pentru a continua.",
"transactionDetails": "Detalii Tranzacții",
"selectTokenCont": "Selectează un jeton pentru a continua.",
"noLiquidity": "Nu Există Lichiditate.",
"insufficientLiquidity": "Lichiditate Insuficientă.",
"unlockTokenCont": "Te rog deblochează jetonul pentru a continua.",
"transactionDetails": "Detalii Avansate",
"hideDetails": "Ascunde Detaili",
"youAreSelling": "Vinzi",
"slippageWarning": "Alertă Deviație de Preț",
"highSlippageWarning": "Alertă Deviație de Preț Mare",
"youAreSelling": "Tu vinzi",
"orTransFail": "sau tranzacția va eșua.",
"youWillReceive": "Vei primi cel puțin",
"youAreBuying": "Cumperi",
"youAreBuying": "Tu cumperi",
"itWillCost": "Va costa cel mult",
"forAtMost": "pentru maximum",
"insufficientBalance": "Balanță Insuficientă",
"inputNotValid": "Valoarea setată nu este validă",
"differentToken": "Trebuie să fie un token diferit.",
"differentToken": "Trebuie să fie un jeton diferit.",
"noRecipient": "Setează o adresă de portofel pentru destinatar.",
"invalidRecipient": "Te rog setează o adresă de portofel validă pentru destinatar.",
"recipientAddress": "Adresa Destinatarului",
"youAreSending": "Trimiți",
"youAreSending": "Tu trimiți",
"willReceive": "vei primi cel puțin",
"to": "spre",
"addLiquidity": "Crește Lichiditatea",
"addLiquidity": "Adaugă Lichiditate",
"deposit": "Depozitează",
"currentPoolSize": "Mărimea Actualului Fond Comun",
"yourPoolShare": "Partea Ta Din Fond Comun",
"currentPoolSize": "Volumul Actual al Fondului",
"yourPoolShare": "Partea Ta din Fond",
"noZero": "Cantitatea nu poate fi zero.",
"mustBeETH": "Una dintre valori trebuie să fie ETH.",
"enterCurrencyOrLabelCont": "Setează o valoare pentru {{ inputCurrency }} sau {{ label }} pentru a continua. ",
"youAreAdding": "Adaugi între",
"youAreAdding": "Tu adaugi",
"and": "și",
"intoPool": "în fondul comun de lichidatate.",
"outPool": "din fondul comun de lichiditate.",
"youWillMint": "Vei printa",
"liquidityTokens": "tokene disponibile.",
"totalSupplyIs": "Actuala ofertă totală de tokene disponibile este",
"youAreSettingExRate": "Setezi cursul valutar inițial la",
"totalSupplyIs0": "Actuala ofertă totală de tokene disponibile este 0.",
"tokenWorth": "La acest schimb valutar, fiecare token disponibil este estimată la",
"firstLiquidity": "Ești prima persoană care aduce lichiditate!",
"initialExchangeRate": "Schimbul valutar inițial va fi setat în funcție de depozitul tău. Te rog asigură-te că depoziturile ETH și {{ label }} pe care le-ai făcut au aceeași valoare în monezi naționale.",
"removeLiquidity": "Elimină Lichiditate",
"poolTokens": "Extrage Tokene",
"intoPool": "în fondul de lichidatate.",
"outPool": "din fondul de lichiditate.",
"youWillMint": "Tu vei tipări",
"liquidityTokens": "jetoane de lichiditate.",
"totalSupplyIs": "Cantitatea totală de jetoane de lichiditate este",
"youAreSettingExRate": "Setezi cursul de schimb inițial la",
"totalSupplyIs0": "Cantitatea totală de jetoane de lichiditate este 0.",
"tokenWorth": "La cursul de schimb actual, fiecare jeton de lichiditate este valorat la",
"firstLiquidity": "Ești prima persoană care depune lichiditate!",
"initialExchangeRate": "Cursul de schimb inițial va fi setat în funcție de depozitele tale. Te rog asigură-te că ETH-ul și {{ label }}-ul pe care le-ai depozitat au aceeași valoare în bani fiat.",
"removeLiquidity": "Retrage Lichiditat",
"poolTokens": "Depune Jetoane",
"enterLabelCont": "Setează o valoare pentru {{ label }} pentru a continua.",
"youAreRemoving": "Elimini între",
"youWillRemove": "Vei elimina",
"createExchange": "Creează Exchange",
"invalidTokenAddress": "Adresă de token nu este validă",
"exchangeExists": "{{ label }} Exchange există deja!",
"invalidSymbol": "Simbol invalid!",
"invalidDecimals": "Zecimale invalidate",
"tokenAddress": "Adresă Token",
"youAreRemoving": "Retragi între",
"youWillRemove": "Vei retrage",
"createExchange": "Creează Piață de Schimb",
"invalidTokenAddress": "Adresa de jeton nu este validă",
"exchangeExists": "{{ label }} Piața de schimb există deja!",
"invalidSymbol": "Simbol invalid",
"invalidDecimals": "Zecimale invalide",
"tokenAddress": "Adresă Jeton",
"label": "Denumire",
"name": "Nume",
"symbol": "Simbol",
"decimals": "Zecimale",
"enterTokenCont": "Setează o adresă de token pentru a continua"
"enterTokenCont": "Setează o adresă de jeton pentru a continua",
"priceChange": "Deviație de preț așteptată",
"forAtLeast": "pentru cel puțin ",
"brokenToken": "Jetonul selectat nu este compatibil cu Uniswap V1. Depunerea de lichiditate îți va bloca fondurile pe vecie.",
"toleranceExplanation": "Micșorând această limită, vei reduce riscul de frontrunning. În același timp, devine mai probabil că tranzacția va eșua din cauza variațiilor normale de preț.",
"tokenSearchPlaceholder": "Caută nume sau lipește adresă"
}

View File

@@ -1,87 +1,87 @@
{
"noWallet": "Không tìm thấy ví tiền Ethereum",
"wrongNetwork": "Kết nối mạng không đúng",
"switchNetwork": "Vui lòng chuyển sang {{ correctNetwork }}",
"installWeb3MobileBrowser": "Vui lòng truy cập từ trình duyệt di động hỗ trợ web3 như là Ví Trust hoặc Ví Coinbase",
"installMetamask": "Vui lòng truy cập sau khi cài đặt Metamask trên Chrome hoặc Brave.",
"disconnected": "Ngắt kết nối rồi",
"swap": "Hoán đổi",
"swapAnyway": "Tiếp tục hoán đổi?",
"send": "Gửi",
"sendAnyway": "Tiếp tục gửi?",
"pool": "Chung vốn",
"betaWarning": "Dự án này đang trong giai đoạn thử nghiệm. Sử dụng có rủi ro của riêng bạn",
"input": "Đầu vào",
"output": "Đầu ra",
"estimated": "ước lượng",
"balance": "Số dư: {{ balanceInput }}",
"unlock": "Mở khóa",
"pending": "Đang chờ xử lý",
"selectToken": "Chọn một đồng tiền ảo",
"searchOrPaste": "Tìm kiếm tên, biểu tượng, hoặc địa chỉ của đồng tiền ảo",
"searchOrPasteMobile": "Tên, Biểu tương, hoặc Địa chỉ",
"noExchange": "Không Tìm Thấy Giao Dịch",
"exchangeRate": "Tỷ giá",
"unknownError": "Rất tiếc! Xảy ra lỗi không xác định. Vui lòng làm mới trang, hoặc truy cập từ trình duyệt hay thiết bị khác.",
"enterValueCont": "Nhập một giá trị {{ missingCurrencyValue }} để tiếp tục.",
"selectTokenCont": "Chọn một đồng tiền ảo để tiếp tục.",
"noLiquidity": "Không có tính thanh khoản.",
"insufficientLiquidity": "Không đủ tính thanh khoản.",
"unlockTokenCont": "Vui lòng mở khoá đồng tiền ảo để tiếp tục",
"transactionDetails": "Chi tiết nâng cao",
"hideDetails": "Che giấu chi tiết",
"slippageWarning": "Cảnh báo trượt giá",
"highSlippageWarning": "Cảnh báo trượt giá cao",
"youAreSelling": "Bạn đang bán",
"orTransFail": "hoặc giao dịch sẽ thất bại.",
"youWillReceive": "Bạn sẽ nhận dược ít nhất là",
"youAreBuying": "Bạn đang mua",
"itWillCost": "Nó sẽ có giá cao nhất",
"forAtMost": "nhiều nhất",
"insufficientBalance": "Số dư không đủ",
"inputNotValid": "Giá trị nhập vào không hợp lệ",
"differentToken": "Đồng tiền ảo phải khác nhau.",
"noRecipient": "Nhập địa chỉ ví để gửi đến.",
"invalidRecipient": "Vui lòng nhập một người nhận địa chỉ ví hợp lệ.",
"recipientAddress": "Địa chỉ người nhận",
"youAreSending": "Bạn đang gửi",
"willReceive": "sẽ nhận dược ít nhất là",
"to": "đến",
"addLiquidity": "Thêm tiền thanh khoản",
"deposit": "Gửi tiền",
"currentPoolSize": "Quy mô hiện tại của quỹ",
"yourPoolShare": "Phần hùn của bạn trong quỹ",
"noZero": "Số tiền không thể bằng không.",
"mustBeETH": "Một trong những đầu vào phải là ETH.",
"enterCurrencyOrLabelCont": "Nhập giá trị {{ inputCurrency }} hoặc {{ label }} để tiếp tục.",
"youAreAdding": "Bạn đang thêm",
"and": "và",
"intoPool": "vào nhóm thanh khoản.",
"outPool": "từ nhóm thanh khoản.",
"youWillMint": "Bạn sẽ đúc tiền",
"liquidityTokens": "dồng thanh khoản.",
"totalSupplyIs": "Tổng cung hiện tại của đồng thanh khoản là",
"youAreSettingExRate": "Bạn đang đặt tỷ giá hối đoái ban đầu thành",
"totalSupplyIs0": "Tổng cung hiện tại của đồng thanh khoản là 0.",
"tokenWorth": "Tại tỷ giá hối đoái hiện tại, giá trị đồng token của quỹ là",
"firstLiquidity": "Bạn là người đầu tiên thêm thanh khoản!",
"initialExchangeRate": "Tỷ giá hối đoái ban đầu sẽ được thiết lập dựa trên tiền gửi của bạn. Vui lòng đảm bảo rằng tiền gửi ETH và {{ label }} của bạn có cùng giá trị tiền định danh.",
"removeLiquidity": "Loại bỏ thanh khoản",
"poolTokens": "Đồng tiền ảo của quỹ",
"enterLabelCont": "Nhập giá trị {{ label }} để tiếp tục",
"youAreRemoving": "Bạn đang loại bỏ giữa",
"youWillRemove": "Bạn sẽ loại bỏ",
"createExchange": "Tạo giao dịch",
"invalidTokenAddress": "Địa chỉ đồng tiền điện tử không hợp lệ",
"exchangeExists": "{{ label }} Giao dịch đã tồn tại!",
"invalidSymbol": "Biểu tượng không hợp lệ",
"invalidDecimals": "Số thập phân không hợp lệ",
"tokenAddress": "Địa chỉ đồng tiền điện tử",
"label": "Nhãn",
"name": "Tên",
"symbol": "Biểu tượng",
"decimals": "Số thập phân",
"enterTokenCont": "Nhập địa chỉ đồng tiền ảo để tiếp tục",
"priceChange": "Trượt giá dự kiến",
"forAtLeast": "cho ít nhất "
"noWallet": "Không tìm thấy ví tiền Ethereum",
"wrongNetwork": "Kết nối mạng không đúng",
"switchNetwork": "Vui lòng chuyển sang {{ correctNetwork }}",
"installWeb3MobileBrowser": "Vui lòng truy cập từ trình duyệt di động hỗ trợ web3 như là Ví Trust hoặc Ví Coinbase",
"installMetamask": "Vui lòng truy cập sau khi cài đặt Metamask trên Chrome hoặc Brave.",
"disconnected": "Ngắt kết nối rồi",
"swap": "Hoán đổi",
"swapAnyway": "Tiếp tục hoán đổi?",
"send": "Gửi",
"sendAnyway": "Tiếp tục gửi?",
"pool": "Chung vốn",
"betaWarning": "Dự án này đang trong giai đoạn thử nghiệm. Sử dụng có rủi ro của riêng bạn",
"input": "Đầu vào",
"output": "Đầu ra",
"estimated": "ước lượng",
"balance": "Số dư: {{ balanceInput }}",
"unlock": "Mở khóa",
"pending": "Đang chờ xử lý",
"selectToken": "Chọn một đồng tiền ảo",
"searchOrPaste": "Tìm kiếm tên, biểu tượng, hoặc địa chỉ của đồng tiền ảo",
"searchOrPasteMobile": "Tên, Biểu tưng, hoặc Địa chỉ",
"noExchange": "Không tìm thấy giao dịch",
"exchangeRate": "Tỷ giá",
"unknownError": "Rất tiếc! Xảy ra lỗi không xác định. Vui lòng làm mới trang, hoặc truy cập từ trình duyệt hay thiết bị khác.",
"enterValueCont": "Nhập một giá trị {{ missingCurrencyValue }} để tiếp tục.",
"selectTokenCont": "Chọn một đồng tiền ảo để tiếp tục.",
"noLiquidity": "Không có tính thanh khoản.",
"insufficientLiquidity": "Không đủ tính thanh khoản.",
"unlockTokenCont": "Vui lòng mở khoá đồng tiền ảo để tiếp tục",
"transactionDetails": "Chi tiết nâng cao",
"hideDetails": "Ẩn chi tiết",
"slippageWarning": "Cảnh báo trượt giá",
"highSlippageWarning": "Cảnh báo trượt giá cao",
"youAreSelling": "Bạn đang bán",
"orTransFail": "hoặc giao dịch sẽ thất bại.",
"youWillReceive": "Bạn sẽ nhận dược ít nhất là",
"youAreBuying": "Bạn đang mua",
"itWillCost": "Nó sẽ có giá cao nhất",
"forAtMost": "nhiều nhất",
"insufficientBalance": "Số dư không đủ",
"inputNotValid": "Giá trị nhập vào không hợp lệ",
"differentToken": "Đồng tiền ảo phải khác nhau.",
"noRecipient": "Nhập địa chỉ ví để gửi đến.",
"invalidRecipient": "Vui lòng nhập một người nhận địa chỉ ví hợp lệ.",
"recipientAddress": "Địa chỉ người nhận",
"youAreSending": "Bạn đang gửi",
"willReceive": "sẽ nhận dược ít nhất là",
"to": "đến",
"addLiquidity": "Thêm tiền thanh khoản",
"deposit": "Gửi tiền",
"currentPoolSize": "Quy mô hiện tại của quỹ",
"yourPoolShare": "Phần hùn của bạn trong quỹ",
"noZero": "Số tiền không thể bằng không.",
"mustBeETH": "Một trong những đầu vào phải là ETH.",
"enterCurrencyOrLabelCont": "Nhập giá trị {{ inputCurrency }} hoặc {{ label }} để tiếp tục.",
"youAreAdding": "Bạn đang thêm",
"and": "và",
"intoPool": "vào nhóm thanh khoản.",
"outPool": "từ nhóm thanh khoản.",
"youWillMint": "Bạn sẽ đúc tiền",
"liquidityTokens": "đồng thanh khoản.",
"totalSupplyIs": "Tổng cung hiện tại của đồng thanh khoản là",
"youAreSettingExRate": "Bạn đang đặt tỷ giá hối đoái ban đầu thành",
"totalSupplyIs0": "Tổng cung hiện tại của đồng thanh khoản là 0.",
"tokenWorth": "Tại tỷ giá hối đoái hiện tại, giá trị đồng token của quỹ là",
"firstLiquidity": "Bạn là người đầu tiên thêm thanh khoản!",
"initialExchangeRate": "Tỷ giá hối đoái ban đầu sẽ được thiết lập dựa trên tiền gửi của bạn. Vui lòng đảm bảo rằng tiền gửi ETH và {{ label }} của bạn có cùng giá trị tiền định danh.",
"removeLiquidity": "Loại bỏ thanh khoản",
"poolTokens": "Đồng tiền ảo của quỹ",
"enterLabelCont": "Nhập giá trị {{ label }} để tiếp tục",
"youAreRemoving": "Bạn đang loại bỏ giữa",
"youWillRemove": "Bạn sẽ loại bỏ",
"createExchange": "Tạo giao dịch",
"invalidTokenAddress": "Địa chỉ đồng tiền điện tử không hợp lệ",
"exchangeExists": "{{ label }} Giao dịch đã tồn tại!",
"invalidSymbol": "Biểu tượng không hợp lệ",
"invalidDecimals": "Số thập phân không hợp lệ",
"tokenAddress": "Địa chỉ đồng tiền điện tử",
"label": "Nhãn",
"name": "Tên",
"symbol": "Biểu tượng",
"decimals": "Số thập phân",
"enterTokenCont": "Nhập địa chỉ đồng tiền ảo để tiếp tục",
"priceChange": "Trượt giá dự kiến",
"forAtLeast": "cho ít nhất "
}

View File

@@ -1,15 +1,24 @@
{
"short_name": "Uniswap",
"name": "Uniswap Exchange",
"background_color": "#fff",
"display": "standalone",
"homepage_url": "https://app.uniswap.org",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
"src": "./images/192x192_App_Icon.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "./images/512x512_App_Icon.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"orientation": "portrait",
"name": "Uniswap",
"short_name": "Uniswap",
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
"theme_color": "#ff007a"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 KiB

View File

@@ -0,0 +1,3 @@
<svg width="94" height="94" viewBox="0 0 94 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M92 47C92 22.1472 71.8528 2 47 2C22.1472 2 2 22.1472 2 47C2 71.8528 22.1472 92 47 92" stroke="#2172E5" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="417px" viewBox="0 0 256 417" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<polygon fill="#343434" points="127.9611 0 125.1661 9.5 125.1661 285.168 127.9611 287.958 255.9231 212.32"/>
<polygon fill="#8C8C8C" points="127.962 0 0 212.32 127.962 287.959 127.962 154.158"/>
<polygon fill="#3C3C3B" points="127.9611 312.1866 126.3861 314.1066 126.3861 412.3056 127.9611 416.9066 255.9991 236.5866"/>
<polygon fill="#8C8C8C" points="127.962 416.9052 127.962 312.1852 0 236.5852"/>
<polygon fill="#141414" points="127.9611 287.9577 255.9211 212.3207 127.9611 154.1587"/>
<polygon fill="#393939" points="0.0009 212.3208 127.9609 287.9578 127.9609 154.1588"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 840 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 24 24" focusable="false" role="presentation" aria-hidden="true" class="css-yyruks"><g fill="none" stroke="currentColor" stroke-linecap="full" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><path d="M15 3h6v6"></path><path d="M10 14L21 3"></path></g></svg>

After

Width:  |  Height:  |  Size: 317 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="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19 13C19.5523 13 20 12.5523 20 12C20 11.4477 19.5523 11 19 11C18.4477 11 18 11.4477 18 12C18 12.5523 18.4477 13 19 13Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 13C5.55228 13 6 12.5523 6 12C6 11.4477 5.55228 11 5 11C4.44772 11 4 11.4477 4 12C4 12.5523 4.44772 13 5 13Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 727 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="8" fill="#E1E1E1"/>
<path d="M7.09618 9.67828H8.18831V9.60573C8.20358 8.72745 8.45561 8.33413 9.10477 7.93317C9.78831 7.52076 10.2084 6.94033 10.2084 6.08115C10.2084 4.8401 9.26897 4 7.86754 4C6.58067 4 5.54964 4.75227 5.5 6.12697H6.66086C6.70668 5.31742 7.28329 4.96229 7.86754 4.96229C8.51671 4.96229 9.04368 5.39379 9.04368 6.06969C9.04368 6.63866 8.68854 7.03962 8.23413 7.3222C7.52387 7.75752 7.10382 8.18902 7.09618 9.60573V9.67828ZM7.67279 12C8.08902 12 8.43652 11.6601 8.43652 11.2363C8.43652 10.82 8.08902 10.4764 7.67279 10.4764C7.25274 10.4764 6.90907 10.82 6.90907 11.2363C6.90907 11.6601 7.25274 12 7.67279 12Z" fill="#737373"/>
<circle cx="8" cy="8" r="8" fill="#EDEEF2"/>
<path d="M7.09618 9.67828H8.18831V9.60573C8.20358 8.72745 8.45561 8.33413 9.10477 7.93317C9.78831 7.52076 10.2084 6.94033 10.2084 6.08115C10.2084 4.8401 9.26897 4 7.86754 4C6.58067 4 5.54964 4.75227 5.5 6.12697H6.66086C6.70668 5.31742 7.28329 4.96229 7.86754 4.96229C8.51671 4.96229 9.04368 5.39379 9.04368 6.06969C9.04368 6.63866 8.68854 7.03962 8.23413 7.3222C7.52387 7.75752 7.10382 8.18902 7.09618 9.60573V9.67828ZM7.67279 12C8.08902 12 8.43652 11.6601 8.43652 11.2363C8.43652 10.82 8.08902 10.4764 7.67279 10.4764C7.25274 10.4764 6.90907 10.82 6.90907 11.2363C6.90907 11.6601 7.25274 12 7.67279 12Z" fill="#565A69"/>
</svg>

Before

Width:  |  Height:  |  Size: 770 B

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

3
src/assets/svg/QR.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 1.5H1.5V7.5H7.5V1.5ZM1.5 0H0V1.5V7.5V9H1.5H7.5H9V7.5V1.5V0H7.5H1.5ZM4.5 3H3V4.5V6H4.5H6V4.5V3H4.5ZM1.5 19.5V13.5H7.5V19.5H1.5ZM0 12H1.5H7.5H9V13.5V19.5V21H7.5H1.5H0V19.5V13.5V12ZM4.5 15H3V16.5V18H4.5H6V16.5V15H4.5ZM13.5 1.5H19.5V7.5H13.5V1.5ZM12 0H13.5H19.5H21V1.5V7.5V9H19.5H13.5H12V7.5V1.5V0ZM16.5 3H15V4.5V6H16.5H18V4.5V3H16.5ZM16.5 12H12V21H13.5V16.5H15V18H21V12H19.5V13.5H16.5V12ZM18 19.5H16.5V21H18V19.5ZM19.5 19.5H21V21H19.5V19.5Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 611 B

View File

@@ -1,12 +0,0 @@
import React from 'react'
const SVGArrowDown = props => (
<svg width="1em" height="1em" viewBox="0 0 9 10" fill="currentColor" {...props}>
<path
d="M5.298 0H4.24v7.911h-.075L1.256 4.932l-.717.735L4.769 10 9 5.667l-.718-.735-2.908 2.979h-.076V0z"
fill="currentColor"
/>
</svg>
)
export default SVGArrowDown

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52 (66869) - http://www.bohemiancoding.com/sketch -->
<title>Path</title>
<desc>Created with Sketch.</desc>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="check-circle-(1)" transform="translate(1.000000, 0.000000)" stroke="#D9EAFF" stroke-width="2">
<path d="M20,10.08 L20,11 C19.9974678,15.4286859 17.082294,19.328213 12.8353524,20.583901 C8.58841086,21.839589 4.02139355,20.1523121 1.61095509,16.4370663 C-0.799483376,12.7218205 -0.479136554,7.86363898 2.39827419,4.49707214 C5.27568494,1.13050531 10.0247126,0.0575252842 14.07,1.86" id="Path"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 911 B

13
src/assets/svg/logo.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

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

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

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

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -0,0 +1,30 @@
<svg width="225" height="225" viewBox="0 0 225 225" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M74.8125 190.529C65.7561 190.513 55.5298 183.748 51.9715 175.42L19.9417 100.456C16.3834 92.1277 20.8404 85.39 29.8968 85.4068L111.417 85.5579C120.473 85.5747 130.699 92.3395 134.258 100.668L166.288 175.632C169.846 183.96 165.389 190.697 156.332 190.681L74.8125 190.529Z" fill="#131313"/>
<path d="M92.1541 164.065C83.0977 164.049 72.8715 157.284 69.3132 148.956L28.3003 52.9672C24.7419 44.6391 29.199 37.9015 38.2554 37.9182L142.638 38.1117C151.695 38.1285 161.921 44.8933 165.479 53.2214L206.492 149.21C210.051 157.538 205.594 164.276 196.537 164.259L92.1541 164.065Z" fill="white"/>
<path d="M92.1541 164.065C83.0977 164.049 72.8715 157.284 69.3132 148.956L28.3003 52.9672C24.7419 44.6391 29.199 37.9015 38.2554 37.9182L142.638 38.1117C151.695 38.1285 161.921 44.8933 165.479 53.2214L206.492 149.21C210.051 157.538 205.594 164.276 196.537 164.259L92.1541 164.065Z" fill="url(#paint0_radial)"/>
<path d="M92.1541 164.065C83.0977 164.049 72.8715 157.284 69.3132 148.956L28.3003 52.9672C24.7419 44.6391 29.199 37.9015 38.2554 37.9182L142.638 38.1117C151.695 38.1285 161.921 44.8933 165.479 53.2214L206.492 149.21C210.051 157.538 205.594 164.276 196.537 164.259L92.1541 164.065Z" fill="url(#paint1_radial)"/>
<path d="M92.1541 164.065C83.0977 164.049 72.8715 157.284 69.3132 148.956L28.3003 52.9672C24.7419 44.6391 29.199 37.9015 38.2554 37.9182L142.638 38.1117C151.695 38.1285 161.921 44.8933 165.479 53.2214L206.492 149.21C210.051 157.538 205.594 164.276 196.537 164.259L92.1541 164.065Z" fill="url(#paint2_radial)"/>
<path d="M92.1541 164.065C83.0977 164.049 72.8715 157.284 69.3132 148.956L28.3003 52.9672C24.7419 44.6391 29.199 37.9015 38.2554 37.9182L142.638 38.1117C151.695 38.1285 161.921 44.8933 165.479 53.2214L206.492 149.21C210.051 157.538 205.594 164.276 196.537 164.259L92.1541 164.065Z" fill="url(#paint3_radial)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.958 165.95C82.7695 165.931 71.265 158.321 67.2619 148.952L26.2489 52.9632C22.2458 43.5941 27.26 36.0143 37.4485 36.0332L141.832 36.2266C152.02 36.2455 163.525 43.8559 167.528 53.225L208.541 149.214C212.544 158.583 207.53 166.163 197.341 166.144L92.958 165.95ZM71.3614 148.959C74.475 156.246 83.4229 162.166 91.3473 162.18L195.73 162.374C203.655 162.388 207.555 156.493 204.441 149.206L163.428 53.2174C160.315 45.9304 151.367 40.0111 143.442 39.9964L39.0592 39.803C31.1349 39.7883 27.2349 45.6837 30.3485 52.9708L71.3614 148.959Z" fill="#131313"/>
<path d="M68.565 53.3425C81.1781 53.3659 95.4205 62.7875 100.376 74.3862C105.332 85.985 99.1246 95.3687 86.5115 95.3454C73.8984 95.322 59.6559 85.9004 54.7001 74.3016C49.7443 62.7028 55.9518 53.3191 68.565 53.3425Z" fill="#131313"/>
<path d="M90.6891 104.981C103.302 105.004 117.545 114.425 122.5 126.024C127.456 137.623 121.249 147.007 108.636 146.983C96.0225 146.96 81.7801 137.538 76.8243 125.94C71.8685 114.341 78.076 104.957 90.6891 104.981Z" fill="#131313"/>
<path d="M147.538 105.142C160.151 105.166 174.394 114.587 179.349 126.186C184.305 137.785 178.098 147.168 165.485 147.145C152.871 147.122 138.629 137.7 133.673 126.101C128.717 114.503 134.925 105.119 147.538 105.142Z" fill="#131313"/>
<defs>
<radialGradient id="paint0_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(134.41 68.3006) rotate(-33.9533) scale(90.6795 83.3208)">
<stop offset="0.661458" stop-color="#C4FCF8"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint1_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(42.7873 129.218) rotate(-24.1606) scale(213.359 196.045)">
<stop stop-color="#FF0099" stop-opacity="0.9"/>
<stop offset="0.770833" stop-color="white" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint2_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(176.854 148.655) rotate(-53.4908) scale(107.342 98.6309)">
<stop stop-color="#FFEC43"/>
<stop offset="0.805707" stop-color="#FFF6A8" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint3_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(57.5443 53.4752) rotate(20.3896) scale(137.027 125.907)">
<stop offset="0.125" stop-color="#5886FE" stop-opacity="0.46"/>
<stop offset="0.673044" stop-color="white" stop-opacity="0"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 106 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 107 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 107 KiB

View File

@@ -1,34 +1,35 @@
import React from 'react'
import styled from 'styled-components'
import { useCopyClipboard } from '../../hooks'
import useCopyClipboard from '../../hooks/useCopyClipboard'
import { Link } from '../../theme'
import { LinkStyledButton } from '../../theme'
import { CheckCircle, Copy } from 'react-feather'
const CopyIcon = styled(Link)`
color: ${({ theme }) => theme.silverGray};
const CopyIcon = styled(LinkStyledButton)`
color: ${({ theme }) => theme.text3};
flex-shrink: 0;
margin-right: 1rem;
margin-left: 0.5rem;
display: flex;
text-decoration: none;
font-size: 0.825rem;
:hover,
:active,
:focus {
text-decoration: none;
color: ${({ theme }) => theme.doveGray};
color: ${({ theme }) => theme.text2};
}
`
const TransactionStatusText = styled.span`
margin-left: 0.25rem;
font-size: 0.825rem;
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
`
export default function CopyHelper({ toCopy }) {
export default function CopyHelper(props: { toCopy: string; children?: React.ReactNode }) {
const [isCopied, setCopied] = useCopyClipboard()
return (
<CopyIcon onClick={() => setCopied(toCopy)}>
<CopyIcon onClick={() => setCopied(props.toCopy)}>
{isCopied ? (
<TransactionStatusText>
<CheckCircle size={'16'} />
@@ -39,6 +40,7 @@ export default function CopyHelper({ toCopy }) {
<Copy size={'16'} />
</TransactionStatusText>
)}
{isCopied ? '' : props.children}
</CopyIcon>
)
}

View File

@@ -1,107 +0,0 @@
import React from 'react'
import styled, { keyframes } from 'styled-components'
import { Check } from 'react-feather'
import { useWeb3React } from '../../hooks'
import { getEtherscanLink } from '../../utils'
import { Link, Spinner } from '../../theme'
import Copy from './Copy'
import Circle from '../../assets/images/circle.svg'
import { transparentize } from 'polished'
const TransactionStatusWrapper = styled.div`
display: flex;
align-items: center;
min-width: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`
const TransactionWrapper = styled.div`
${({ theme }) => theme.flexRowNoWrap}
justify-content: space-between;
width: 100%;
margin-top: 0.75rem;
a {
/* flex: 1 1 auto; */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
max-width: 250px;
}
`
const TransactionStatusText = styled.span`
margin-left: 0.5rem;
`
const rotate = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`
const TransactionState = styled.div`
display: flex;
background-color: ${({ pending, theme }) =>
pending ? transparentize(0.95, theme.royalBlue) : transparentize(0.95, theme.connectedGreen)};
border-radius: 1.5rem;
padding: 0.5rem 0.75rem;
font-weight: 500;
font-size: 0.75rem;
border: 1px solid;
border-color: ${({ pending, theme }) =>
pending ? transparentize(0.75, theme.royalBlue) : transparentize(0.75, theme.connectedGreen)};
#pending {
animation: 2s ${rotate} linear infinite;
}
:hover {
border-color: ${({ pending, theme }) =>
pending ? transparentize(0, theme.royalBlue) : transparentize(0, theme.connectedGreen)};
}
`
const ButtonWrapper = styled.div`
a {
color: ${({ pending, theme }) => (pending ? theme.royalBlue : theme.connectedGreen)};
}
`
export default function Transaction({ hash, pending }) {
const { chainId } = useWeb3React()
return (
<TransactionWrapper key={hash}>
<TransactionStatusWrapper>
<Link href={getEtherscanLink(chainId, hash, 'transaction')}>{hash} </Link>
<Copy toCopy={hash} />
</TransactionStatusWrapper>
{pending ? (
<ButtonWrapper pending={pending}>
<Link href={getEtherscanLink(chainId, hash, 'transaction')}>
<TransactionState pending={pending}>
<Spinner src={Circle} id="pending" />
<TransactionStatusText>Pending</TransactionStatusText>
</TransactionState>
</Link>
</ButtonWrapper>
) : (
<ButtonWrapper pending={pending}>
<Link href={getEtherscanLink(chainId, hash, 'transaction')}>
<TransactionState pending={pending}>
<Check size="16" />
<TransactionStatusText>Confirmed</TransactionStatusText>
</TransactionState>
</Link>
</ButtonWrapper>
)}
</TransactionWrapper>
)
}

View File

@@ -0,0 +1,62 @@
import React from 'react'
import styled from 'styled-components'
import { CheckCircle, Triangle } from 'react-feather'
import { useActiveWeb3React } from '../../hooks'
import { getEtherscanLink } from '../../utils'
import { ExternalLink } from '../../theme'
import { useAllTransactions } from '../../state/transactions/hooks'
import { RowFixed } from '../Row'
import Loader from '../Loader'
const TransactionWrapper = styled.div``
const TransactionStatusText = styled.div`
margin-right: 0.5rem;
display: flex;
align-items: center;
:hover {
text-decoration: underline;
}
`
const TransactionState = styled(ExternalLink)<{ pending: boolean; success?: boolean }>`
display: flex;
justify-content: space-between;
align-items: center;
text-decoration: none !important;
border-radius: 0.5rem;
padding: 0.25rem 0rem;
font-weight: 500;
font-size: 0.825rem;
color: ${({ theme }) => theme.primary1};
`
const IconWrapper = styled.div<{ pending: boolean; success?: boolean }>`
color: ${({ pending, success, theme }) => (pending ? theme.primary1 : success ? theme.green1 : theme.red1)};
`
export default function Transaction({ hash }: { hash: string }) {
const { chainId } = useActiveWeb3React()
const allTransactions = useAllTransactions()
const tx = allTransactions?.[hash]
const summary = tx?.summary
const pending = !tx?.receipt
const success = !pending && tx && (tx.receipt?.status === 1 || typeof tx.receipt?.status === 'undefined')
if (!chainId) return null
return (
<TransactionWrapper>
<TransactionState href={getEtherscanLink(chainId, hash, 'transaction')} pending={pending} success={success}>
<RowFixed>
<TransactionStatusText>{summary ?? hash} </TransactionStatusText>
</RowFixed>
<IconWrapper pending={pending} success={success}>
{pending ? <Loader /> : success ? <CheckCircle size="16" /> : <Triangle size="16" />}
</IconWrapper>
</TransactionState>
</TransactionWrapper>
)
}

View File

@@ -1,377 +0,0 @@
import React from 'react'
import styled from 'styled-components'
import { useWeb3React } from '../../hooks'
import { isMobile } from 'react-device-detect'
import Copy from './Copy'
import Transaction from './Transaction'
import { SUPPORTED_WALLETS } from '../../constants'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { getEtherscanLink } from '../../utils'
import { injected, walletconnect, walletlink, fortmatic, portis } from '../../connectors'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import PortisIcon from '../../assets/images/portisIcon.png'
import Identicon from '../Identicon'
import { Link } from '../../theme'
const OptionButton = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
justify-content: center;
align-items: center;
border-radius: 20px;
border: 1px solid ${({ theme }) => theme.royalBlue};
color: ${({ theme }) => theme.royalBlue};
padding: 8px 24px;
&:hover {
border: 1px solid ${({ theme }) => theme.malibuBlue};
cursor: pointer;
}
${({ theme }) => theme.mediaWidth.upToMedium`
font-size: 12px;
`};
`
const HeaderRow = styled.div`
${({ theme }) => theme.flexRowNoWrap};
padding: 1.5rem 1.5rem;
font-weight: 500;
color: ${props => (props.color === 'blue' ? ({ theme }) => theme.royalBlue : 'inherit')};
${({ theme }) => theme.mediaWidth.upToMedium`
padding: 1rem;
`};
`
const UpperSection = styled.div`
position: relative;
background-color: ${({ theme }) => theme.concreteGray};
h5 {
margin: 0;
margin-bottom: 0.5rem;
font-size: 1rem;
font-weight: 400;
}
h5:last-child {
margin-bottom: 0px;
}
h4 {
margin-top: 0;
font-weight: 500;
}
`
const InfoCard = styled.div`
padding: 1rem;
border: 1px solid ${({ theme }) => theme.placeholderGray};
border-radius: 20px;
`
const AccountGroupingRow = styled.div`
${({ theme }) => theme.flexRowNoWrap};
justify-content: space-between;
align-items: center;
font-weight: 500;
color: ${({ theme }) => theme.textColor};
div {
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
}
&:first-of-type {
margin-bottom: 20px;
}
`
const AccountSection = styled.div`
background-color: ${({ theme }) => theme.concreteGray};
padding: 0rem 1.5rem;
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0rem 1rem 1rem 1rem;`};
`
const YourAccount = styled.div`
h5 {
margin: 0 0 1rem 0;
font-weight: 400;
}
h4 {
margin: 0;
font-weight: 500;
}
`
const GreenCircle = styled.div`
${({ theme }) => theme.flexRowNoWrap}
justify-content: center;
align-items: center;
&:first-child {
height: 8px;
width: 8px;
margin-left: 12px;
margin-right: 2px;
background-color: ${({ theme }) => theme.connectedGreen};
border-radius: 50%;
}
`
const CircleWrapper = styled.div`
color: ${({ theme }) => theme.connectedGreen};
display: flex;
justify-content: center;
align-items: center;
`
const LowerSection = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
padding: 2rem;
flex-grow: 1;
overflow: auto;
h5 {
margin: 0;
font-weight: 400;
color: ${({ theme }) => theme.doveGray};
}
`
const AccountControl = styled.div`
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
min-width: 0;
font-weight: ${({ hasENS, isENS }) => (hasENS ? (isENS ? 500 : 400) : 500)};
font-size: ${({ hasENS, isENS }) => (hasENS ? (isENS ? '1rem' : '0.8rem') : '1rem')};
a:hover {
text-decoration: underline;
}
a {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
`
const ConnectButtonRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
justify-content: center;
margin: 30px;
`
const StyledLink = styled(Link)`
color: ${({ hasENS, isENS, theme }) => (hasENS ? (isENS ? theme.royalBlue : theme.doveGray) : theme.royalBlue)};
`
const CloseIcon = styled.div`
position: absolute;
right: 1rem;
top: 14px;
&:hover {
cursor: pointer;
opacity: 0.6;
}
`
const CloseColor = styled(Close)`
path {
stroke: ${({ theme }) => theme.chaliceGray};
}
`
const WalletName = styled.div`
padding-left: 0.5rem;
width: initial;
`
const IconWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
& > img,
span {
height: ${({ size }) => (size ? size + 'px' : '32px')};
width: ${({ size }) => (size ? size + 'px' : '32px')};
}
${({ theme }) => theme.mediaWidth.upToMedium`
align-items: flex-end;
`};
`
const TransactionListWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
`
const WalletAction = styled.div`
color: ${({ theme }) => theme.chaliceGray};
margin-left: 16px;
font-weight: 400;
:hover {
cursor: pointer;
text-decoration: underline;
}
`
const MainWalletAction = styled(WalletAction)`
color: ${({ theme }) => theme.royalBlue};
`
function renderTransactions(transactions, pending) {
return (
<TransactionListWrapper>
{transactions.map((hash, i) => {
return <Transaction key={i} hash={hash} pending={pending} />
})}
</TransactionListWrapper>
)
}
export default function AccountDetails({
toggleWalletModal,
pendingTransactions,
confirmedTransactions,
ENSName,
openOptions
}) {
const { chainId, account, connector } = useWeb3React()
function formatConnectorName() {
const isMetaMask = window.ethereum && window.ethereum.isMetaMask ? true : false
const name = Object.keys(SUPPORTED_WALLETS)
.filter(
k =>
SUPPORTED_WALLETS[k].connector === connector && (connector !== injected || isMetaMask === (k === 'METAMASK'))
)
.map(k => SUPPORTED_WALLETS[k].name)[0]
return <WalletName>{name}</WalletName>
}
function getStatusIcon() {
if (connector === injected) {
return (
<IconWrapper size={16}>
<Identicon /> {formatConnectorName()}
</IconWrapper>
)
} else if (connector === walletconnect) {
return (
<IconWrapper size={16}>
<img src={WalletConnectIcon} alt={''} /> {formatConnectorName()}
</IconWrapper>
)
} else if (connector === walletlink) {
return (
<IconWrapper size={16}>
<img src={CoinbaseWalletIcon} alt={''} /> {formatConnectorName()}
</IconWrapper>
)
} else if (connector === fortmatic) {
return (
<IconWrapper size={16}>
<img src={FortmaticIcon} alt={''} /> {formatConnectorName()}
</IconWrapper>
)
} else if (connector === portis) {
return (
<>
<IconWrapper size={16}>
<img src={PortisIcon} alt={''} /> {formatConnectorName()}
<MainWalletAction
onClick={() => {
portis.portis.showPortis()
}}
>
Show Portis
</MainWalletAction>
</IconWrapper>
</>
)
}
}
return (
<>
<UpperSection>
<CloseIcon onClick={toggleWalletModal}>
<CloseColor alt={'close icon'} />
</CloseIcon>
<HeaderRow>Account</HeaderRow>
<AccountSection>
<YourAccount>
<InfoCard>
<AccountGroupingRow>
{getStatusIcon()}
<div>
{connector !== injected && connector !== walletlink && (
<WalletAction
onClick={() => {
connector.close()
}}
>
Disconnect
</WalletAction>
)}
<CircleWrapper>
<GreenCircle>
<div />
</GreenCircle>
</CircleWrapper>
</div>
</AccountGroupingRow>
<AccountGroupingRow>
{ENSName ? (
<AccountControl hasENS={!!ENSName} isENS={true}>
<StyledLink hasENS={!!ENSName} isENS={true} href={getEtherscanLink(chainId, ENSName, 'address')}>
{ENSName} {' '}
</StyledLink>
<Copy toCopy={ENSName} />
</AccountControl>
) : (
<AccountControl hasENS={!!ENSName} isENS={false}>
<StyledLink hasENS={!!ENSName} isENS={false} href={getEtherscanLink(chainId, account, 'address')}>
{account} {' '}
</StyledLink>
<Copy toCopy={account} />
</AccountControl>
)}
</AccountGroupingRow>
</InfoCard>
</YourAccount>
{!(isMobile && (window.web3 || window.ethereum)) && (
<ConnectButtonRow>
<OptionButton
onClick={() => {
openOptions()
}}
>
Connect to a different wallet
</OptionButton>
</ConnectButtonRow>
)}
</AccountSection>
</UpperSection>
{!!pendingTransactions.length || !!confirmedTransactions.length ? (
<LowerSection>
<h5>Recent Transactions</h5>
{renderTransactions(pendingTransactions, true)}
{renderTransactions(confirmedTransactions, false)}
</LowerSection>
) : (
<LowerSection>
<h5>Your transactions will appear here...</h5>
</LowerSection>
)}
</>
)
}

View File

@@ -0,0 +1,411 @@
import React, { useCallback, useContext } from 'react'
import { useDispatch } from 'react-redux'
import styled, { ThemeContext } from 'styled-components'
import { useActiveWeb3React } from '../../hooks'
import { AppDispatch } from '../../state'
import { clearAllTransactions } from '../../state/transactions/actions'
import { shortenAddress } from '../../utils'
import { AutoRow } from '../Row'
import Copy from './Copy'
import Transaction from './Transaction'
import { SUPPORTED_WALLETS } from '../../constants'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { getEtherscanLink } from '../../utils'
import { injected, walletconnect, walletlink, fortmatic, portis } from '../../connectors'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import PortisIcon from '../../assets/images/portisIcon.png'
import Identicon from '../Identicon'
import { ButtonSecondary } from '../Button'
import { ExternalLink as LinkIcon } from 'react-feather'
import { ExternalLink, LinkStyledButton, TYPE } from '../../theme'
const HeaderRow = styled.div`
${({ theme }) => theme.flexRowNoWrap};
padding: 1rem 1rem;
font-weight: 500;
color: ${props => (props.color === 'blue' ? ({ theme }) => theme.primary1 : 'inherit')};
${({ theme }) => theme.mediaWidth.upToMedium`
padding: 1rem;
`};
`
const UpperSection = styled.div`
position: relative;
h5 {
margin: 0;
margin-bottom: 0.5rem;
font-size: 1rem;
font-weight: 400;
}
h5:last-child {
margin-bottom: 0px;
}
h4 {
margin-top: 0;
font-weight: 500;
}
`
const InfoCard = styled.div`
padding: 1rem;
border: 1px solid ${({ theme }) => theme.bg3};
border-radius: 20px;
position: relative;
display: grid;
grid-row-gap: 12px;
margin-bottom: 20px;
`
const AccountGroupingRow = styled.div`
${({ theme }) => theme.flexRowNoWrap};
justify-content: space-between;
align-items: center;
font-weight: 400;
color: ${({ theme }) => theme.text1};
div {
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
}
`
const AccountSection = styled.div`
background-color: ${({ theme }) => theme.bg1};
padding: 0rem 1rem;
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0rem 1rem 1.5rem 1rem;`};
`
const YourAccount = styled.div`
h5 {
margin: 0 0 1rem 0;
font-weight: 400;
}
h4 {
margin: 0;
font-weight: 500;
}
`
const LowerSection = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
padding: 1.5rem;
flex-grow: 1;
overflow: auto;
background-color: ${({ theme }) => theme.bg2};
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
h5 {
margin: 0;
font-weight: 400;
color: ${({ theme }) => theme.text3};
}
`
const AccountControl = styled.div`
display: flex;
justify-content: space-between;
min-width: 0;
width: 100%;
font-weight: 500;
font-size: 1.25rem;
a:hover {
text-decoration: underline;
}
p {
min-width: 0;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
`
const AddressLink = styled(ExternalLink)<{ hasENS: boolean; isENS: boolean }>`
font-size: 0.825rem;
color: ${({ theme }) => theme.text3};
margin-left: 1rem;
font-size: 0.825rem;
display: flex;
:hover {
color: ${({ theme }) => theme.text2};
}
`
const CloseIcon = styled.div`
position: absolute;
right: 1rem;
top: 14px;
&:hover {
cursor: pointer;
opacity: 0.6;
}
`
const CloseColor = styled(Close)`
path {
stroke: ${({ theme }) => theme.text4};
}
`
const WalletName = styled.div`
width: initial;
font-size: 0.825rem;
font-weight: 500;
color: ${({ theme }) => theme.text3};
`
const IconWrapper = styled.div<{ size?: number }>`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
margin-right: 8px;
& > img,
span {
height: ${({ size }) => (size ? size + 'px' : '32px')};
width: ${({ size }) => (size ? size + 'px' : '32px')};
}
${({ theme }) => theme.mediaWidth.upToMedium`
align-items: flex-end;
`};
`
const TransactionListWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
`
const WalletAction = styled(ButtonSecondary)`
width: fit-content;
font-weight: 400;
margin-left: 8px;
font-size: 0.825rem;
padding: 4px 6px;
:hover {
cursor: pointer;
text-decoration: underline;
}
`
const MainWalletAction = styled(WalletAction)`
color: ${({ theme }) => theme.primary1};
`
function renderTransactions(transactions: string[]) {
return (
<TransactionListWrapper>
{transactions.map((hash, i) => {
return <Transaction key={i} hash={hash} />
})}
</TransactionListWrapper>
)
}
interface AccountDetailsProps {
toggleWalletModal: () => void
pendingTransactions: string[]
confirmedTransactions: string[]
ENSName?: string
openOptions: () => void
}
export default function AccountDetails({
toggleWalletModal,
pendingTransactions,
confirmedTransactions,
ENSName,
openOptions
}: AccountDetailsProps) {
const { chainId, account, connector } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const dispatch = useDispatch<AppDispatch>()
function formatConnectorName() {
const { ethereum } = window
const isMetaMask = !!(ethereum && ethereum.isMetaMask)
const name = Object.keys(SUPPORTED_WALLETS)
.filter(
k =>
SUPPORTED_WALLETS[k].connector === connector && (connector !== injected || isMetaMask === (k === 'METAMASK'))
)
.map(k => SUPPORTED_WALLETS[k].name)[0]
return <WalletName>Connected with {name}</WalletName>
}
function getStatusIcon() {
if (connector === injected) {
return (
<IconWrapper size={16}>
<Identicon />
</IconWrapper>
)
} else if (connector === walletconnect) {
return (
<IconWrapper size={16}>
<img src={WalletConnectIcon} alt={'wallet connect logo'} />
</IconWrapper>
)
} else if (connector === walletlink) {
return (
<IconWrapper size={16}>
<img src={CoinbaseWalletIcon} alt={'coinbase wallet logo'} />
</IconWrapper>
)
} else if (connector === fortmatic) {
return (
<IconWrapper size={16}>
<img src={FortmaticIcon} alt={'fortmatic logo'} />
</IconWrapper>
)
} else if (connector === portis) {
return (
<>
<IconWrapper size={16}>
<img src={PortisIcon} alt={'portis logo'} />
<MainWalletAction
onClick={() => {
portis.portis.showPortis()
}}
>
Show Portis
</MainWalletAction>
</IconWrapper>
</>
)
}
return null
}
const clearAllTransactionsCallback = useCallback(() => {
if (chainId) dispatch(clearAllTransactions({ chainId }))
}, [dispatch, chainId])
return (
<>
<UpperSection>
<CloseIcon onClick={toggleWalletModal}>
<CloseColor />
</CloseIcon>
<HeaderRow>Account</HeaderRow>
<AccountSection>
<YourAccount>
<InfoCard>
<AccountGroupingRow>
{formatConnectorName()}
<div>
{connector !== injected && connector !== walletlink && (
<WalletAction
style={{ fontSize: '.825rem', fontWeight: 400, marginRight: '8px' }}
onClick={() => {
;(connector as any).close()
}}
>
Disconnect
</WalletAction>
)}
<WalletAction
style={{ fontSize: '.825rem', fontWeight: 400 }}
onClick={() => {
openOptions()
}}
>
Change
</WalletAction>
</div>
</AccountGroupingRow>
<AccountGroupingRow id="web3-account-identifier-row">
<AccountControl>
{ENSName ? (
<>
<div>
{getStatusIcon()}
<p> {ENSName}</p>
</div>
</>
) : (
<>
<div>
{getStatusIcon()}
<p> {account && shortenAddress(account)}</p>
</div>
</>
)}
</AccountControl>
</AccountGroupingRow>
<AccountGroupingRow>
{ENSName ? (
<>
<AccountControl>
<div>
{account && (
<Copy toCopy={account}>
<span style={{ marginLeft: '4px' }}>Copy Address</span>
</Copy>
)}
{chainId && account && (
<AddressLink
hasENS={!!ENSName}
isENS={true}
href={chainId && getEtherscanLink(chainId, ENSName, 'address')}
>
<LinkIcon size={16} />
<span style={{ marginLeft: '4px' }}>View on Etherscan</span>
</AddressLink>
)}
</div>
</AccountControl>
</>
) : (
<>
<AccountControl>
<div>
{account && (
<Copy toCopy={account}>
<span style={{ marginLeft: '4px' }}>Copy Address</span>
</Copy>
)}
{chainId && account && (
<AddressLink
hasENS={!!ENSName}
isENS={false}
href={getEtherscanLink(chainId, account, 'address')}
>
<LinkIcon size={16} />
<span style={{ marginLeft: '4px' }}>View on Etherscan</span>
</AddressLink>
)}
</div>
</AccountControl>
</>
)}
</AccountGroupingRow>
</InfoCard>
</YourAccount>
</AccountSection>
</UpperSection>
{!!pendingTransactions.length || !!confirmedTransactions.length ? (
<LowerSection>
<AutoRow mb={'1rem'} style={{ justifyContent: 'space-between' }}>
<TYPE.body>Recent Transactions</TYPE.body>
<LinkStyledButton onClick={clearAllTransactionsCallback}>(clear all)</LinkStyledButton>
</AutoRow>
{renderTransactions(pendingTransactions)}
{renderTransactions(confirmedTransactions)}
</LowerSection>
) : (
<LowerSection>
<TYPE.body color={theme.text1}>Your transactions will appear here...</TYPE.body>
</LowerSection>
)}
</>
)
}

View File

@@ -1,191 +0,0 @@
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import { transparentize } from 'polished'
import { isAddress } from '../../utils'
import { useWeb3React, useDebounce } from '../../hooks'
const InputPanel = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadowColor)};
position: relative;
border-radius: 1.25rem;
background-color: ${({ theme }) => theme.inputBackground};
z-index: 1;
`
const ContainerRow = styled.div`
display: flex;
justify-content: center;
align-items: center;
border-radius: 1.25rem;
border: 1px solid ${({ error, theme }) => (error ? theme.salmonRed : theme.mercuryGray)};
background-color: ${({ theme }) => theme.inputBackground};
`
const InputContainer = styled.div`
flex: 1;
`
const LabelRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
color: ${({ theme }) => theme.doveGray};
font-size: 0.75rem;
line-height: 1rem;
padding: 0.75rem 1rem;
`
const LabelContainer = styled.div`
flex: 1 1 auto;
width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`
const InputRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
padding: 0.25rem 0.85rem 0.75rem;
`
const Input = styled.input`
font-size: 1rem;
outline: none;
border: none;
flex: 1 1 auto;
width: 0;
background-color: ${({ theme }) => theme.inputBackground};
color: ${({ error, theme }) => (error ? theme.salmonRed : theme.royalBlue)};
overflow: hidden;
text-overflow: ellipsis;
::placeholder {
color: ${({ theme }) => theme.placeholderGray};
}
`
export default function AddressInputPanel({ title, initialInput = '', onChange = () => {}, onError = () => {} }) {
const { t } = useTranslation()
const { library } = useWeb3React()
const [input, setInput] = useState(initialInput.address ? initialInput.address : '')
const debouncedInput = useDebounce(input, 150)
const [data, setData] = useState({ address: undefined, name: undefined })
const [error, setError] = useState(false)
// keep data and errors in sync
useEffect(() => {
onChange({ address: data.address, name: data.name })
}, [onChange, data.address, data.name])
useEffect(() => {
onError(error)
}, [onError, error])
// run parser on debounced input
useEffect(() => {
let stale = false
if (isAddress(debouncedInput)) {
try {
library
.lookupAddress(debouncedInput)
.then(name => {
if (!stale) {
// if an ENS name exists, set it as the destination
if (name) {
setInput(name)
} else {
setData({ address: debouncedInput, name: '' })
setError(null)
}
}
})
.catch(() => {
if (!stale) {
setData({ address: debouncedInput, name: '' })
setError(null)
}
})
} catch {
setData({ address: debouncedInput, name: '' })
setError(null)
}
} else {
if (debouncedInput !== '') {
try {
library
.resolveName(debouncedInput)
.then(address => {
if (!stale) {
// if the debounced input name resolves to an address
if (address) {
setData({ address: address, name: debouncedInput })
setError(null)
} else {
setError(true)
}
}
})
.catch(() => {
if (!stale) {
setError(true)
}
})
} catch {
setError(true)
}
}
}
return () => {
stale = true
}
}, [debouncedInput, library, onChange, onError])
function onInput(event) {
if (data.address !== undefined || data.name !== undefined) {
setData({ address: undefined, name: undefined })
}
if (error !== undefined) {
setError()
}
const input = event.target.value
const checksummedInput = isAddress(input)
setInput(checksummedInput || input)
}
return (
<InputPanel>
<ContainerRow error={input !== '' && error}>
<InputContainer>
<LabelRow>
<LabelContainer>
<span>{title || t('recipientAddress')}</span>
</LabelContainer>
</LabelRow>
<InputRow>
<Input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
placeholder="0x1234..."
error={input !== '' && error}
onChange={onInput}
value={input}
/>
</InputRow>
</InputContainer>
</ContainerRow>
</InputPanel>
)
}

View File

@@ -0,0 +1,128 @@
import React, { useContext, useCallback } from 'react'
import styled, { ThemeContext } from 'styled-components'
import useENS from '../../hooks/useENS'
import { useActiveWeb3React } from '../../hooks'
import { ExternalLink, TYPE } from '../../theme'
import { AutoColumn } from '../Column'
import { RowBetween } from '../Row'
import { getEtherscanLink } from '../../utils'
const InputPanel = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
position: relative;
border-radius: 1.25rem;
background-color: ${({ theme }) => theme.bg1};
z-index: 1;
width: 100%;
`
const ContainerRow = styled.div<{ error: boolean }>`
display: flex;
justify-content: center;
align-items: center;
border-radius: 1.25rem;
border: 1px solid ${({ error, theme }) => (error ? theme.red1 : theme.bg2)};
transition: border-color 300ms ${({ error }) => (error ? 'step-end' : 'step-start')},
color 500ms ${({ error }) => (error ? 'step-end' : 'step-start')};
background-color: ${({ theme }) => theme.bg1};
`
const InputContainer = styled.div`
flex: 1;
padding: 1rem;
`
const Input = styled.input<{ error?: boolean }>`
font-size: 1.25rem;
outline: none;
border: none;
flex: 1 1 auto;
width: 0;
background-color: ${({ theme }) => theme.bg1};
transition: color 300ms ${({ error }) => (error ? 'step-end' : 'step-start')};
color: ${({ error, theme }) => (error ? theme.red1 : theme.primary1)};
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
width: 100%;
::placeholder {
color: ${({ theme }) => theme.text4};
}
padding: 0px;
-webkit-appearance: textfield;
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-outer-spin-button,
::-webkit-inner-spin-button {
-webkit-appearance: none;
}
::placeholder {
color: ${({ theme }) => theme.text4};
}
`
export default function AddressInputPanel({
id,
value,
onChange
}: {
id?: string
// the typed string value
value: string
// triggers whenever the typed value changes
onChange: (value: string) => void
}) {
const { chainId } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const { address, loading, name } = useENS(value)
const handleInput = useCallback(
event => {
const input = event.target.value
const withoutSpaces = input.replace(/\s+/g, '')
onChange(withoutSpaces)
},
[onChange]
)
const error = Boolean(value.length > 0 && !loading && !address)
return (
<InputPanel id={id}>
<ContainerRow error={error}>
<InputContainer>
<AutoColumn gap="md">
<RowBetween>
<TYPE.black color={theme.text2} fontWeight={500} fontSize={14}>
Recipient
</TYPE.black>
{address && chainId && (
<ExternalLink href={getEtherscanLink(chainId, name ?? address, 'address')} style={{ fontSize: '14px' }}>
(View on Etherscan)
</ExternalLink>
)}
</RowBetween>
<Input
className="recipient-address-input"
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
placeholder="Wallet Address or ENS name"
error={error}
pattern="^(0x[a-fA-F0-9]{40})$"
onChange={handleInput}
value={value}
/>
</AutoColumn>
</InputContainer>
</ContainerRow>
</InputPanel>
)
}

View File

@@ -0,0 +1,12 @@
import React, { ReactNode, useMemo } from 'react'
import { BLOCKED_ADDRESSES } from '../../constants'
import { useActiveWeb3React } from '../../hooks'
export default function Blocklist({ children }: { children: ReactNode }) {
const { account } = useActiveWeb3React()
const blocked: boolean = useMemo(() => Boolean(account && BLOCKED_ADDRESSES.indexOf(account) !== -1), [account])
if (blocked) {
return <div>Blocked address</div>
}
return <>{children}</>
}

View File

@@ -0,0 +1,339 @@
import React from 'react'
import styled from 'styled-components'
import { darken, lighten } from 'polished'
import { RowBetween } from '../Row'
import { ChevronDown } from 'react-feather'
import { Button as RebassButton, ButtonProps } from 'rebass/styled-components'
const Base = styled(RebassButton)<{
padding?: string
width?: string
borderRadius?: string
altDisabledStyle?: boolean
}>`
padding: ${({ padding }) => (padding ? padding : '18px')};
width: ${({ width }) => (width ? width : '100%')};
font-weight: 500;
text-align: center;
border-radius: 20px;
border-radius: ${({ borderRadius }) => borderRadius && borderRadius};
outline: none;
border: 1px solid transparent;
color: white;
text-decoration: none;
display: flex;
justify-content: center;
flex-wrap: nowrap;
align-items: center;
cursor: pointer;
position: relative;
z-index: 1;
&:disabled {
cursor: auto;
}
> * {
user-select: none;
}
`
export const ButtonPrimary = styled(Base)`
background-color: ${({ theme }) => theme.primary1};
color: white;
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.primary1)};
background-color: ${({ theme }) => darken(0.05, theme.primary1)};
}
&:hover {
background-color: ${({ theme }) => darken(0.05, theme.primary1)};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.primary1)};
background-color: ${({ theme }) => darken(0.1, theme.primary1)};
}
&:disabled {
background-color: ${({ theme, altDisabledStyle, disabled }) =>
altDisabledStyle ? (disabled ? theme.bg3 : theme.primary1) : theme.bg3};
color: ${({ theme, altDisabledStyle, disabled }) =>
altDisabledStyle ? (disabled ? theme.text3 : 'white') : theme.text3};
cursor: auto;
box-shadow: none;
border: 1px solid transparent;
outline: none;
opacity: ${({ altDisabledStyle }) => (altDisabledStyle ? '0.5' : '1')};
}
`
export const ButtonLight = styled(Base)`
background-color: ${({ theme }) => theme.primary5};
color: ${({ theme }) => theme.primaryText1};
font-size: 16px;
font-weight: 500;
&:focus {
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && darken(0.03, theme.primary5)};
background-color: ${({ theme, disabled }) => !disabled && darken(0.03, theme.primary5)};
}
&:hover {
background-color: ${({ theme, disabled }) => !disabled && darken(0.03, theme.primary5)};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && darken(0.05, theme.primary5)};
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.primary5)};
}
:disabled {
opacity: 0.4;
:hover {
cursor: auto;
background-color: ${({ theme }) => theme.primary5};
box-shadow: none;
border: 1px solid transparent;
outline: none;
}
}
`
export const ButtonGray = styled(Base)`
background-color: ${({ theme }) => theme.bg3};
color: ${({ theme }) => theme.text2};
font-size: 16px;
font-weight: 500;
&:focus {
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg4)};
}
&:hover {
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg4)};
}
&:active {
background-color: ${({ theme, disabled }) => !disabled && darken(0.1, theme.bg4)};
}
`
export const ButtonSecondary = styled(Base)`
border: 1px solid ${({ theme }) => theme.primary4};
color: ${({ theme }) => theme.primary1};
background-color: transparent;
font-size: 16px;
border-radius: 12px;
padding: ${({ padding }) => (padding ? padding : '10px')};
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => theme.primary4};
border: 1px solid ${({ theme }) => theme.primary3};
}
&:hover {
border: 1px solid ${({ theme }) => theme.primary3};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme }) => theme.primary4};
border: 1px solid ${({ theme }) => theme.primary3};
}
&:disabled {
opacity: 50%;
cursor: auto;
}
a:hover {
text-decoration: none;
}
`
export const ButtonPink = styled(Base)`
background-color: ${({ theme }) => theme.primary1};
color: white;
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.primary1)};
background-color: ${({ theme }) => darken(0.05, theme.primary1)};
}
&:hover {
background-color: ${({ theme }) => darken(0.05, theme.primary1)};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.primary1)};
background-color: ${({ theme }) => darken(0.1, theme.primary1)};
}
&:disabled {
background-color: ${({ theme }) => theme.primary1};
opacity: 50%;
cursor: auto;
}
`
export const ButtonUNIGradient = styled(ButtonPrimary)`
color: white;
padding: 4px 8px;
height: 36px;
font-weight: 500;
background-color: ${({ theme }) => theme.bg3};
background: radial-gradient(174.47% 188.91% at 1.84% 0%, #ff007a 0%, #2172e5 100%), #edeef2;
width: fit-content;
position: relative;
cursor: pointer;
border: none;
white-space: no-wrap;
:hover {
opacity: 0.8;
}
:active {
opacity: 0.9;
}
`
export const ButtonOutlined = styled(Base)`
border: 1px solid ${({ theme }) => theme.bg2};
background-color: transparent;
color: ${({ theme }) => theme.text1};
&:focus {
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
}
&:hover {
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
}
&:active {
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
}
&:disabled {
opacity: 50%;
cursor: auto;
}
`
export const ButtonEmpty = styled(Base)`
background-color: transparent;
color: ${({ theme }) => theme.primary1};
display: flex;
justify-content: center;
align-items: center;
&:focus {
text-decoration: underline;
}
&:hover {
text-decoration: none;
}
&:active {
text-decoration: none;
}
&:disabled {
opacity: 50%;
cursor: auto;
}
`
export const ButtonWhite = styled(Base)`
border: 1px solid #edeef2;
background-color: ${({ theme }) => theme.bg1};
color: black;
&:focus {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
box-shadow: 0 0 0 1pt ${darken(0.05, '#edeef2')};
}
&:hover {
box-shadow: 0 0 0 1pt ${darken(0.1, '#edeef2')};
}
&:active {
box-shadow: 0 0 0 1pt ${darken(0.1, '#edeef2')};
}
&:disabled {
opacity: 50%;
cursor: auto;
}
`
const ButtonConfirmedStyle = styled(Base)`
background-color: ${({ theme }) => lighten(0.5, theme.green1)};
color: ${({ theme }) => theme.green1};
border: 1px solid ${({ theme }) => theme.green1};
&:disabled {
opacity: 50%;
cursor: auto;
}
`
const ButtonErrorStyle = styled(Base)`
background-color: ${({ theme }) => theme.red1};
border: 1px solid ${({ theme }) => theme.red1};
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.red1)};
background-color: ${({ theme }) => darken(0.05, theme.red1)};
}
&:hover {
background-color: ${({ theme }) => darken(0.05, theme.red1)};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.red1)};
background-color: ${({ theme }) => darken(0.1, theme.red1)};
}
&:disabled {
opacity: 50%;
cursor: auto;
box-shadow: none;
background-color: ${({ theme }) => theme.red1};
border: 1px solid ${({ theme }) => theme.red1};
}
`
export function ButtonConfirmed({
confirmed,
altDisabledStyle,
...rest
}: { confirmed?: boolean; altDisabledStyle?: boolean } & ButtonProps) {
if (confirmed) {
return <ButtonConfirmedStyle {...rest} />
} else {
return <ButtonPrimary {...rest} altDisabledStyle={altDisabledStyle} />
}
}
export function ButtonError({ error, ...rest }: { error?: boolean } & ButtonProps) {
if (error) {
return <ButtonErrorStyle {...rest} />
} else {
return <ButtonPrimary {...rest} />
}
}
export function ButtonDropdown({ disabled = false, children, ...rest }: { disabled?: boolean } & ButtonProps) {
return (
<ButtonPrimary {...rest} disabled={disabled}>
<RowBetween>
<div style={{ display: 'flex', alignItems: 'center' }}>{children}</div>
<ChevronDown size={24} />
</RowBetween>
</ButtonPrimary>
)
}
export function ButtonDropdownGrey({ disabled = false, children, ...rest }: { disabled?: boolean } & ButtonProps) {
return (
<ButtonGray {...rest} disabled={disabled} style={{ borderRadius: '20px' }}>
<RowBetween>
<div style={{ display: 'flex', alignItems: 'center' }}>{children}</div>
<ChevronDown size={24} />
</RowBetween>
</ButtonGray>
)
}
export function ButtonDropdownLight({ disabled = false, children, ...rest }: { disabled?: boolean } & ButtonProps) {
return (
<ButtonOutlined {...rest} disabled={disabled}>
<RowBetween>
<div style={{ display: 'flex', alignItems: 'center' }}>{children}</div>
<ChevronDown size={24} />
</RowBetween>
</ButtonOutlined>
)
}
export function ButtonRadio({ active, ...rest }: { active?: boolean } & ButtonProps) {
if (!active) {
return <ButtonWhite {...rest} />
} else {
return <ButtonPrimary {...rest} />
}
}

View File

@@ -0,0 +1,60 @@
import React from 'react'
import styled from 'styled-components'
import { CardProps, Text } from 'rebass'
import { Box } from 'rebass/styled-components'
const Card = styled(Box)<{ width?: string; padding?: string; border?: string; borderRadius?: string }>`
width: ${({ width }) => width ?? '100%'};
border-radius: 16px;
padding: 1.25rem;
padding: ${({ padding }) => padding};
border: ${({ border }) => border};
border-radius: ${({ borderRadius }) => borderRadius};
`
export default Card
export const LightCard = styled(Card)`
border: 1px solid ${({ theme }) => theme.bg2};
background-color: ${({ theme }) => theme.bg1};
`
export const LightGreyCard = styled(Card)`
background-color: ${({ theme }) => theme.bg2};
`
export const GreyCard = styled(Card)`
background-color: ${({ theme }) => theme.bg3};
`
export const OutlineCard = styled(Card)`
border: 1px solid ${({ theme }) => theme.bg3};
`
export const YellowCard = styled(Card)`
background-color: rgba(243, 132, 30, 0.05);
color: ${({ theme }) => theme.yellow2};
font-weight: 500;
`
export const PinkCard = styled(Card)`
background-color: rgba(255, 0, 122, 0.03);
color: ${({ theme }) => theme.primary1};
font-weight: 500;
`
const BlueCardStyled = styled(Card)`
background-color: ${({ theme }) => theme.primary5};
color: ${({ theme }) => theme.primary1};
border-radius: 12px;
width: fit-content;
`
export const BlueCard = ({ children, ...rest }: CardProps) => {
return (
<BlueCardStyled {...rest}>
<Text fontWeight={500} color="#2172E5">
{children}
</Text>
</BlueCardStyled>
)
}

View File

@@ -0,0 +1,23 @@
import styled from 'styled-components'
const Column = styled.div`
display: flex;
flex-direction: column;
justify-content: flex-start;
`
export const ColumnCenter = styled(Column)`
width: 100%;
align-items: center;
`
export const AutoColumn = styled.div<{
gap?: 'sm' | 'md' | 'lg' | string
justify?: 'stretch' | 'center' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'space-between'
}>`
display: grid;
grid-auto-rows: auto;
grid-row-gap: ${({ gap }) => (gap === 'sm' && '8px') || (gap === 'md' && '12px') || (gap === 'lg' && '24px') || gap};
justify-items: ${({ justify }) => justify && justify};
`
export default Column

View File

@@ -0,0 +1,32 @@
import React from 'react'
import ReactConfetti from 'react-confetti'
import { useWindowSize } from '../../hooks/useWindowSize'
// eslint-disable-next-line react/prop-types
export default function Confetti({ start, variant }: { start: boolean; variant?: string }) {
const { width, height } = useWindowSize()
const _variant = variant ? variant : height && width && height > 1.5 * width ? 'bottom' : variant
return start && width && height ? (
<ReactConfetti
style={{ zIndex: 1401 }}
numberOfPieces={400}
recycle={false}
run={true}
width={width}
height={height}
confettiSource={{
h: height,
w: width,
x: 0,
y: _variant === 'top' ? height * 0.25 : _variant === 'bottom' ? height * 0.75 : height * 0.5
}}
initialVelocityX={15}
initialVelocityY={30}
gravity={0.45}
tweenDuration={100}
wind={0.05}
/>
) : null
}

View File

@@ -1,128 +0,0 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import ReactGA from 'react-ga'
import { ReactComponent as Dropup } from '../../assets/images/dropup-blue.svg'
import { ReactComponent as Dropdown } from '../../assets/images/dropdown-blue.svg'
const SummaryWrapper = styled.div`
color: ${({ error, theme }) => (error ? theme.salmonRed : theme.doveGray)};
font-size: 0.75rem;
text-align: center;
margin-top: 1rem;
padding-top: 1rem;
`
const Details = styled.div`
background-color: ${({ theme }) => theme.concreteGray};
padding: 1.5rem;
border-radius: 1rem;
font-size: 0.75rem;
margin-top: 1rem;
`
const SummaryWrapperContainer = styled.div`
${({ theme }) => theme.flexRowNoWrap};
color: ${({ theme }) => theme.royalBlue};
text-align: center;
margin-top: 1rem;
padding-top: 1rem;
cursor: pointer;
align-items: center;
justify-content: center;
font-size: 0.75rem;
span {
margin-right: 12px;
}
img {
height: 0.75rem;
width: 0.75rem;
}
`
const WrappedDropup = ({ isError, highSlippageWarning, ...rest }) => <Dropup {...rest} />
const ColoredDropup = styled(WrappedDropup)`
path {
stroke: ${({ theme }) => theme.royalBlue};
}
`
const WrappedDropdown = ({ isError, highSlippageWarning, ...rest }) => <Dropdown {...rest} />
const ColoredDropdown = styled(WrappedDropdown)`
path {
stroke: ${({ theme }) => theme.royalBlue};
}
`
class ContextualInfo extends Component {
static propTypes = {
openDetailsText: PropTypes.string,
renderTransactionDetails: PropTypes.func,
contextualInfo: PropTypes.string,
isError: PropTypes.bool
}
static defaultProps = {
openDetailsText: 'Advanced Details',
closeDetailsText: 'Hide Advanced',
renderTransactionDetails() {},
contextualInfo: '',
isError: false
}
state = {
showDetails: false
}
renderDetails() {
if (!this.state.showDetails) {
return null
}
return <Details>{this.props.renderTransactionDetails()}</Details>
}
render() {
const { openDetailsText, closeDetailsText, contextualInfo, isError } = this.props
if (contextualInfo) {
return <SummaryWrapper error={isError}>{contextualInfo}</SummaryWrapper>
}
return (
<>
<SummaryWrapperContainer
onClick={() => {
!this.state.showDetails &&
ReactGA.event({
category: 'Advanced Interaction',
action: 'Open Advanced Details',
label: 'Pool Page Details'
})
this.setState(prevState => {
return { showDetails: !prevState.showDetails }
})
}}
>
{!this.state.showDetails ? (
<>
<span>{openDetailsText}</span>
<ColoredDropup />
</>
) : (
<>
<span>{closeDetailsText}</span>
<ColoredDropdown />
</>
)}
</SummaryWrapperContainer>
{this.renderDetails()}
</>
)
}
}
export default ContextualInfo

View File

@@ -1,133 +0,0 @@
import React, { useState } from 'react'
import styled, { css } from 'styled-components'
import { transparentize } from 'polished'
import ReactGA from 'react-ga'
import { ReactComponent as Dropup } from '../../assets/images/dropup-blue.svg'
import { ReactComponent as Dropdown } from '../../assets/images/dropdown-blue.svg'
const SummaryWrapper = styled.div`
color: ${({ error, brokenTokenWarning, theme }) => (error || brokenTokenWarning ? theme.salmonRed : theme.doveGray)};
font-size: 0.75rem;
text-align: center;
margin-top: 1rem;
padding-top: 1rem;
`
const SummaryWrapperContainer = styled.div`
${({ theme }) => theme.flexRowNoWrap};
color: ${({ theme }) => theme.royalBlue};
text-align: center;
margin-top: 1rem;
padding-top: 1rem;
cursor: pointer;
align-items: center;
justify-content: center;
font-size: 0.75rem;
img {
height: 0.75rem;
width: 0.75rem;
}
`
const Details = styled.div`
background-color: ${({ theme }) => theme.concreteGray};
/* padding: 1.25rem 1.25rem 1rem 1.25rem; */
border-radius: 1rem;
font-size: 0.75rem;
margin: 1rem 0.5rem 0 0.5rem;
`
const ErrorSpan = styled.span`
margin-right: 12px;
font-size: 0.75rem;
line-height: 0.75rem;
color: ${({ isError, theme }) => isError && theme.salmonRed};
${({ slippageWarning, highSlippageWarning, theme }) =>
highSlippageWarning
? css`
color: ${theme.salmonRed};
font-weight: 600;
`
: slippageWarning &&
css`
background-color: ${transparentize(0.6, theme.warningYellow)};
font-weight: 600;
padding: 0.25rem;
`}
`
const WrappedDropup = ({ isError, highSlippageWarning, ...rest }) => <Dropup {...rest} />
const ColoredDropup = styled(WrappedDropup)`
path {
stroke: ${({ isError, theme }) => (isError ? theme.salmonRed : theme.royalBlue)};
${({ highSlippageWarning, theme }) =>
highSlippageWarning &&
css`
stroke: ${theme.salmonRed};
`}
}
`
const WrappedDropdown = ({ isError, highSlippageWarning, ...rest }) => <Dropdown {...rest} />
const ColoredDropdown = styled(WrappedDropdown)`
path {
stroke: ${({ isError, theme }) => (isError ? theme.salmonRed : theme.royalBlue)};
${({ highSlippageWarning, theme }) =>
highSlippageWarning &&
css`
stroke: ${theme.salmonRed};
`}
}
`
export default function ContextualInfo({
openDetailsText = 'Advanced Details',
closeDetailsText = 'Hide Advanced',
contextualInfo = '',
allowExpand = false,
isError = false,
slippageWarning,
highSlippageWarning,
brokenTokenWarning,
dropDownContent
}) {
const [showDetails, setShowDetails] = useState(false)
return !allowExpand ? (
<SummaryWrapper brokenTokenWarning={brokenTokenWarning}>{contextualInfo}</SummaryWrapper>
) : (
<>
<SummaryWrapperContainer
onClick={() => {
!showDetails &&
ReactGA.event({
category: 'Advanced Interaction',
action: 'Open Advanced Details',
label: 'Swap/Send Page Details'
})
setShowDetails(s => !s)
}}
>
<>
<ErrorSpan isError={isError} slippageWarning={slippageWarning} highSlippageWarning={highSlippageWarning}>
{(slippageWarning || highSlippageWarning) && (
<span role="img" aria-label="warning">
</span>
)}
{contextualInfo ? contextualInfo : showDetails ? closeDetailsText : openDetailsText}
</ErrorSpan>
{showDetails ? (
<ColoredDropup isError={isError} highSlippageWarning={highSlippageWarning} />
) : (
<ColoredDropdown isError={isError} highSlippageWarning={highSlippageWarning} />
)}
</>
</SummaryWrapperContainer>
{showDetails && <Details>{dropDownContent()}</Details>}
</>
)
}

View File

@@ -1,684 +0,0 @@
import React, { useState, useRef, useMemo } from 'react'
import { Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { ethers } from 'ethers'
import { BigNumber } from '@uniswap/sdk'
import styled from 'styled-components'
import escapeStringRegex from 'escape-string-regexp'
import { darken } from 'polished'
import Tooltip from '@reach/tooltip'
import '@reach/tooltip/styles.css'
import { isMobile } from 'react-device-detect'
import { BorderlessInput } from '../../theme'
import { useWeb3React, useTokenContract } from '../../hooks'
import { isAddress, calculateGasMargin, formatToUsd, formatTokenBalance, formatEthBalance } from '../../utils'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import Modal from '../Modal'
import TokenLogo from '../TokenLogo'
import SearchIcon from '../../assets/images/magnifying-glass.svg'
import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions'
import { useTokenDetails, useAllTokenDetails, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
import { useAddressBalance } from '../../contexts/Balances'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { transparentize } from 'polished'
import { Spinner } from '../../theme'
import Circle from '../../assets/images/circle-grey.svg'
import { useETHPriceInUSD, useAllBalances } from '../../contexts/Balances'
const GAS_MARGIN = ethers.utils.bigNumberify(1000)
const SubCurrencySelect = styled.button`
${({ theme }) => theme.flexRowNoWrap}
padding: 4px 50px 4px 15px;
margin-right: -40px;
line-height: 0;
height: 2rem;
align-items: center;
border-radius: 2.5rem;
outline: none;
cursor: pointer;
user-select: none;
background: ${({ theme }) => theme.zumthorBlue};
border: 1px solid ${({ theme }) => theme.royalBlue};
color: ${({ theme }) => theme.royalBlue};
`
const InputRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
padding: 0.25rem 0.85rem 0.75rem;
`
const Input = styled(BorderlessInput)`
font-size: 1.5rem;
color: ${({ error, theme }) => error && theme.salmonRed};
background-color: ${({ theme }) => theme.inputBackground};
-moz-appearance: textfield;
`
const StyledBorderlessInput = styled(BorderlessInput)`
min-height: 2.5rem;
flex-shrink: 0;
text-align: left;
padding-left: 1.6rem;
background-color: ${({ theme }) => theme.concreteGray};
`
const CurrencySelect = styled.button`
align-items: center;
font-size: 1rem;
color: ${({ selected, theme }) => (selected ? theme.textColor : theme.royalBlue)};
height: 2rem;
border: 1px solid ${({ selected, theme }) => (selected ? theme.mercuryGray : theme.royalBlue)};
border-radius: 2.5rem;
background-color: ${({ selected, theme }) => (selected ? theme.concreteGray : theme.zumthorBlue)};
outline: none;
cursor: pointer;
user-select: none;
:hover {
border: 1px solid
${({ selected, theme }) => (selected ? darken(0.1, theme.mercuryGray) : darken(0.1, theme.royalBlue))};
}
:focus {
border: 1px solid ${({ theme }) => darken(0.1, theme.royalBlue)};
}
:active {
background-color: ${({ theme }) => theme.zumthorBlue};
}
`
const Aligner = styled.span`
display: flex;
align-items: center;
justify-content: space-between;
`
const StyledDropDown = styled(DropDown)`
margin: 0 0.5rem 0 0.5rem;
height: 35%;
path {
stroke: ${({ selected, theme }) => (selected ? theme.textColor : theme.royalBlue)};
}
`
const InputPanel = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadowColor)};
position: relative;
border-radius: 1.25rem;
background-color: ${({ theme }) => theme.inputBackground};
z-index: 1;
`
const Container = styled.div`
border-radius: 1.25rem;
border: 1px solid ${({ error, theme }) => (error ? theme.salmonRed : theme.mercuryGray)};
background-color: ${({ theme }) => theme.inputBackground};
:focus-within {
border: 1px solid ${({ theme }) => theme.malibuBlue};
}
`
const LabelRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
color: ${({ theme }) => theme.doveGray};
font-size: 0.75rem;
line-height: 1rem;
padding: 0.75rem 1rem;
span:hover {
cursor: pointer;
color: ${({ theme }) => darken(0.2, theme.doveGray)};
}
`
const LabelContainer = styled.div`
flex: 1 1 auto;
width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`
const ErrorSpan = styled.span`
color: ${({ error, theme }) => error && theme.salmonRed};
:hover {
cursor: pointer;
color: ${({ error, theme }) => error && darken(0.1, theme.salmonRed)};
}
`
const TokenModal = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
width: 100%;
`
const ModalHeader = styled.div`
position: relative;
display: flex;
flex-direction: row;
align-items: center;
padding: 0px 0px 0px 1rem;
height: 60px;
`
const CloseColor = styled(Close)`
path {
stroke: ${({ theme }) => theme.textColor};
}
`
const CloseIcon = styled.div`
position: absolute;
right: 1rem;
top: 14px;
&:hover {
cursor: pointer;
opacity: 0.6;
}
`
const SearchContainer = styled.div`
${({ theme }) => theme.flexRowNoWrap}
justify-content: flex-start;
padding: 0.5rem 1.5rem;
background-color: ${({ theme }) => theme.concreteGray};
`
const TokenModalInfo = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
padding: 1rem 1.5rem;
margin: 0.25rem 0.5rem;
justify-content: center;
user-select: none;
`
const TokenList = styled.div`
flex-grow: 1;
height: 100%;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
`
const TokenModalRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
justify-content: space-between;
padding: 1rem;
cursor: pointer;
user-select: none;
#symbol {
color: ${({ theme }) => theme.doveGrey};
}
:hover {
background-color: ${({ theme }) => theme.tokenRowHover};
}
${({ theme }) => theme.mediaWidth.upToMedium`
padding: 0.8rem 1rem;
padding-right: 2rem;
`}
`
const TokenRowLeft = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items : center;
`
const TokenSymbolGroup = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
margin-left: 1rem;
`
const TokenFullName = styled.div`
color: ${({ theme }) => theme.chaliceGray};
`
const FadedSpan = styled.span`
color: ${({ theme }) => theme.royalBlue};
`
const TokenRowBalance = styled.div`
font-size: 1rem;
line-height: 20px;
`
const TokenRowUsd = styled.div`
font-size: 1rem;
line-height: 1.5rem;
color: ${({ theme }) => theme.chaliceGray};
`
const TokenRowRight = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
align-items: flex-end;
`
const StyledTokenName = styled.span`
margin: 0 0.25rem 0 0.25rem;
`
const SpinnerWrapper = styled(Spinner)`
margin: 0 0.25rem 0 0.25rem;
color: ${({ theme }) => theme.chaliceGray};
opacity: 0.6;
`
export default function CurrencyInputPanel({
onValueChange = () => {},
allBalances,
renderInput,
onCurrencySelected = () => {},
title,
description,
extraText,
extraTextClickHander = () => {},
errorMessage,
disableUnlock,
disableTokenSelect,
selectedTokenAddress = '',
showUnlock,
value,
urlAddedTokens
}) {
const { t } = useTranslation()
const [modalIsOpen, setModalIsOpen] = useState(false)
const tokenContract = useTokenContract(selectedTokenAddress)
const { exchangeAddress: selectedTokenExchangeAddress } = useTokenDetails(selectedTokenAddress)
const pendingApproval = usePendingApproval(selectedTokenAddress)
const addTransaction = useTransactionAdder()
const allTokens = useAllTokenDetails()
const { account } = useWeb3React()
const userTokenBalance = useAddressBalance(account, selectedTokenAddress)
function renderUnlockButton() {
if (disableUnlock || !showUnlock || selectedTokenAddress === 'ETH' || !selectedTokenAddress) {
return null
} else {
if (!pendingApproval) {
return (
<SubCurrencySelect
onClick={async () => {
let estimatedGas
let useUserBalance = false
estimatedGas = await tokenContract.estimate
.approve(selectedTokenExchangeAddress, ethers.constants.MaxUint256)
.catch(e => {
console.log('Error setting max token approval.')
})
if (!estimatedGas) {
// general fallback for tokens who restrict approval amounts
estimatedGas = await tokenContract.estimate.approve(selectedTokenExchangeAddress, userTokenBalance)
useUserBalance = true
}
tokenContract
.approve(
selectedTokenExchangeAddress,
useUserBalance ? userTokenBalance : ethers.constants.MaxUint256,
{
gasLimit: calculateGasMargin(estimatedGas, GAS_MARGIN)
}
)
.then(response => {
addTransaction(response, { approval: selectedTokenAddress })
})
}}
>
{t('unlock')}
</SubCurrencySelect>
)
} else {
return <SubCurrencySelect>{t('pending')}</SubCurrencySelect>
}
}
}
function _renderInput() {
if (typeof renderInput === 'function') {
return renderInput()
}
return (
<InputRow>
<Input
type="number"
min="0"
step="0.000000000000000001"
error={!!errorMessage}
placeholder="0.0"
onChange={e => onValueChange(e.target.value)}
onKeyPress={e => {
const charCode = e.which ? e.which : e.keyCode
// Prevent 'minus' character
if (charCode === 45) {
e.preventDefault()
e.stopPropagation()
}
}}
value={value}
/>
{renderUnlockButton()}
<CurrencySelect
selected={!!selectedTokenAddress}
onClick={() => {
if (!disableTokenSelect) {
setModalIsOpen(true)
}
}}
>
<Aligner>
{selectedTokenAddress ? <TokenLogo address={selectedTokenAddress} /> : null}
{
<StyledTokenName>
{(allTokens[selectedTokenAddress] && allTokens[selectedTokenAddress].symbol) || t('selectToken')}
</StyledTokenName>
}
{!disableTokenSelect && <StyledDropDown selected={!!selectedTokenAddress} />}
</Aligner>
</CurrencySelect>
</InputRow>
)
}
return (
<InputPanel>
<Container error={!!errorMessage}>
<LabelRow>
<LabelContainer>
<span>{title}</span> <span>{description}</span>
</LabelContainer>
<ErrorSpan
data-tip={'Enter max'}
error={!!errorMessage}
onClick={() => {
extraTextClickHander()
}}
>
<Tooltip
label="Enter Max"
style={{
background: 'hsla(0, 0%, 0%, 0.75)',
color: 'white',
border: 'none',
borderRadius: '24px',
padding: '0.5em 1em',
marginTop: '-64px'
}}
>
<span>{extraText}</span>
</Tooltip>
</ErrorSpan>
</LabelRow>
{_renderInput()}
</Container>
{!disableTokenSelect && (
<CurrencySelectModal
isOpen={modalIsOpen}
onDismiss={() => {
setModalIsOpen(false)
}}
urlAddedTokens={urlAddedTokens}
onTokenSelect={onCurrencySelected}
allBalances={allBalances}
/>
)}
</InputPanel>
)
}
function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, urlAddedTokens }) {
const { t } = useTranslation()
const [searchQuery, setSearchQuery] = useState('')
const { exchangeAddress } = useTokenDetails(searchQuery)
const allTokens = useAllTokenDetails()
const { account, chainId } = useWeb3React()
// BigNumber.js instance
const ethPrice = useETHPriceInUSD()
// all balances for both account and exchanges
let allBalances = useAllBalances()
const _usdAmounts = Object.keys(allTokens).map(k => {
if (ethPrice && allBalances[account] && allBalances[account][k] && allBalances[account][k].value) {
let ethRate = 1 // default for ETH
let exchangeDetails = allBalances[allTokens[k].exchangeAddress]
if (
exchangeDetails &&
exchangeDetails[k] &&
exchangeDetails[k].value &&
exchangeDetails['ETH'] &&
exchangeDetails['ETH'].value
) {
const tokenBalance = new BigNumber(exchangeDetails[k].value.toString())
const ethBalance = new BigNumber(exchangeDetails['ETH'].value.toString())
ethRate = ethBalance.div(tokenBalance)
}
const USDRate = ethPrice
.times(ethRate)
.times(new BigNumber(10).pow(allTokens[k].decimals).div(new BigNumber(10).pow(18)))
const balanceBigNumber = new BigNumber(allBalances[account][k].value.toString())
const usdBalance = balanceBigNumber.times(USDRate).div(new BigNumber(10).pow(allTokens[k].decimals))
return usdBalance
} else {
return null
}
})
const usdAmounts =
_usdAmounts &&
Object.keys(allTokens).reduce(
(accumulator, currentValue, i) => Object.assign({ [currentValue]: _usdAmounts[i] }, accumulator),
{}
)
const tokenList = useMemo(() => {
return Object.keys(allTokens)
.sort((a, b) => {
if (allTokens[a].symbol && allTokens[b].symbol) {
const aSymbol = allTokens[a].symbol.toLowerCase()
const bSymbol = allTokens[b].symbol.toLowerCase()
// pin ETH to top
if (aSymbol === 'ETH'.toLowerCase() || bSymbol === 'ETH'.toLowerCase()) {
return aSymbol === bSymbol ? 0 : aSymbol === 'ETH'.toLowerCase() ? -1 : 1
}
// then tokens with balance
if (usdAmounts[a] && !usdAmounts[b]) {
return -1
} else if (usdAmounts[b] && !usdAmounts[a]) {
return 1
}
// sort by balance
if (usdAmounts[a] && usdAmounts[b]) {
const aUSD = usdAmounts[a]
const bUSD = usdAmounts[b]
return aUSD.gt(bUSD) ? -1 : aUSD.lt(bUSD) ? 1 : 0
}
// sort alphabetically
return aSymbol < bSymbol ? -1 : aSymbol > bSymbol ? 1 : 0
} else {
return 0
}
})
.map(k => {
let balance
let usdBalance
// only update if we have data
if (k === 'ETH' && allBalances[account] && allBalances[account][k] && allBalances[account][k].value) {
balance = formatEthBalance(allBalances[account][k].value)
usdBalance = usdAmounts[k]
} else if (allBalances[account] && allBalances[account][k] && allBalances[account][k].value) {
balance = formatTokenBalance(allBalances[account][k].value, allTokens[k].decimals)
usdBalance = usdAmounts[k]
}
return {
name: allTokens[k].name,
symbol: allTokens[k].symbol,
address: k,
balance: balance,
usdBalance: usdBalance
}
})
}, [allBalances, allTokens, usdAmounts, account])
const filteredTokenList = useMemo(() => {
return tokenList.filter(tokenEntry => {
const inputIsAddress = searchQuery.slice(0, 2) === '0x'
// check the regex for each field
const regexMatches = Object.keys(tokenEntry).map(tokenEntryKey => {
// if address field only search if input starts with 0x
if (tokenEntryKey === 'address') {
return (
inputIsAddress &&
typeof tokenEntry[tokenEntryKey] === 'string' &&
!!tokenEntry[tokenEntryKey].match(new RegExp(escapeStringRegex(searchQuery), 'i'))
)
}
return (
typeof tokenEntry[tokenEntryKey] === 'string' &&
!!tokenEntry[tokenEntryKey].match(new RegExp(escapeStringRegex(searchQuery), 'i'))
)
})
return regexMatches.some(m => m)
})
}, [tokenList, searchQuery])
function _onTokenSelect(address) {
setSearchQuery('')
onTokenSelect(address)
onDismiss()
}
function renderTokenList() {
if (isAddress(searchQuery) && exchangeAddress === undefined) {
return <TokenModalInfo>Searching for Exchange...</TokenModalInfo>
}
if (isAddress(searchQuery) && exchangeAddress === ethers.constants.AddressZero) {
return (
<>
<TokenModalInfo>{t('noExchange')}</TokenModalInfo>
<TokenModalInfo>
<Link to={`/create-exchange/${searchQuery}`}>{t('createExchange')}</Link>
</TokenModalInfo>
</>
)
}
if (!filteredTokenList.length) {
return <TokenModalInfo>{t('noExchange')}</TokenModalInfo>
}
return filteredTokenList.map(({ address, symbol, name, balance, usdBalance }) => {
const urlAdded = urlAddedTokens && urlAddedTokens.hasOwnProperty(address)
const customAdded =
address !== 'ETH' &&
INITIAL_TOKENS_CONTEXT[chainId] &&
!INITIAL_TOKENS_CONTEXT[chainId].hasOwnProperty(address) &&
!urlAdded
return (
<TokenModalRow key={address} onClick={() => _onTokenSelect(address)}>
<TokenRowLeft>
<TokenLogo address={address} size={'2rem'} />
<TokenSymbolGroup>
<div>
<span id="symbol">{symbol}</span>
<FadedSpan>
{urlAdded && '(Added by URL)'} {customAdded && '(Added by user)'}
</FadedSpan>
</div>
<TokenFullName> {name}</TokenFullName>
</TokenSymbolGroup>
</TokenRowLeft>
<TokenRowRight>
{balance ? (
<TokenRowBalance>{balance && (balance > 0 || balance === '<0.0001') ? balance : '-'}</TokenRowBalance>
) : account ? (
<SpinnerWrapper src={Circle} alt="loader" />
) : (
'-'
)}
<TokenRowUsd>
{usdBalance && !usdBalance.isNaN()
? usdBalance.isZero()
? ''
: usdBalance.lt(0.01)
? '<$0.01'
: '$' + formatToUsd(usdBalance)
: ''}
</TokenRowUsd>
</TokenRowRight>
</TokenModalRow>
)
})
}
// manage focus on modal show
const inputRef = useRef()
function onInput(event) {
const input = event.target.value
const checksummedInput = isAddress(input)
setSearchQuery(checksummedInput || input)
}
function clearInputAndDismiss() {
setSearchQuery('')
onDismiss()
}
return (
<Modal
isOpen={isOpen}
onDismiss={clearInputAndDismiss}
minHeight={60}
maxHeight={50}
initialFocusRef={isMobile ? undefined : inputRef}
>
<TokenModal>
<ModalHeader>
<p>Select Token</p>
<CloseIcon onClick={clearInputAndDismiss}>
<CloseColor alt={'close icon'} />
</CloseIcon>
</ModalHeader>
<SearchContainer>
<img src={SearchIcon} alt="search" />
<StyledBorderlessInput
ref={inputRef}
type="text"
placeholder={isMobile ? t('searchOrPasteMobile') : t('searchOrPaste')}
onChange={onInput}
/>
</SearchContainer>
<TokenList>{renderTokenList()}</TokenList>
</TokenModal>
</Modal>
)
}

View File

@@ -0,0 +1,249 @@
import { Currency, Pair } from '@uniswap/sdk'
import React, { useState, useCallback } from 'react'
import styled from 'styled-components'
import { darken } from 'polished'
import { useCurrencyBalance } from '../../state/wallet/hooks'
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
import CurrencyLogo from '../CurrencyLogo'
import DoubleCurrencyLogo from '../DoubleLogo'
import { RowBetween } from '../Row'
import { TYPE } from '../../theme'
import { Input as NumericalInput } from '../NumericalInput'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import { useActiveWeb3React } from '../../hooks'
import { useTranslation } from 'react-i18next'
import useTheme from '../../hooks/useTheme'
const InputRow = styled.div<{ selected: boolean }>`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
padding: ${({ selected }) => (selected ? '0.75rem 0.5rem 0.75rem 1rem' : '0.75rem 0.75rem 0.75rem 1rem')};
`
const CurrencySelect = styled.button<{ selected: boolean }>`
align-items: center;
height: 2.2rem;
font-size: 20px;
font-weight: 500;
background-color: ${({ selected, theme }) => (selected ? theme.bg1 : theme.primary1)};
color: ${({ selected, theme }) => (selected ? theme.text1 : theme.white)};
border-radius: 12px;
box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')};
outline: none;
cursor: pointer;
user-select: none;
border: none;
padding: 0 0.5rem;
:focus,
:hover {
background-color: ${({ selected, theme }) => (selected ? theme.bg2 : darken(0.05, theme.primary1))};
}
`
const LabelRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
color: ${({ theme }) => theme.text1};
font-size: 0.75rem;
line-height: 1rem;
padding: 0.75rem 1rem 0 1rem;
span:hover {
cursor: pointer;
color: ${({ theme }) => darken(0.2, theme.text2)};
}
`
const Aligner = styled.span`
display: flex;
align-items: center;
justify-content: space-between;
`
const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
margin: 0 0.25rem 0 0.5rem;
height: 35%;
path {
stroke: ${({ selected, theme }) => (selected ? theme.text1 : theme.white)};
stroke-width: 1.5px;
}
`
const InputPanel = styled.div<{ hideInput?: boolean }>`
${({ theme }) => theme.flexColumnNoWrap}
position: relative;
border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')};
background-color: ${({ theme }) => theme.bg2};
z-index: 1;
`
const Container = styled.div<{ hideInput: boolean }>`
border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')};
border: 1px solid ${({ theme }) => theme.bg2};
background-color: ${({ theme }) => theme.bg1};
`
const StyledTokenName = styled.span<{ active?: boolean }>`
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.75rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
font-size: ${({ active }) => (active ? '20px' : '16px')};
`
const StyledBalanceMax = styled.button`
height: 28px;
background-color: ${({ theme }) => theme.primary5};
border: 1px solid ${({ theme }) => theme.primary5};
border-radius: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
margin-right: 0.5rem;
color: ${({ theme }) => theme.primaryText1};
:hover {
border: 1px solid ${({ theme }) => theme.primary1};
}
:focus {
border: 1px solid ${({ theme }) => theme.primary1};
outline: none;
}
${({ theme }) => theme.mediaWidth.upToExtraSmall`
margin-right: 0.5rem;
`};
`
interface CurrencyInputPanelProps {
value: string
onUserInput: (value: string) => void
onMax?: () => void
showMaxButton: boolean
label?: string
onCurrencySelect?: (currency: Currency) => void
currency?: Currency | null
disableCurrencySelect?: boolean
hideBalance?: boolean
pair?: Pair | null
hideInput?: boolean
otherCurrency?: Currency | null
id: string
showCommonBases?: boolean
customBalanceText?: string
}
export default function CurrencyInputPanel({
value,
onUserInput,
onMax,
showMaxButton,
label = 'Input',
onCurrencySelect,
currency,
disableCurrencySelect = false,
hideBalance = false,
pair = null, // used for double token logo
hideInput = false,
otherCurrency,
id,
showCommonBases,
customBalanceText
}: CurrencyInputPanelProps) {
const { t } = useTranslation()
const [modalOpen, setModalOpen] = useState(false)
const { account } = useActiveWeb3React()
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
const theme = useTheme()
const handleDismissSearch = useCallback(() => {
setModalOpen(false)
}, [setModalOpen])
return (
<InputPanel id={id}>
<Container hideInput={hideInput}>
{!hideInput && (
<LabelRow>
<RowBetween>
<TYPE.body color={theme.text2} fontWeight={500} fontSize={14}>
{label}
</TYPE.body>
{account && (
<TYPE.body
onClick={onMax}
color={theme.text2}
fontWeight={500}
fontSize={14}
style={{ display: 'inline', cursor: 'pointer' }}
>
{!hideBalance && !!currency && selectedCurrencyBalance
? (customBalanceText ?? 'Balance: ') + selectedCurrencyBalance?.toSignificant(6)
: ' -'}
</TYPE.body>
)}
</RowBetween>
</LabelRow>
)}
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={disableCurrencySelect}>
{!hideInput && (
<>
<NumericalInput
className="token-amount-input"
value={value}
onUserInput={val => {
onUserInput(val)
}}
/>
{account && currency && showMaxButton && label !== 'To' && (
<StyledBalanceMax onClick={onMax}>MAX</StyledBalanceMax>
)}
</>
)}
<CurrencySelect
selected={!!currency}
className="open-currency-select-button"
onClick={() => {
if (!disableCurrencySelect) {
setModalOpen(true)
}
}}
>
<Aligner>
{pair ? (
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
) : currency ? (
<CurrencyLogo currency={currency} size={'24px'} />
) : null}
{pair ? (
<StyledTokenName className="pair-name-container">
{pair?.token0.symbol}:{pair?.token1.symbol}
</StyledTokenName>
) : (
<StyledTokenName className="token-symbol-container" active={Boolean(currency && currency.symbol)}>
{(currency && currency.symbol && currency.symbol.length > 20
? currency.symbol.slice(0, 4) +
'...' +
currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length)
: currency?.symbol) || t('selectToken')}
</StyledTokenName>
)}
{!disableCurrencySelect && <StyledDropDown selected={!!currency} />}
</Aligner>
</CurrencySelect>
</InputRow>
</Container>
{!disableCurrencySelect && onCurrencySelect && (
<CurrencySearchModal
isOpen={modalOpen}
onDismiss={handleDismissSearch}
onCurrencySelect={onCurrencySelect}
selectedCurrency={currency}
otherSelectedCurrency={otherCurrency}
showCommonBases={showCommonBases}
/>
)}
</InputPanel>
)
}

View File

@@ -0,0 +1,56 @@
import { Currency, ETHER, Token } from '@uniswap/sdk'
import React, { useMemo } from 'react'
import styled from 'styled-components'
import EthereumLogo from '../../assets/images/ethereum-logo.png'
import useHttpLocations from '../../hooks/useHttpLocations'
import { WrappedTokenInfo } from '../../state/lists/hooks'
import Logo from '../Logo'
export const getTokenLogoURL = (address: string) =>
`https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${address}/logo.png`
const StyledEthereumLogo = styled.img<{ size: string }>`
width: ${({ size }) => size};
height: ${({ size }) => size};
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
border-radius: 24px;
`
const StyledLogo = styled(Logo)<{ size: string }>`
width: ${({ size }) => size};
height: ${({ size }) => size};
border-radius: ${({ size }) => size};
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
background-color: ${({ theme }) => theme.white};
`
export default function CurrencyLogo({
currency,
size = '24px',
style
}: {
currency?: Currency
size?: string
style?: React.CSSProperties
}) {
const uriLocations = useHttpLocations(currency instanceof WrappedTokenInfo ? currency.logoURI : undefined)
const srcs: string[] = useMemo(() => {
if (currency === ETHER) return []
if (currency instanceof Token) {
if (currency instanceof WrappedTokenInfo) {
return [...uriLocations, getTokenLogoURL(currency.address)]
}
return [getTokenLogoURL(currency.address)]
}
return []
}, [currency, uriLocations])
if (currency === ETHER) {
return <StyledEthereumLogo src={EthereumLogo} size={size} style={style} />
}
return <StyledLogo size={size} srcs={srcs} alt={`${currency?.symbol ?? 'token'} logo`} style={style} />
}

View File

@@ -0,0 +1,40 @@
import { Currency } from '@uniswap/sdk'
import React from 'react'
import styled from 'styled-components'
import CurrencyLogo from '../CurrencyLogo'
const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>`
position: relative;
display: flex;
flex-direction: row;
margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'};
`
interface DoubleCurrencyLogoProps {
margin?: boolean
size?: number
currency0?: Currency
currency1?: Currency
}
const HigherLogo = styled(CurrencyLogo)`
z-index: 2;
`
const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>`
position: absolute;
left: ${({ sizeraw }) => '-' + (sizeraw / 2).toString() + 'px'} !important;
`
export default function DoubleCurrencyLogo({
currency0,
currency1,
size = 16,
margin = false
}: DoubleCurrencyLogoProps) {
return (
<Wrapper sizeraw={size} margin={margin}>
{currency0 && <HigherLogo currency={currency0} size={size.toString() + 'px'} />}
{currency1 && <CoveredLogo currency={currency1} size={size.toString() + 'px'} sizeraw={size} />}
</Wrapper>
)
}

View File

@@ -1,935 +0,0 @@
import React, { useState, useReducer, useEffect } from 'react'
import ReactGA from 'react-ga'
import { createBrowserHistory } from 'history'
import { ethers } from 'ethers'
import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import { useWeb3React } from '../../hooks'
import { brokenTokens } from '../../constants'
import { amountFormatter, calculateGasMargin, isAddress } from '../../utils'
import { useExchangeContract } from '../../hooks'
import { useTokenDetails, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useAddressBalance, useExchangeReserves } from '../../contexts/Balances'
import { useAddressAllowance } from '../../contexts/Allowances'
import { useWalletModalToggle } from '../../contexts/Application'
import { Button } from '../../theme'
import CurrencyInputPanel from '../CurrencyInputPanel'
import AddressInputPanel from '../AddressInputPanel'
import OversizedPanel from '../OversizedPanel'
import TransactionDetails from '../TransactionDetails'
import ArrowDown from '../../assets/svg/SVGArrowDown'
import WarningCard from '../WarningCard'
const INPUT = 0
const OUTPUT = 1
const ETH_TO_TOKEN = 0
const TOKEN_TO_ETH = 1
const TOKEN_TO_TOKEN = 2
// denominated in bips
const ALLOWED_SLIPPAGE_DEFAULT = 50
const TOKEN_ALLOWED_SLIPPAGE_DEFAULT = 50
// 15 minutes, denominated in seconds
const DEFAULT_DEADLINE_FROM_NOW = 60 * 15
// % above the calculated gas cost that we actually send, denominated in bips
const GAS_MARGIN = ethers.utils.bigNumberify(1000)
const DownArrowBackground = styled.div`
${({ theme }) => theme.flexRowNoWrap}
justify-content: center;
align-items: center;
`
const WrappedArrowDown = ({ clickable, active, ...rest }) => <ArrowDown {...rest} />
const DownArrow = styled(WrappedArrowDown)`
color: ${({ theme, active }) => (active ? theme.royalBlue : theme.chaliceGray)};
width: 0.625rem;
height: 0.625rem;
position: relative;
padding: 0.875rem;
cursor: ${({ clickable }) => clickable && 'pointer'};
`
const ExchangeRateWrapper = styled.div`
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
color: ${({ theme }) => theme.doveGray};
font-size: 0.75rem;
padding: 0.5rem 1rem;
`
const ExchangeRate = styled.span`
flex: 1 1 auto;
width: 0;
color: ${({ theme }) => theme.doveGray};
`
const Flex = styled.div`
display: flex;
justify-content: center;
padding: 2rem;
button {
max-width: 20rem;
}
`
function calculateSlippageBounds(value, token = false, tokenAllowedSlippage, allowedSlippage) {
if (value) {
const offset = value.mul(token ? tokenAllowedSlippage : allowedSlippage).div(ethers.utils.bigNumberify(10000))
const minimum = value.sub(offset)
const maximum = value.add(offset)
return {
minimum: minimum.lt(ethers.constants.Zero) ? ethers.constants.Zero : minimum,
maximum: maximum.gt(ethers.constants.MaxUint256) ? ethers.constants.MaxUint256 : maximum
}
} else {
return {}
}
}
function getSwapType(inputCurrency, outputCurrency) {
if (!inputCurrency || !outputCurrency) {
return null
} else if (inputCurrency === 'ETH') {
return ETH_TO_TOKEN
} else if (outputCurrency === 'ETH') {
return TOKEN_TO_ETH
} else {
return TOKEN_TO_TOKEN
}
}
// this mocks the getInputPrice function, and calculates the required output
function calculateEtherTokenOutputFromInput(inputAmount, inputReserve, outputReserve) {
const inputAmountWithFee = inputAmount.mul(ethers.utils.bigNumberify(997))
const numerator = inputAmountWithFee.mul(outputReserve)
const denominator = inputReserve.mul(ethers.utils.bigNumberify(1000)).add(inputAmountWithFee)
return numerator.div(denominator)
}
// this mocks the getOutputPrice function, and calculates the required input
function calculateEtherTokenInputFromOutput(outputAmount, inputReserve, outputReserve) {
const numerator = inputReserve.mul(outputAmount).mul(ethers.utils.bigNumberify(1000))
const denominator = outputReserve.sub(outputAmount).mul(ethers.utils.bigNumberify(997))
return numerator.div(denominator).add(ethers.constants.One)
}
function getInitialSwapState(state) {
return {
independentValue: state.exactFieldURL && state.exactAmountURL ? state.exactAmountURL : '', // this is a user input
dependentValue: '', // this is a calculated number
independentField: state.exactFieldURL === 'output' ? OUTPUT : INPUT,
inputCurrency: state.inputCurrencyURL ? state.inputCurrencyURL : 'ETH',
outputCurrency: state.outputCurrencyURL
? state.outputCurrencyURL === 'ETH'
? state.inputCurrencyURL && state.inputCurrencyURL !== 'ETH'
? 'ETH'
: ''
: state.outputCurrencyURL
: state.initialCurrency
? state.initialCurrency
: ''
}
}
function swapStateReducer(state, action) {
switch (action.type) {
case 'FLIP_INDEPENDENT': {
const { independentField, inputCurrency, outputCurrency } = state
return {
...state,
dependentValue: '',
independentField: independentField === INPUT ? OUTPUT : INPUT,
inputCurrency: outputCurrency,
outputCurrency: inputCurrency
}
}
case 'SELECT_CURRENCY': {
const { inputCurrency, outputCurrency } = state
const { field, currency } = action.payload
const newInputCurrency = field === INPUT ? currency : inputCurrency
const newOutputCurrency = field === OUTPUT ? currency : outputCurrency
if (newInputCurrency === newOutputCurrency) {
return {
...state,
inputCurrency: field === INPUT ? currency : '',
outputCurrency: field === OUTPUT ? currency : ''
}
} else {
return {
...state,
inputCurrency: newInputCurrency,
outputCurrency: newOutputCurrency
}
}
}
case 'UPDATE_INDEPENDENT': {
const { field, value } = action.payload
const { dependentValue, independentValue } = state
return {
...state,
independentValue: value,
dependentValue: value === independentValue ? dependentValue : '',
independentField: field
}
}
case 'UPDATE_DEPENDENT': {
return {
...state,
dependentValue: action.payload
}
}
default: {
return getInitialSwapState()
}
}
}
function getExchangeRate(inputValue, inputDecimals, outputValue, outputDecimals, invert = false) {
try {
if (
inputValue &&
(inputDecimals || inputDecimals === 0) &&
outputValue &&
(outputDecimals || outputDecimals === 0)
) {
const factor = ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18))
if (invert) {
return inputValue
.mul(factor)
.mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(outputDecimals)))
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(inputDecimals)))
.div(outputValue)
} else {
return outputValue
.mul(factor)
.mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(inputDecimals)))
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(outputDecimals)))
.div(inputValue)
}
}
} catch {}
}
function getMarketRate(
swapType,
inputReserveETH,
inputReserveToken,
inputDecimals,
outputReserveETH,
outputReserveToken,
outputDecimals,
invert = false
) {
if (swapType === ETH_TO_TOKEN) {
return getExchangeRate(outputReserveETH, 18, outputReserveToken, outputDecimals, invert)
} else if (swapType === TOKEN_TO_ETH) {
return getExchangeRate(inputReserveToken, inputDecimals, inputReserveETH, 18, invert)
} else if (swapType === TOKEN_TO_TOKEN) {
const factor = ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18))
const firstRate = getExchangeRate(inputReserveToken, inputDecimals, inputReserveETH, 18)
const secondRate = getExchangeRate(outputReserveETH, 18, outputReserveToken, outputDecimals)
try {
return !!(firstRate && secondRate) ? firstRate.mul(secondRate).div(factor) : undefined
} catch {}
}
}
export default function ExchangePage({ initialCurrency, sending = false, params }) {
const { t } = useTranslation()
const { account, chainId, error } = useWeb3React()
const urlAddedTokens = {}
if (params.inputCurrency) {
urlAddedTokens[params.inputCurrency] = true
}
if (params.outputCurrency) {
urlAddedTokens[params.outputCurrency] = true
}
if (isAddress(initialCurrency)) {
urlAddedTokens[initialCurrency] = true
}
const addTransaction = useTransactionAdder()
// check if URL specifies valid slippage, if so use as default
const initialSlippage = (token = false) => {
let slippage = Number.parseInt(params.slippage)
if (!isNaN(slippage) && (slippage === 0 || slippage >= 1)) {
return slippage // round to match custom input availability
}
// check for token <-> token slippage option
return token ? TOKEN_ALLOWED_SLIPPAGE_DEFAULT : ALLOWED_SLIPPAGE_DEFAULT
}
// check URL params for recipient, only on send page
const initialRecipient = () => {
if (sending && params.recipient) {
return params.recipient
}
return ''
}
const [brokenTokenWarning, setBrokenTokenWarning] = useState()
const [deadlineFromNow, setDeadlineFromNow] = useState(DEFAULT_DEADLINE_FROM_NOW)
const [rawSlippage, setRawSlippage] = useState(() => initialSlippage())
const [rawTokenSlippage, setRawTokenSlippage] = useState(() => initialSlippage(true))
const allowedSlippageBig = ethers.utils.bigNumberify(rawSlippage)
const tokenAllowedSlippageBig = ethers.utils.bigNumberify(rawTokenSlippage)
// analytics
useEffect(() => {
ReactGA.pageview(window.location.pathname + window.location.search)
}, [])
// core swap state
const [swapState, dispatchSwapState] = useReducer(
swapStateReducer,
{
initialCurrency: initialCurrency,
inputCurrencyURL: params.inputCurrency,
outputCurrencyURL: params.outputCurrency,
exactFieldURL: params.exactField,
exactAmountURL: params.exactAmount
},
getInitialSwapState
)
const { independentValue, dependentValue, independentField, inputCurrency, outputCurrency } = swapState
useEffect(() => {
setBrokenTokenWarning(false)
for (let i = 0; i < brokenTokens.length; i++) {
if (
brokenTokens[i].toLowerCase() === outputCurrency.toLowerCase() ||
brokenTokens[i].toLowerCase() === inputCurrency.toLowerCase()
) {
setBrokenTokenWarning(true)
}
}
}, [outputCurrency, inputCurrency])
const [recipient, setRecipient] = useState({
address: initialRecipient(),
name: ''
})
const [recipientError, setRecipientError] = useState()
// get swap type from the currency types
const swapType = getSwapType(inputCurrency, outputCurrency)
// get decimals and exchange address for each of the currency types
const { symbol: inputSymbol, decimals: inputDecimals, exchangeAddress: inputExchangeAddress } = useTokenDetails(
inputCurrency
)
const { symbol: outputSymbol, decimals: outputDecimals, exchangeAddress: outputExchangeAddress } = useTokenDetails(
outputCurrency
)
const inputExchangeContract = useExchangeContract(inputExchangeAddress)
const outputExchangeContract = useExchangeContract(outputExchangeAddress)
const contract = swapType === ETH_TO_TOKEN ? outputExchangeContract : inputExchangeContract
// get input allowance
const inputAllowance = useAddressAllowance(account, inputCurrency, inputExchangeAddress)
// fetch reserves for each of the currency types
const { reserveETH: inputReserveETH, reserveToken: inputReserveToken } = useExchangeReserves(inputCurrency)
const { reserveETH: outputReserveETH, reserveToken: outputReserveToken } = useExchangeReserves(outputCurrency)
// get balances for each of the currency types
const inputBalance = useAddressBalance(account, inputCurrency)
const outputBalance = useAddressBalance(account, outputCurrency)
const inputBalanceFormatted = !!(inputBalance && Number.isInteger(inputDecimals))
? amountFormatter(inputBalance, inputDecimals, Math.min(4, inputDecimals))
: ''
const outputBalanceFormatted = !!(outputBalance && Number.isInteger(outputDecimals))
? amountFormatter(outputBalance, outputDecimals, Math.min(4, outputDecimals))
: ''
// compute useful transforms of the data above
const independentDecimals = independentField === INPUT ? inputDecimals : outputDecimals
const dependentDecimals = independentField === OUTPUT ? inputDecimals : outputDecimals
// declare/get parsed and formatted versions of input/output values
const [independentValueParsed, setIndependentValueParsed] = useState()
const dependentValueFormatted = !!(dependentValue && (dependentDecimals || dependentDecimals === 0))
? amountFormatter(dependentValue, dependentDecimals, Math.min(4, dependentDecimals), false)
: ''
const inputValueParsed = independentField === INPUT ? independentValueParsed : dependentValue
const inputValueFormatted = independentField === INPUT ? independentValue : dependentValueFormatted
const outputValueParsed = independentField === OUTPUT ? independentValueParsed : dependentValue
const outputValueFormatted = independentField === OUTPUT ? independentValue : dependentValueFormatted
// validate + parse independent value
const [independentError, setIndependentError] = useState()
useEffect(() => {
if (independentValue && (independentDecimals || independentDecimals === 0)) {
try {
const parsedValue = ethers.utils.parseUnits(independentValue, independentDecimals)
if (parsedValue.lte(ethers.constants.Zero) || parsedValue.gte(ethers.constants.MaxUint256)) {
throw Error()
} else {
setIndependentValueParsed(parsedValue)
setIndependentError(null)
}
} catch {
setIndependentError(t('inputNotValid'))
}
return () => {
setIndependentValueParsed()
setIndependentError()
}
}
}, [independentValue, independentDecimals, t])
// calculate slippage from target rate
const { minimum: dependentValueMinumum, maximum: dependentValueMaximum } = calculateSlippageBounds(
dependentValue,
swapType === TOKEN_TO_TOKEN,
tokenAllowedSlippageBig,
allowedSlippageBig
)
// validate input allowance + balance
const [inputError, setInputError] = useState()
const [showUnlock, setShowUnlock] = useState(false)
useEffect(() => {
const inputValueCalculation = independentField === INPUT ? independentValueParsed : dependentValueMaximum
if (inputBalance && (inputAllowance || inputCurrency === 'ETH') && inputValueCalculation) {
if (inputBalance.lt(inputValueCalculation)) {
setInputError(t('insufficientBalance'))
} else if (inputCurrency !== 'ETH' && inputAllowance.lt(inputValueCalculation)) {
setInputError(t('unlockTokenCont'))
setShowUnlock(true)
} else {
setInputError(null)
setShowUnlock(false)
}
return () => {
setInputError()
setShowUnlock(false)
}
}
}, [independentField, independentValueParsed, dependentValueMaximum, inputBalance, inputCurrency, inputAllowance, t])
// calculate dependent value
useEffect(() => {
const amount = independentValueParsed
if (swapType === ETH_TO_TOKEN) {
const reserveETH = outputReserveETH
const reserveToken = outputReserveToken
if (amount && reserveETH && reserveToken) {
try {
const calculatedDependentValue =
independentField === INPUT
? calculateEtherTokenOutputFromInput(amount, reserveETH, reserveToken)
: calculateEtherTokenInputFromOutput(amount, reserveETH, reserveToken)
if (calculatedDependentValue.lte(ethers.constants.Zero)) {
throw Error()
}
dispatchSwapState({
type: 'UPDATE_DEPENDENT',
payload: calculatedDependentValue
})
} catch {
setIndependentError(t('insufficientLiquidity'))
}
return () => {
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: '' })
}
}
} else if (swapType === TOKEN_TO_ETH) {
const reserveETH = inputReserveETH
const reserveToken = inputReserveToken
if (amount && reserveETH && reserveToken) {
try {
const calculatedDependentValue =
independentField === INPUT
? calculateEtherTokenOutputFromInput(amount, reserveToken, reserveETH)
: calculateEtherTokenInputFromOutput(amount, reserveToken, reserveETH)
if (calculatedDependentValue.lte(ethers.constants.Zero)) {
throw Error()
}
dispatchSwapState({
type: 'UPDATE_DEPENDENT',
payload: calculatedDependentValue
})
} catch {
setIndependentError(t('insufficientLiquidity'))
}
return () => {
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: '' })
}
}
} else if (swapType === TOKEN_TO_TOKEN) {
const reserveETHFirst = inputReserveETH
const reserveTokenFirst = inputReserveToken
const reserveETHSecond = outputReserveETH
const reserveTokenSecond = outputReserveToken
if (amount && reserveETHFirst && reserveTokenFirst && reserveETHSecond && reserveTokenSecond) {
try {
if (independentField === INPUT) {
const intermediateValue = calculateEtherTokenOutputFromInput(amount, reserveTokenFirst, reserveETHFirst)
if (intermediateValue.lte(ethers.constants.Zero)) {
throw Error()
}
const calculatedDependentValue = calculateEtherTokenOutputFromInput(
intermediateValue,
reserveETHSecond,
reserveTokenSecond
)
if (calculatedDependentValue.lte(ethers.constants.Zero)) {
throw Error()
}
dispatchSwapState({
type: 'UPDATE_DEPENDENT',
payload: calculatedDependentValue
})
} else {
const intermediateValue = calculateEtherTokenInputFromOutput(amount, reserveETHSecond, reserveTokenSecond)
if (intermediateValue.lte(ethers.constants.Zero)) {
throw Error()
}
const calculatedDependentValue = calculateEtherTokenInputFromOutput(
intermediateValue,
reserveTokenFirst,
reserveETHFirst
)
if (calculatedDependentValue.lte(ethers.constants.Zero)) {
throw Error()
}
dispatchSwapState({
type: 'UPDATE_DEPENDENT',
payload: calculatedDependentValue
})
}
} catch {
setIndependentError(t('insufficientLiquidity'))
}
return () => {
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: '' })
}
}
}
}, [
independentValueParsed,
swapType,
outputReserveETH,
outputReserveToken,
inputReserveETH,
inputReserveToken,
independentField,
t
])
useEffect(() => {
const history = createBrowserHistory()
history.push(window.location.pathname + '')
}, [])
const [inverted, setInverted] = useState(false)
const exchangeRate = getExchangeRate(inputValueParsed, inputDecimals, outputValueParsed, outputDecimals)
const exchangeRateInverted = getExchangeRate(inputValueParsed, inputDecimals, outputValueParsed, outputDecimals, true)
const marketRate = getMarketRate(
swapType,
inputReserveETH,
inputReserveToken,
inputDecimals,
outputReserveETH,
outputReserveToken,
outputDecimals
)
const percentSlippage =
exchangeRate && marketRate && !marketRate.isZero()
? exchangeRate
.sub(marketRate)
.abs()
.mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18)))
.div(marketRate)
.sub(ethers.utils.bigNumberify(3).mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(15))))
: undefined
const percentSlippageFormatted = percentSlippage && amountFormatter(percentSlippage, 16, 2)
const slippageWarning =
percentSlippage &&
percentSlippage.gte(ethers.utils.parseEther('.05')) &&
percentSlippage.lt(ethers.utils.parseEther('.2')) // [5% - 20%)
const highSlippageWarning = percentSlippage && percentSlippage.gte(ethers.utils.parseEther('.2')) // [20+%
const isValid = sending
? exchangeRate && inputError === null && independentError === null && recipientError === null && deadlineFromNow
: exchangeRate && inputError === null && independentError === null && deadlineFromNow
const estimatedText = `(${t('estimated')})`
function formatBalance(value) {
return `Balance: ${value}`
}
async function onSwap() {
//if user changed deadline, log new one in minutes
if (deadlineFromNow !== DEFAULT_DEADLINE_FROM_NOW) {
ReactGA.event({
category: 'Advanced Interaction',
action: 'Set Custom Deadline',
value: deadlineFromNow / 60
})
}
const deadline = Math.ceil(Date.now() / 1000) + deadlineFromNow
// if user has changed slippage, log
if (swapType === TOKEN_TO_TOKEN) {
if (parseInt(tokenAllowedSlippageBig.toString()) !== TOKEN_ALLOWED_SLIPPAGE_DEFAULT) {
ReactGA.event({
category: 'Advanced Interaction',
action: 'Set Custom Slippage',
value: parseInt(tokenAllowedSlippageBig.toString())
})
}
} else {
if (parseInt(allowedSlippageBig.toString()) !== ALLOWED_SLIPPAGE_DEFAULT) {
ReactGA.event({
category: 'Advanced Interaction',
action: 'Set Custom Slippage',
value: parseInt(allowedSlippageBig.toString())
})
}
}
let estimate, method, args, value
let inputEthPerToken = 1
if (inputCurrency !== 'ETH') {
inputEthPerToken = inputReserveToken && inputReserveETH ? inputReserveETH / inputReserveToken : null
}
let ethTransactionSize = inputEthPerToken * inputValueFormatted
// params for GA event
let action = ''
let label = ''
if (independentField === INPUT) {
// set GA params
action = sending ? 'SendInput' : 'SwapInput'
label = outputCurrency
if (swapType === ETH_TO_TOKEN) {
estimate = sending ? contract.estimate.ethToTokenTransferInput : contract.estimate.ethToTokenSwapInput
method = sending ? contract.ethToTokenTransferInput : contract.ethToTokenSwapInput
args = sending ? [dependentValueMinumum, deadline, recipient.address] : [dependentValueMinumum, deadline]
value = independentValueParsed
} else if (swapType === TOKEN_TO_ETH) {
estimate = sending ? contract.estimate.tokenToEthTransferInput : contract.estimate.tokenToEthSwapInput
method = sending ? contract.tokenToEthTransferInput : contract.tokenToEthSwapInput
args = sending
? [independentValueParsed, dependentValueMinumum, deadline, recipient.address]
: [independentValueParsed, dependentValueMinumum, deadline]
value = ethers.constants.Zero
} else if (swapType === TOKEN_TO_TOKEN) {
estimate = sending ? contract.estimate.tokenToTokenTransferInput : contract.estimate.tokenToTokenSwapInput
method = sending ? contract.tokenToTokenTransferInput : contract.tokenToTokenSwapInput
args = sending
? [
independentValueParsed,
dependentValueMinumum,
ethers.constants.One,
deadline,
recipient.address,
outputCurrency
]
: [independentValueParsed, dependentValueMinumum, ethers.constants.One, deadline, outputCurrency]
value = ethers.constants.Zero
}
} else if (independentField === OUTPUT) {
// set GA params
action = sending ? 'SendOutput' : 'SwapOutput'
label = outputCurrency
if (swapType === ETH_TO_TOKEN) {
estimate = sending ? contract.estimate.ethToTokenTransferOutput : contract.estimate.ethToTokenSwapOutput
method = sending ? contract.ethToTokenTransferOutput : contract.ethToTokenSwapOutput
args = sending ? [independentValueParsed, deadline, recipient.address] : [independentValueParsed, deadline]
value = dependentValueMaximum
} else if (swapType === TOKEN_TO_ETH) {
estimate = sending ? contract.estimate.tokenToEthTransferOutput : contract.estimate.tokenToEthSwapOutput
method = sending ? contract.tokenToEthTransferOutput : contract.tokenToEthSwapOutput
args = sending
? [independentValueParsed, dependentValueMaximum, deadline, recipient.address]
: [independentValueParsed, dependentValueMaximum, deadline]
value = ethers.constants.Zero
} else if (swapType === TOKEN_TO_TOKEN) {
estimate = sending ? contract.estimate.tokenToTokenTransferOutput : contract.estimate.tokenToTokenSwapOutput
method = sending ? contract.tokenToTokenTransferOutput : contract.tokenToTokenSwapOutput
args = sending
? [
independentValueParsed,
dependentValueMaximum,
ethers.constants.MaxUint256,
deadline,
recipient.address,
outputCurrency
]
: [independentValueParsed, dependentValueMaximum, ethers.constants.MaxUint256, deadline, outputCurrency]
value = ethers.constants.Zero
}
}
const estimatedGasLimit = await estimate(...args, { value })
method(...args, {
value,
gasLimit: calculateGasMargin(estimatedGasLimit, GAS_MARGIN)
}).then(response => {
addTransaction(response)
ReactGA.event({
category: 'Transaction',
action: action,
label: label,
value: ethTransactionSize,
dimension1: response.hash
})
ReactGA.event({
category: 'Hash',
action: response.hash,
label: ethTransactionSize.toString()
})
})
}
const [customSlippageError, setcustomSlippageError] = useState('')
const toggleWalletModal = useWalletModalToggle()
const newInputDetected =
inputCurrency !== 'ETH' && inputCurrency && !INITIAL_TOKENS_CONTEXT[chainId].hasOwnProperty(inputCurrency)
const newOutputDetected =
outputCurrency !== 'ETH' && outputCurrency && !INITIAL_TOKENS_CONTEXT[chainId].hasOwnProperty(outputCurrency)
const [showInputWarning, setShowInputWarning] = useState(false)
const [showOutputWarning, setShowOutputWarning] = useState(false)
useEffect(() => {
if (newInputDetected) {
setShowInputWarning(true)
} else {
setShowInputWarning(false)
}
}, [newInputDetected, setShowInputWarning])
useEffect(() => {
if (newOutputDetected) {
setShowOutputWarning(true)
} else {
setShowOutputWarning(false)
}
}, [newOutputDetected, setShowOutputWarning])
return (
<>
{showInputWarning && (
<WarningCard
onDismiss={() => {
setShowInputWarning(false)
}}
urlAddedTokens={urlAddedTokens}
currency={inputCurrency}
/>
)}
{showOutputWarning && (
<WarningCard
onDismiss={() => {
setShowOutputWarning(false)
}}
urlAddedTokens={urlAddedTokens}
currency={outputCurrency}
/>
)}
<CurrencyInputPanel
title={t('input')}
urlAddedTokens={urlAddedTokens}
description={inputValueFormatted && independentField === OUTPUT ? estimatedText : ''}
extraText={inputBalanceFormatted && formatBalance(inputBalanceFormatted)}
extraTextClickHander={() => {
if (inputBalance && inputDecimals) {
const valueToSet = inputCurrency === 'ETH' ? inputBalance.sub(ethers.utils.parseEther('.1')) : inputBalance
if (valueToSet.gt(ethers.constants.Zero)) {
dispatchSwapState({
type: 'UPDATE_INDEPENDENT',
payload: {
value: amountFormatter(valueToSet, inputDecimals, inputDecimals, false),
field: INPUT
}
})
}
}
}}
onCurrencySelected={inputCurrency => {
dispatchSwapState({
type: 'SELECT_CURRENCY',
payload: { currency: inputCurrency, field: INPUT }
})
}}
onValueChange={inputValue => {
dispatchSwapState({
type: 'UPDATE_INDEPENDENT',
payload: { value: inputValue, field: INPUT }
})
}}
showUnlock={showUnlock}
selectedTokens={[inputCurrency, outputCurrency]}
selectedTokenAddress={inputCurrency}
value={inputValueFormatted}
errorMessage={inputError ? inputError : independentField === INPUT ? independentError : ''}
/>
<OversizedPanel>
<DownArrowBackground>
<DownArrow
onClick={() => {
dispatchSwapState({ type: 'FLIP_INDEPENDENT' })
}}
clickable
alt="swap"
active={isValid}
/>
</DownArrowBackground>
</OversizedPanel>
<CurrencyInputPanel
title={t('output')}
description={outputValueFormatted && independentField === INPUT ? estimatedText : ''}
extraText={outputBalanceFormatted && formatBalance(outputBalanceFormatted)}
urlAddedTokens={urlAddedTokens}
onCurrencySelected={outputCurrency => {
dispatchSwapState({
type: 'SELECT_CURRENCY',
payload: { currency: outputCurrency, field: OUTPUT }
})
}}
onValueChange={outputValue => {
dispatchSwapState({
type: 'UPDATE_INDEPENDENT',
payload: { value: outputValue, field: OUTPUT }
})
}}
selectedTokens={[inputCurrency, outputCurrency]}
selectedTokenAddress={outputCurrency}
value={outputValueFormatted}
errorMessage={independentField === OUTPUT ? independentError : ''}
disableUnlock
/>
{sending ? (
<>
<OversizedPanel>
<DownArrowBackground>
<DownArrow active={isValid} alt="arrow" />
</DownArrowBackground>
</OversizedPanel>
<AddressInputPanel onChange={setRecipient} onError={setRecipientError} initialInput={recipient} />
</>
) : (
''
)}
<OversizedPanel hideBottom>
<ExchangeRateWrapper
onClick={() => {
setInverted(inverted => !inverted)
}}
>
<ExchangeRate>{t('exchangeRate')}</ExchangeRate>
{inverted ? (
<span>
{exchangeRate
? `1 ${inputSymbol} = ${amountFormatter(exchangeRate, 18, 6, false)} ${outputSymbol}`
: ' - '}
</span>
) : (
<span>
{exchangeRate
? `1 ${outputSymbol} = ${amountFormatter(exchangeRateInverted, 18, 6, false)} ${inputSymbol}`
: ' - '}
</span>
)}
</ExchangeRateWrapper>
</OversizedPanel>
<TransactionDetails
account={account}
setRawSlippage={setRawSlippage}
setRawTokenSlippage={setRawTokenSlippage}
rawSlippage={rawSlippage}
slippageWarning={slippageWarning}
highSlippageWarning={highSlippageWarning}
brokenTokenWarning={brokenTokenWarning}
setDeadline={setDeadlineFromNow}
deadline={deadlineFromNow}
inputError={inputError}
independentError={independentError}
inputCurrency={inputCurrency}
outputCurrency={outputCurrency}
independentValue={independentValue}
independentValueParsed={independentValueParsed}
independentField={independentField}
INPUT={INPUT}
inputValueParsed={inputValueParsed}
outputValueParsed={outputValueParsed}
inputSymbol={inputSymbol}
outputSymbol={outputSymbol}
dependentValueMinumum={dependentValueMinumum}
dependentValueMaximum={dependentValueMaximum}
dependentDecimals={dependentDecimals}
independentDecimals={independentDecimals}
percentSlippageFormatted={percentSlippageFormatted}
setcustomSlippageError={setcustomSlippageError}
recipientAddress={recipient.address}
sending={sending}
/>
<Flex>
<Button
disabled={
brokenTokenWarning ? true : !account && !error ? false : !isValid || customSlippageError === 'invalid'
}
onClick={account && !error ? onSwap : toggleWalletModal}
warning={highSlippageWarning || customSlippageError === 'warning'}
loggedOut={!account}
>
{brokenTokenWarning
? 'Swap'
: !account
? 'Connect to a Wallet'
: sending
? highSlippageWarning || customSlippageError === 'warning'
? t('sendAnyway')
: t('send')
: highSlippageWarning || customSlippageError === 'warning'
? t('swapAnyway')
: t('swap')}
</Button>
</Flex>
</>
)
}

View File

@@ -1,118 +0,0 @@
import React from 'react'
import ReactGA from 'react-ga'
import styled from 'styled-components'
import { darken, transparentize } from 'polished'
import Toggle from 'react-switch'
import { Link } from '../../theme'
import { useDarkModeManager } from '../../contexts/LocalStorage'
const FooterFrame = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
`
const FooterElement = styled.div`
margin: 1.25rem;
display: flex;
min-width: 0;
display: flex;
align-items: center;
`
const Title = styled.div`
display: flex;
align-items: center;
color: ${({ theme }) => theme.uniswapPink};
:hover {
cursor: pointer;
}
#link {
text-decoration-color: ${({ theme }) => theme.uniswapPink};
}
#title {
display: inline;
font-size: 0.825rem;
margin-right: 12px;
font-weight: 400;
color: ${({ theme }) => theme.uniswapPink};
:hover {
color: ${({ theme }) => darken(0.2, theme.uniswapPink)};
}
}
`
const StyledToggle = styled(Toggle)`
margin-right: 24px;
.react-switch-bg[style] {
background-color: ${({ theme }) => darken(0.05, theme.inputBackground)} !important;
border: 1px solid ${({ theme }) => theme.concreteGray} !important;
}
.react-switch-handle[style] {
background-color: ${({ theme }) => theme.inputBackground};
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.93, theme.shadowColor)};
border: 1px solid ${({ theme }) => theme.mercuryGray};
border-color: ${({ theme }) => theme.mercuryGray} !important;
top: 2px !important;
}
`
const EmojiToggle = styled.span`
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-family: Arial sans-serif;
`
export default function Footer() {
const [isDark, toggleDarkMode] = useDarkModeManager()
return (
<FooterFrame>
<FooterElement>
<Title>
<Link id="link" href="https://uniswap.io/">
<h1 id="title">About</h1>
</Link>
<Link id="link" href="https://docs.uniswap.io/">
<h1 id="title">Docs</h1>
</Link>
<Link id="link" href="https://github.com/Uniswap">
<h1 id="title">Code</h1>
</Link>
</Title>
</FooterElement>
<StyledToggle
checked={!isDark}
uncheckedIcon={
<EmojiToggle role="img" aria-label="moon">
{/* eslint-disable-line jsx-a11y/accessible-emoji */}
🌙
</EmojiToggle>
}
checkedIcon={
<EmojiToggle role="img" aria-label="sun">
{/* eslint-disable-line jsx-a11y/accessible-emoji */}
{'☀️'}
</EmojiToggle>
}
onChange={() => {
ReactGA.event({
category: 'Advanced Interaction',
action: 'Toggle Theme',
label: isDark ? 'Light' : 'Dark'
})
toggleDarkMode()
}}
/>
</FooterFrame>
)
}

View File

@@ -0,0 +1,22 @@
import React from 'react'
import { CurrencyAmount, Fraction, JSBI } from '@uniswap/sdk'
const CURRENCY_AMOUNT_MIN = new Fraction(JSBI.BigInt(1), JSBI.BigInt(1000000))
export default function FormattedCurrencyAmount({
currencyAmount,
significantDigits = 4
}: {
currencyAmount: CurrencyAmount
significantDigits?: number
}) {
return (
<>
{currencyAmount.equalTo(JSBI.BigInt(0))
? '0'
: currencyAmount.greaterThan(CURRENCY_AMOUNT_MIN)
? currencyAmount.toSignificant(significantDigits)
: `<${CURRENCY_AMOUNT_MIN.toSignificant(1)}`}
</>
)
}

View File

@@ -0,0 +1,94 @@
import React, { useState, useEffect } from 'react'
import styled, { keyframes } from 'styled-components'
import { TYPE, ExternalLink } from '../../theme'
import { useBlockNumber } from '../../state/application/hooks'
import { getEtherscanLink } from '../../utils'
import { useActiveWeb3React } from '../../hooks'
const StyledPolling = styled.div`
position: fixed;
display: flex;
right: 0;
bottom: 0;
padding: 1rem;
color: white;
transition: opacity 0.25s ease;
color: ${({ theme }) => theme.green1};
:hover {
opacity: 1;
}
${({ theme }) => theme.mediaWidth.upToMedium`
display: none;
`}
`
const StyledPollingDot = styled.div`
width: 8px;
height: 8px;
min-height: 8px;
min-width: 8px;
margin-left: 0.5rem;
margin-top: 3px;
border-radius: 50%;
position: relative;
background-color: ${({ theme }) => theme.green1};
`
const rotate360 = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`
const Spinner = styled.div`
animation: ${rotate360} 1s cubic-bezier(0.83, 0, 0.17, 1) infinite;
transform: translateZ(0);
border-top: 1px solid transparent;
border-right: 1px solid transparent;
border-bottom: 1px solid transparent;
border-left: 2px solid ${({ theme }) => theme.green1};
background: transparent;
width: 14px;
height: 14px;
border-radius: 50%;
position: relative;
left: -3px;
top: -3px;
`
export default function Polling() {
const { chainId } = useActiveWeb3React()
const blockNumber = useBlockNumber()
const [isMounted, setIsMounted] = useState(true)
useEffect(
() => {
const timer1 = setTimeout(() => setIsMounted(true), 1000)
// this will clear Timeout when component unmount like in willComponentUnmount
return () => {
setIsMounted(false)
clearTimeout(timer1)
}
},
[blockNumber] //useEffect will run only one time
//if you pass a value to array, like this [data] than clearTimeout will run every time this value changes (useEffect re-run)
)
return (
<ExternalLink href={chainId && blockNumber ? getEtherscanLink(chainId, blockNumber.toString(), 'block') : ''}>
<StyledPolling>
<TYPE.small style={{ opacity: isMounted ? '0.2' : '0.6' }}>{blockNumber}</TYPE.small>
<StyledPollingDot>{!isMounted && <Spinner />}</StyledPollingDot>
</StyledPolling>
</ExternalLink>
)
}

View File

@@ -0,0 +1,47 @@
import React from 'react'
import styled from 'styled-components'
import { AlertTriangle, X } from 'react-feather'
import { useURLWarningToggle, useURLWarningVisible } from '../../state/user/hooks'
import { isMobile } from 'react-device-detect'
const PhishAlert = styled.div<{ isActive: any }>`
width: 100%;
padding: 6px 6px;
background-color: ${({ theme }) => theme.blue1};
color: white;
font-size: 11px;
justify-content: space-between;
align-items: center;
display: ${({ isActive }) => (isActive ? 'flex' : 'none')};
`
export const StyledClose = styled(X)`
:hover {
cursor: pointer;
}
`
export default function URLWarning() {
const toggleURLWarning = useURLWarningToggle()
const showURLWarning = useURLWarningVisible()
return isMobile ? (
<PhishAlert isActive={showURLWarning}>
<div style={{ display: 'flex' }}>
<AlertTriangle style={{ marginRight: 6 }} size={12} /> Make sure the URL is
<code style={{ padding: '0 4px', display: 'inline', fontWeight: 'bold' }}>app.uniswap.org</code>
</div>
<StyledClose size={12} onClick={toggleURLWarning} />
</PhishAlert>
) : window.location.hostname === 'app.uniswap.org' ? (
<PhishAlert isActive={showURLWarning}>
<div style={{ display: 'flex' }}>
<AlertTriangle style={{ marginRight: 6 }} size={12} /> Always make sure the URL is
<code style={{ padding: '0 4px', display: 'inline', fontWeight: 'bold' }}>app.uniswap.org</code> - bookmark it
to be safe.
</div>
<StyledClose size={12} onClick={toggleURLWarning} />
</PhishAlert>
) : null
}

View File

@@ -0,0 +1,127 @@
import { ChainId, TokenAmount } from '@uniswap/sdk'
import React, { useMemo } from 'react'
import { X } from 'react-feather'
import styled from 'styled-components'
import tokenLogo from '../../assets/images/token-logo.png'
import { UNI } from '../../constants'
import { useTotalSupply } from '../../data/TotalSupply'
import { useActiveWeb3React } from '../../hooks'
import { useMerkleDistributorContract } from '../../hooks/useContract'
import useCurrentBlockTimestamp from '../../hooks/useCurrentBlockTimestamp'
import { useTotalUniEarned } from '../../state/stake/hooks'
import { useAggregateUniBalance, useTokenBalance } from '../../state/wallet/hooks'
import { ExternalLink, StyledInternalLink, TYPE, UniTokenAnimated } from '../../theme'
import { computeUniCirculation } from '../../utils/computeUniCirculation'
import useUSDCPrice from '../../utils/useUSDCPrice'
import { AutoColumn } from '../Column'
import { RowBetween } from '../Row'
import { Break, CardBGImage, CardNoise, CardSection, DataCard } from '../earn/styled'
const ContentWrapper = styled(AutoColumn)`
width: 100%;
`
const ModalUpper = styled(DataCard)`
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
background: radial-gradient(76.02% 75.41% at 1.84% 0%, #ff007a 0%, #021d43 100%);
padding: 0.5rem;
`
const StyledClose = styled(X)`
position: absolute;
right: 16px;
top: 16px;
:hover {
cursor: pointer;
}
`
/**
* Content for balance stats modal
*/
export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowUniBalanceModal: any }) {
const { account, chainId } = useActiveWeb3React()
const uni = chainId ? UNI[chainId] : undefined
const total = useAggregateUniBalance()
const uniBalance: TokenAmount | undefined = useTokenBalance(account ?? undefined, uni)
const uniToClaim: TokenAmount | undefined = useTotalUniEarned()
const totalSupply: TokenAmount | undefined = useTotalSupply(uni)
const uniPrice = useUSDCPrice(uni)
const blockTimestamp = useCurrentBlockTimestamp()
const unclaimedUni = useTokenBalance(useMerkleDistributorContract()?.address, uni)
const circulation: TokenAmount | undefined = useMemo(
() =>
blockTimestamp && uni && chainId === ChainId.MAINNET
? computeUniCirculation(uni, blockTimestamp, unclaimedUni)
: totalSupply,
[blockTimestamp, chainId, totalSupply, unclaimedUni, uni]
)
return (
<ContentWrapper gap="lg">
<ModalUpper>
<CardBGImage />
<CardNoise />
<CardSection gap="md">
<RowBetween>
<TYPE.white color="white">Your UNI Breakdown</TYPE.white>
<StyledClose stroke="white" onClick={() => setShowUniBalanceModal(false)} />
</RowBetween>
</CardSection>
<Break />
{account && (
<>
<CardSection gap="sm">
<AutoColumn gap="md" justify="center">
<UniTokenAnimated width="48px" src={tokenLogo} />{' '}
<TYPE.white fontSize={48} fontWeight={600} color="white">
{total?.toFixed(2, { groupSeparator: ',' })}
</TYPE.white>
</AutoColumn>
<AutoColumn gap="md">
<RowBetween>
<TYPE.white color="white">Balance:</TYPE.white>
<TYPE.white color="white">{uniBalance?.toFixed(2, { groupSeparator: ',' })}</TYPE.white>
</RowBetween>
<RowBetween>
<TYPE.white color="white">Unclaimed:</TYPE.white>
<TYPE.white color="white">
{uniToClaim?.toFixed(4, { groupSeparator: ',' })}{' '}
{uniToClaim && uniToClaim.greaterThan('0') && (
<StyledInternalLink onClick={() => setShowUniBalanceModal(false)} to="/uni">
(claim)
</StyledInternalLink>
)}
</TYPE.white>
</RowBetween>
</AutoColumn>
</CardSection>
<Break />
</>
)}
<CardSection gap="sm">
<AutoColumn gap="md">
<RowBetween>
<TYPE.white color="white">UNI price:</TYPE.white>
<TYPE.white color="white">${uniPrice?.toFixed(2) ?? '-'}</TYPE.white>
</RowBetween>
<RowBetween>
<TYPE.white color="white">UNI in circulation:</TYPE.white>
<TYPE.white color="white">{circulation?.toFixed(0, { groupSeparator: ',' })}</TYPE.white>
</RowBetween>
<RowBetween>
<TYPE.white color="white">Total Supply</TYPE.white>
<TYPE.white color="white">{totalSupply?.toFixed(0, { groupSeparator: ',' })}</TYPE.white>
</RowBetween>
{uni && uni.chainId === ChainId.MAINNET ? (
<ExternalLink href={`https://uniswap.info/token/${uni.address}`}>View UNI Analytics</ExternalLink>
) : null}
</AutoColumn>
</CardSection>
</ModalUpper>
</ContentWrapper>
)
}

View File

@@ -1,77 +0,0 @@
import React from 'react'
import styled from 'styled-components'
import { Link } from '../../theme'
import Web3Status from '../Web3Status'
import { darken } from 'polished'
const HeaderFrame = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
`
const HeaderElement = styled.div`
margin: 1.25rem;
display: flex;
min-width: 0;
display: flex;
align-items: center;
`
const Nod = styled.span`
transform: rotate(0deg);
transition: transform 150ms ease-out;
:hover {
transform: rotate(-10deg);
}
`
const Title = styled.div`
display: flex;
align-items: center;
:hover {
cursor: pointer;
}
#link {
text-decoration-color: ${({ theme }) => theme.UniswapPink};
}
#title {
display: inline;
font-size: 1rem;
font-weight: 500;
color: ${({ theme }) => theme.wisteriaPurple};
:hover {
color: ${({ theme }) => darken(0.1, theme.wisteriaPurple)};
}
}
`
export default function Header() {
return (
<HeaderFrame>
<HeaderElement>
<Title>
<Nod>
<Link id="link" href="https://uniswap.io">
<span role="img" aria-label="unicorn">
🦄{' '}
</span>
</Link>
</Nod>
<Link id="link" href="https://uniswap.io">
<h1 id="title">Uniswap</h1>
</Link>
</Title>
</HeaderElement>
<HeaderElement>
<Web3Status />
</HeaderElement>
</HeaderFrame>
)
}

View File

@@ -0,0 +1,420 @@
import { ChainId, TokenAmount } from '@uniswap/sdk'
import React, { useState } from 'react'
import { Text } from 'rebass'
import { NavLink } from 'react-router-dom'
import { darken } from 'polished'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import Logo from '../../assets/svg/logo.svg'
import LogoDark from '../../assets/svg/logo_white.svg'
import { useActiveWeb3React } from '../../hooks'
import { useDarkModeManager } from '../../state/user/hooks'
import { useETHBalances, useAggregateUniBalance } from '../../state/wallet/hooks'
import { CardNoise } from '../earn/styled'
import { CountUp } from 'use-count-up'
import { TYPE, ExternalLink } from '../../theme'
import { YellowCard } from '../Card'
import { Moon, Sun } from 'react-feather'
import Menu from '../Menu'
import Row, { RowFixed } from '../Row'
import Web3Status from '../Web3Status'
import ClaimModal from '../claim/ClaimModal'
import { useToggleSelfClaimModal, useShowClaimPopup } from '../../state/application/hooks'
import { useUserHasAvailableClaim } from '../../state/claim/hooks'
import { useUserHasSubmittedClaim } from '../../state/transactions/hooks'
import { Dots } from '../swap/styleds'
import Modal from '../Modal'
import UniBalanceContent from './UniBalanceContent'
import usePrevious from '../../hooks/usePrevious'
const HeaderFrame = styled.div`
display: grid;
grid-template-columns: 1fr 120px;
align-items: center;
justify-content: space-between;
align-items: center;
flex-direction: row;
width: 100%;
top: 0;
position: relative;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
padding: 1rem;
z-index: 2;
${({ theme }) => theme.mediaWidth.upToMedium`
grid-template-columns: 1fr;
padding: 0 1rem;
width: calc(100%);
position: relative;
`};
${({ theme }) => theme.mediaWidth.upToExtraSmall`
padding: 0.5rem 1rem;
`}
`
const HeaderControls = styled.div`
display: flex;
flex-direction: row;
align-items: center;
justify-self: flex-end;
${({ theme }) => theme.mediaWidth.upToMedium`
flex-direction: row;
justify-content: space-between;
justify-self: center;
width: 100%;
max-width: 960px;
padding: 1rem;
position: fixed;
bottom: 0px;
left: 0px;
width: 100%;
z-index: 99;
height: 72px;
border-radius: 12px 12px 0 0;
background-color: ${({ theme }) => theme.bg1};
`};
`
const HeaderElement = styled.div`
display: flex;
align-items: center;
/* addresses safari's lack of support for "gap" */
& > *:not(:first-child) {
margin-left: 8px;
}
${({ theme }) => theme.mediaWidth.upToMedium`
flex-direction: row-reverse;
align-items: center;
`};
`
const HeaderElementWrap = styled.div`
display: flex;
align-items: center;
`
const HeaderRow = styled(RowFixed)`
${({ theme }) => theme.mediaWidth.upToMedium`
width: 100%;
`};
`
const HeaderLinks = styled(Row)`
justify-content: center;
${({ theme }) => theme.mediaWidth.upToMedium`
padding: 1rem 0 1rem 1rem;
justify-content: flex-end;
`};
`
const AccountElement = styled.div<{ active: boolean }>`
display: flex;
flex-direction: row;
align-items: center;
background-color: ${({ theme, active }) => (!active ? theme.bg1 : theme.bg3)};
border-radius: 12px;
white-space: nowrap;
width: 100%;
cursor: pointer;
:focus {
border: 1px solid blue;
}
`
const UNIAmount = styled(AccountElement)`
color: white;
padding: 4px 8px;
height: 36px;
font-weight: 500;
background-color: ${({ theme }) => theme.bg3};
background: radial-gradient(174.47% 188.91% at 1.84% 0%, #ff007a 0%, #2172e5 100%), #edeef2;
`
const UNIWrapper = styled.span`
width: fit-content;
position: relative;
cursor: pointer;
:hover {
opacity: 0.8;
}
:active {
opacity: 0.9;
}
`
const HideSmall = styled.span`
${({ theme }) => theme.mediaWidth.upToSmall`
display: none;
`};
`
const NetworkCard = styled(YellowCard)`
border-radius: 12px;
padding: 8px 12px;
${({ theme }) => theme.mediaWidth.upToSmall`
margin: 0;
margin-right: 0.5rem;
width: initial;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 1;
`};
`
const BalanceText = styled(Text)`
${({ theme }) => theme.mediaWidth.upToExtraSmall`
display: none;
`};
`
const Title = styled.a`
display: flex;
align-items: center;
pointer-events: auto;
justify-self: flex-start;
margin-right: 12px;
${({ theme }) => theme.mediaWidth.upToSmall`
justify-self: center;
`};
:hover {
cursor: pointer;
}
`
const UniIcon = styled.div`
transition: transform 0.3s ease;
:hover {
transform: rotate(-5deg);
}
`
const activeClassName = 'ACTIVE'
const StyledNavLink = styled(NavLink).attrs({
activeClassName
})`
${({ theme }) => theme.flexRowNoWrap}
align-items: left;
border-radius: 3rem;
outline: none;
cursor: pointer;
text-decoration: none;
color: ${({ theme }) => theme.text2};
font-size: 1rem;
width: fit-content;
margin: 0 12px;
font-weight: 500;
&.${activeClassName} {
border-radius: 12px;
font-weight: 600;
color: ${({ theme }) => theme.text1};
}
:hover,
:focus {
color: ${({ theme }) => darken(0.1, theme.text1)};
}
`
const StyledExternalLink = styled(ExternalLink).attrs({
activeClassName
})<{ isActive?: boolean }>`
${({ theme }) => theme.flexRowNoWrap}
align-items: left;
border-radius: 3rem;
outline: none;
cursor: pointer;
text-decoration: none;
color: ${({ theme }) => theme.text2};
font-size: 1rem;
width: fit-content;
margin: 0 12px;
font-weight: 500;
&.${activeClassName} {
border-radius: 12px;
font-weight: 600;
color: ${({ theme }) => theme.text1};
}
:hover,
:focus {
color: ${({ theme }) => darken(0.1, theme.text1)};
}
${({ theme }) => theme.mediaWidth.upToExtraSmall`
display: none;
`}
`
export const StyledMenuButton = styled.button`
position: relative;
width: 100%;
height: 100%;
border: none;
background-color: transparent;
margin: 0;
padding: 0;
height: 35px;
background-color: ${({ theme }) => theme.bg3};
margin-left: 8px;
padding: 0.15rem 0.5rem;
border-radius: 0.5rem;
:hover,
:focus {
cursor: pointer;
outline: none;
background-color: ${({ theme }) => theme.bg4};
}
svg {
margin-top: 2px;
}
> * {
stroke: ${({ theme }) => theme.text1};
}
`
const NETWORK_LABELS: { [chainId in ChainId]?: string } = {
[ChainId.RINKEBY]: 'Rinkeby',
[ChainId.ROPSTEN]: 'Ropsten',
[ChainId.GÖRLI]: 'Görli',
[ChainId.KOVAN]: 'Kovan'
}
export default function Header() {
const { account, chainId } = useActiveWeb3React()
const { t } = useTranslation()
const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? '']
// const [isDark] = useDarkModeManager()
const [darkMode, toggleDarkMode] = useDarkModeManager()
const toggleClaimModal = useToggleSelfClaimModal()
const availableClaim: boolean = useUserHasAvailableClaim(account)
const { claimTxn } = useUserHasSubmittedClaim(account ?? undefined)
const aggregateBalance: TokenAmount | undefined = useAggregateUniBalance()
const [showUniBalanceModal, setShowUniBalanceModal] = useState(false)
const showClaimPopup = useShowClaimPopup()
const countUpValue = aggregateBalance?.toFixed(0) ?? '0'
const countUpValuePrevious = usePrevious(countUpValue) ?? '0'
return (
<HeaderFrame>
<ClaimModal />
<Modal isOpen={showUniBalanceModal} onDismiss={() => setShowUniBalanceModal(false)}>
<UniBalanceContent setShowUniBalanceModal={setShowUniBalanceModal} />
</Modal>
<HeaderRow>
<Title href=".">
<UniIcon>
<img width={'24px'} src={darkMode ? LogoDark : Logo} alt="logo" />
</UniIcon>
</Title>
<HeaderLinks>
<StyledNavLink id={`swap-nav-link`} to={'/swap'}>
{t('swap')}
</StyledNavLink>
<StyledNavLink
id={`pool-nav-link`}
to={'/pool'}
isActive={(match, { pathname }) =>
Boolean(match) ||
pathname.startsWith('/add') ||
pathname.startsWith('/remove') ||
pathname.startsWith('/create') ||
pathname.startsWith('/find')
}
>
{t('pool')}
</StyledNavLink>
<StyledNavLink id={`stake-nav-link`} to={'/uni'}>
UNI
</StyledNavLink>
<StyledNavLink id={`stake-nav-link`} to={'/vote'}>
Vote
</StyledNavLink>
<StyledExternalLink id={`stake-nav-link`} href={'https://uniswap.info'}>
Charts <span style={{ fontSize: '11px' }}></span>
</StyledExternalLink>
</HeaderLinks>
</HeaderRow>
<HeaderControls>
<HeaderElement>
<HideSmall>
{chainId && NETWORK_LABELS[chainId] && (
<NetworkCard title={NETWORK_LABELS[chainId]}>{NETWORK_LABELS[chainId]}</NetworkCard>
)}
</HideSmall>
{availableClaim && !showClaimPopup && (
<UNIWrapper onClick={toggleClaimModal}>
<UNIAmount active={!!account && !availableClaim} style={{ pointerEvents: 'auto' }}>
<TYPE.white padding="0 2px">
{claimTxn && !claimTxn?.receipt ? <Dots>Claiming UNI</Dots> : 'Claim UNI'}
</TYPE.white>
</UNIAmount>
<CardNoise />
</UNIWrapper>
)}
{!availableClaim && aggregateBalance && (
<UNIWrapper onClick={() => setShowUniBalanceModal(true)}>
<UNIAmount active={!!account && !availableClaim} style={{ pointerEvents: 'auto' }}>
{account && (
<HideSmall>
<TYPE.white
style={{
paddingRight: '.4rem'
}}
>
<CountUp
key={countUpValue}
isCounting
start={parseFloat(countUpValuePrevious)}
end={parseFloat(countUpValue)}
thousandsSeparator={','}
duration={1}
/>
</TYPE.white>
</HideSmall>
)}
UNI
</UNIAmount>
<CardNoise />
</UNIWrapper>
)}
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
{account && userEthBalance ? (
<BalanceText style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
{userEthBalance?.toSignificant(4)} ETH
</BalanceText>
) : null}
<Web3Status />
</AccountElement>
</HeaderElement>
<HeaderElementWrap>
<StyledMenuButton onClick={() => toggleDarkMode()}>
{darkMode ? <Moon size={20} /> : <Sun size={20} />}
</StyledMenuButton>
<Menu />
</HeaderElementWrap>
</HeaderControls>
</HeaderFrame>
)
}

View File

@@ -2,20 +2,20 @@ import React, { useEffect, useRef } from 'react'
import styled from 'styled-components'
import { useWeb3React } from '../../hooks'
import { useActiveWeb3React } from '../../hooks'
import Jazzicon from 'jazzicon'
const StyledIdenticon = styled.div`
const StyledIdenticonContainer = styled.div`
height: 1rem;
width: 1rem;
border-radius: 1.125rem;
background-color: ${({ theme }) => theme.silverGray};
background-color: ${({ theme }) => theme.bg4};
`
export default function Identicon() {
const ref = useRef()
const ref = useRef<HTMLDivElement>()
const { account } = useWeb3React()
const { account } = useActiveWeb3React()
useEffect(() => {
if (account && ref.current) {
@@ -24,5 +24,6 @@ export default function Identicon() {
}
}, [account])
return <StyledIdenticon ref={ref} />
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451
return <StyledIdenticonContainer ref={ref as any} />
}

View File

@@ -0,0 +1,26 @@
import React from 'react'
import styled from 'styled-components'
import useHttpLocations from '../../hooks/useHttpLocations'
import Logo from '../Logo'
const StyledListLogo = styled(Logo)<{ size: string }>`
width: ${({ size }) => size};
height: ${({ size }) => size};
`
export default function ListLogo({
logoURI,
style,
size = '24px',
alt
}: {
logoURI: string
size?: string
style?: React.CSSProperties
alt?: string
}) {
const srcs: string[] = useHttpLocations(logoURI)
return <StyledListLogo alt={alt} size={size} srcs={srcs} style={style} />
}

View File

@@ -0,0 +1,46 @@
import React from 'react'
import styled, { keyframes } from 'styled-components'
const rotate = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`
const StyledSVG = styled.svg<{ size: string; stroke?: string }>`
animation: 2s ${rotate} linear infinite;
height: ${({ size }) => size};
width: ${({ size }) => size};
path {
stroke: ${({ stroke, theme }) => stroke ?? theme.primary1};
}
`
/**
* Takes in custom size and stroke for circle color, default to primary color as fill,
* need ...rest for layered styles on top
*/
export default function Loader({
size = '16px',
stroke,
...rest
}: {
size?: string
stroke?: string
[k: string]: any
}) {
return (
<StyledSVG viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" size={size} stroke={stroke} {...rest}>
<path
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 9.27455 20.9097 6.80375 19.1414 5"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</StyledSVG>
)
}

View File

@@ -0,0 +1,34 @@
import React, { useState } from 'react'
import { HelpCircle } from 'react-feather'
import { ImageProps } from 'rebass'
const BAD_SRCS: { [tokenAddress: string]: true } = {}
export interface LogoProps extends Pick<ImageProps, 'style' | 'alt' | 'className'> {
srcs: string[]
}
/**
* Renders an image by sequentially trying a list of URIs, and then eventually a fallback triangle alert
*/
export default function Logo({ srcs, alt, ...rest }: LogoProps) {
const [, refresh] = useState<number>(0)
const src: string | undefined = srcs.find(src => !BAD_SRCS[src])
if (src) {
return (
<img
{...rest}
alt={alt}
src={src}
onError={() => {
if (src) BAD_SRCS[src] = true
refresh(i => i + 1)
}}
/>
)
}
return <HelpCircle {...rest} />
}

View File

@@ -0,0 +1,137 @@
import React, { useRef } from 'react'
import { BookOpen, Code, Info, MessageCircle, PieChart } from 'react-feather'
import styled from 'styled-components'
import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
import { useActiveWeb3React } from '../../hooks'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import { ApplicationModal } from '../../state/application/actions'
import { useModalOpen, useToggleModal } from '../../state/application/hooks'
import { ExternalLink } from '../../theme'
import { ButtonPrimary } from '../Button'
const StyledMenuIcon = styled(MenuIcon)`
path {
stroke: ${({ theme }) => theme.text1};
}
`
const StyledMenuButton = styled.button`
width: 100%;
height: 100%;
border: none;
background-color: transparent;
margin: 0;
padding: 0;
height: 35px;
background-color: ${({ theme }) => theme.bg3};
padding: 0.15rem 0.5rem;
border-radius: 0.5rem;
:hover,
:focus {
cursor: pointer;
outline: none;
background-color: ${({ theme }) => theme.bg4};
}
svg {
margin-top: 2px;
}
`
const StyledMenu = styled.div`
margin-left: 0.5rem;
display: flex;
justify-content: center;
align-items: center;
position: relative;
border: none;
text-align: left;
`
const MenuFlyout = styled.span`
min-width: 8.125rem;
background-color: ${({ theme }) => theme.bg3};
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),
0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 12px;
padding: 0.5rem;
display: flex;
flex-direction: column;
font-size: 1rem;
position: absolute;
top: 4rem;
right: 0rem;
z-index: 100;
${({ theme }) => theme.mediaWidth.upToMedium`
top: -17.25rem;
`};
`
const MenuItem = styled(ExternalLink)`
flex: 1;
padding: 0.5rem 0.5rem;
color: ${({ theme }) => theme.text2};
:hover {
color: ${({ theme }) => theme.text1};
cursor: pointer;
text-decoration: none;
}
> svg {
margin-right: 8px;
}
`
const CODE_LINK = 'https://github.com/Uniswap/uniswap-interface'
export default function Menu() {
const { account } = useActiveWeb3React()
const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.MENU)
const toggle = useToggleModal(ApplicationModal.MENU)
useOnClickOutside(node, open ? toggle : undefined)
const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
return (
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451
<StyledMenu ref={node as any}>
<StyledMenuButton onClick={toggle}>
<StyledMenuIcon />
</StyledMenuButton>
{open && (
<MenuFlyout>
<MenuItem id="link" href="https://uniswap.org/">
<Info size={14} />
About
</MenuItem>
<MenuItem id="link" href="https://uniswap.org/docs/v2">
<BookOpen size={14} />
Docs
</MenuItem>
<MenuItem id="link" href={CODE_LINK}>
<Code size={14} />
Code
</MenuItem>
<MenuItem id="link" href="https://discord.gg/FCfyBSbCU5">
<MessageCircle size={14} />
Discord
</MenuItem>
<MenuItem id="link" href="https://uniswap.info/">
<PieChart size={14} />
Analytics
</MenuItem>
{account && (
<ButtonPrimary onClick={openClaimModal} padding="8px 16px" width="100%" borderRadius="12px" mt="0.5rem">
Claim UNI
</ButtonPrimary>
)}
</MenuFlyout>
)}
</StyledMenu>
)
}

View File

@@ -1,191 +0,0 @@
import React from 'react'
import styled, { css } from 'styled-components'
import { animated, useTransition, useSpring } from 'react-spring'
import { Spring } from 'react-spring/renderprops'
import { DialogOverlay, DialogContent } from '@reach/dialog'
import { isMobile } from 'react-device-detect'
import '@reach/dialog/styles.css'
import { transparentize } from 'polished'
import { useGesture } from 'react-use-gesture'
const AnimatedDialogOverlay = animated(DialogOverlay)
const WrappedDialogOverlay = ({ suppressClassNameWarning, mobile, ...rest }) => <AnimatedDialogOverlay {...rest} />
const StyledDialogOverlay = styled(WrappedDialogOverlay).attrs({
suppressClassNameWarning: true
})`
&[data-reach-dialog-overlay] {
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
background-color: ${({ theme }) => 'transparent'};
${({ mobile }) =>
mobile &&
css`
align-items: flex-end;
`}
&::after {
content: '';
background-color: ${({ theme }) => theme.modalBackground};
opacity: 0.5;
top: 0;
left: 0;
bottom: 0;
right: 0;
/* position: absolute; */
position: fixed;
z-index: -1;
}
}
`
const FilteredDialogContent = ({ minHeight, maxHeight, isOpen, slideInAnimation, mobile, ...rest }) => (
<DialogContent {...rest} />
)
const StyledDialogContent = styled(FilteredDialogContent)`
&[data-reach-dialog-content] {
margin: 0 0 2rem 0;
border: 1px solid ${({ theme }) => theme.concreteGray};
background-color: ${({ theme }) => theme.inputBackground};
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadowColor)};
padding: 0px;
width: 50vw;
max-width: 650px;
${({ maxHeight }) =>
maxHeight &&
css`
max-height: ${maxHeight}vh;
`}
${({ minHeight }) =>
minHeight &&
css`
min-height: ${minHeight}vh;
`}
display: flex;
overflow: hidden;
border-radius: 10px;
${({ theme }) => theme.mediaWidth.upToMedium`
width: 65vw;
max-height: 65vh;
margin: 0;
`}
${({ theme, mobile, isOpen }) => theme.mediaWidth.upToSmall`
width: 85vw;
max-height: 66vh;
${mobile &&
css`
width: 100vw;
border-radius: 20px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
`}
`}
}
`
const HiddenCloseButton = styled.button`
margin: 0;
padding: 0;
width: 0;
height: 0;
border: none;
`
export default function Modal({ isOpen, onDismiss, minHeight = false, maxHeight = 50, initialFocusRef, children }) {
const transitions = useTransition(isOpen, null, {
config: { duration: 200 },
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 }
})
const [{ xy }, set] = useSpring(() => ({ xy: [0, 0] }))
const bind = useGesture({
onDrag: state => {
let velocity = state.velocity
if (velocity < 1) {
velocity = 1
}
if (velocity > 8) {
velocity = 8
}
set({
xy: state.down ? state.movement : [0, 0],
config: { mass: 1, tension: 210, friction: 20 }
})
if (velocity > 3 && state.direction[1] > 0) {
onDismiss()
}
}
})
if (isMobile) {
return transitions.map(
({ item, key, props }) =>
item && (
<StyledDialogOverlay
key={key}
style={props}
onDismiss={onDismiss}
initialFocusRef={initialFocusRef}
mobile={isMobile}
>
<Spring // animation for entrance and exit
from={{
transform: isOpen ? 'translateY(200px)' : 'translateY(100px)'
}}
to={{
transform: isOpen ? 'translateY(0px)' : 'translateY(200px)'
}}
>
{props => (
<animated.div
{...bind()}
style={{ transform: xy.interpolate((x, y) => `translate3d(${0}px,${y > 0 ? y : 0}px,0)`) }}
>
<StyledDialogContent
style={props}
hidden={true}
minHeight={minHeight}
maxHeight={maxHeight}
mobile={isMobile}
>
<HiddenCloseButton onClick={onDismiss} />
{children}
</StyledDialogContent>
</animated.div>
)}
</Spring>
</StyledDialogOverlay>
)
)
} else {
return transitions.map(
({ item, key, props }) =>
item && (
<StyledDialogOverlay
key={key}
style={props}
onDismiss={onDismiss}
initialFocusRef={initialFocusRef}
mobile={isMobile}
>
<StyledDialogContent
hidden={true}
minHeight={minHeight}
maxHeight={maxHeight}
isOpen={isOpen}
mobile={isMobile}
>
<HiddenCloseButton onClick={onDismiss} />
{children}
</StyledDialogContent>
</StyledDialogOverlay>
)
)
}
}

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