Compare commits
431 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9170af888e | ||
|
|
b258f557d1 | ||
|
|
9d8c7f8e12 | ||
|
|
9c44e61e23 | ||
|
|
71db11b6ac | ||
|
|
db3328c8d9 | ||
|
|
34dfb41a1e | ||
|
|
e77fcd21dc | ||
|
|
0b7846ee1d | ||
|
|
f450d34d69 | ||
|
|
76ab349b9e | ||
|
|
5c3c1c67f5 | ||
|
|
9efd5da1f7 | ||
|
|
8fd894f2d1 | ||
|
|
cc22183388 | ||
|
|
9175dd10cc | ||
|
|
bbd50f066d | ||
|
|
2291e3ec20 | ||
|
|
28d8f0b0bb | ||
|
|
8bed3900ba | ||
|
|
a1000c6576 | ||
|
|
267204d98e | ||
|
|
74f50f1b7e | ||
|
|
a70aa41df2 | ||
|
|
587b659816 | ||
|
|
5388cab779 | ||
|
|
cadd68fb37 | ||
|
|
ab8ce37ceb | ||
|
|
5a9a71ae2d | ||
|
|
b93fd230bd | ||
|
|
e9a11bb604 | ||
|
|
c5afbedb3e | ||
|
|
5b2c44522e | ||
|
|
7fd4005154 | ||
|
|
48eab0d0ac | ||
|
|
c9ee1b3b32 | ||
|
|
eb4c305eff | ||
|
|
d9825622f1 | ||
|
|
8975086a69 | ||
|
|
ddf88345a9 | ||
|
|
32ac25556b | ||
|
|
50a599c005 | ||
|
|
9c473270ee | ||
|
|
b650b17563 | ||
|
|
fc76177791 | ||
|
|
69655980db | ||
|
|
e835a34d0d | ||
|
|
33fdf28dec | ||
|
|
c45c293e82 | ||
|
|
9e140d1214 | ||
|
|
bf8efc9bf6 | ||
|
|
4f922a2824 | ||
|
|
b50cac0181 | ||
|
|
1ac16586ff | ||
|
|
e2686e0ed1 | ||
|
|
f64b3c2c8f | ||
|
|
5a579e05e7 | ||
|
|
22a7fdc8e7 | ||
|
|
8c4fd0a47d | ||
|
|
7d526c5855 | ||
|
|
03913d9c0b | ||
|
|
23c7f4ce38 | ||
|
|
f33bba2176 | ||
|
|
fb31c75838 | ||
|
|
b8c383c20e | ||
|
|
fbf39e4932 | ||
|
|
975570fa97 | ||
|
|
d6aa0e98a4 | ||
|
|
4644cd7b0a | ||
|
|
9ddedd8dab | ||
|
|
95030a52c5 | ||
|
|
1911f72536 | ||
|
|
85217452db | ||
|
|
f7a1a2ab58 | ||
|
|
66a2006284 | ||
|
|
610b7f4464 | ||
|
|
ce12635332 | ||
|
|
2182e18f85 | ||
|
|
ad2c7dfdff | ||
|
|
cb36c9103e | ||
|
|
0a1459ee83 | ||
|
|
8896a042f0 | ||
|
|
61ad07c3f2 | ||
|
|
81a5164d99 | ||
|
|
467e80a42f | ||
|
|
58f25aa439 | ||
|
|
377c71f2e5 | ||
|
|
7cf25ac7c8 | ||
|
|
09b54570e1 | ||
|
|
73580de922 | ||
|
|
e32fd3a8fc | ||
|
|
057417c666 | ||
|
|
f1b300af70 | ||
|
|
600049bc6e | ||
|
|
6e91311489 | ||
|
|
f6a464cb3b | ||
|
|
e589c751d7 | ||
|
|
0f91af1df2 | ||
|
|
10ef04510a | ||
|
|
e3b3d9e825 | ||
|
|
3050e967f7 | ||
|
|
2150450760 | ||
|
|
1b07e95885 | ||
|
|
9bb50d6a7b | ||
|
|
b08bb7eaff | ||
|
|
3a36ac5538 | ||
|
|
2962cd0e14 | ||
|
|
6a311aa6d7 | ||
|
|
e78b6d61f2 | ||
|
|
365b429c0b | ||
|
|
32d300009e | ||
|
|
806623c602 | ||
|
|
3272f8e9db | ||
|
|
010ef108eb | ||
|
|
19b1e9e399 | ||
|
|
6287b95b92 | ||
|
|
4e8a6e2a4c | ||
|
|
848c7b418b | ||
|
|
f619cf4353 | ||
|
|
877db71e2a | ||
|
|
f4b5727fdb | ||
|
|
1fd6b1e659 | ||
|
|
6570beef32 | ||
|
|
b57f58ab35 | ||
|
|
2f40c4f614 | ||
|
|
3f9c34d37d | ||
|
|
1d5c6530e3 | ||
|
|
78f294c340 | ||
|
|
90d24a26f3 | ||
|
|
7a3a5bd546 | ||
|
|
081ae15aa8 | ||
|
|
f5a5c5e70d | ||
|
|
e05e0206b7 | ||
|
|
344b4340ae | ||
|
|
eeef306bdd | ||
|
|
63a491d4b1 | ||
|
|
6831a73fdf | ||
|
|
a4aef02747 | ||
|
|
c26716047f | ||
|
|
0fa238af0b | ||
|
|
21c1484c0e | ||
|
|
8a845ee0e9 | ||
|
|
f5229ca838 | ||
|
|
875203f0ef | ||
|
|
91a8202737 | ||
|
|
0b4819d165 | ||
|
|
e7d3289754 | ||
|
|
0698e0f82a | ||
|
|
0350cc4701 | ||
|
|
997052869d | ||
|
|
9ec16c2ba8 | ||
|
|
e2cf8f1642 | ||
|
|
ed6952d1f7 | ||
|
|
3277d70e93 | ||
|
|
d1a31fe763 | ||
|
|
f88af029ae | ||
|
|
9f3e49b4d8 | ||
|
|
d4911d1054 | ||
|
|
90df9c4ced | ||
|
|
14f15d1fd6 | ||
|
|
69818ace1f | ||
|
|
42906d6709 | ||
|
|
2f8936a980 | ||
|
|
f5c4468c3c | ||
|
|
852e8f749f | ||
|
|
6694e5e398 | ||
|
|
2c9a50a372 | ||
|
|
0fc0cba6de | ||
|
|
041c86c04d | ||
|
|
123373e671 | ||
|
|
eb1732deee | ||
|
|
3c13321a71 | ||
|
|
58703f31a0 | ||
|
|
58721fb191 | ||
|
|
678cd1a06f | ||
|
|
a5ff3beb92 | ||
|
|
35ccf425f6 | ||
|
|
fe030412cd | ||
|
|
4d5a43351f | ||
|
|
ac1bc3b3a6 | ||
|
|
d1063d50ed | ||
|
|
46fc74e90f | ||
|
|
2c4f4092d8 | ||
|
|
aac7268dc8 | ||
|
|
fd162a72ff | ||
|
|
e20936709c | ||
|
|
2fda2c8c15 | ||
|
|
1f09757c49 | ||
|
|
7e49babff7 | ||
|
|
b35653ade1 | ||
|
|
57b53013d1 | ||
|
|
bafd3f3c05 | ||
|
|
29db0a50b3 | ||
|
|
9566fb888e | ||
|
|
13c8903e8f | ||
|
|
1d06b47e8d | ||
|
|
0089c2ee43 | ||
|
|
9869a9fcb7 | ||
|
|
631f29d66d | ||
|
|
97deebad37 | ||
|
|
e667615449 | ||
|
|
4ab61faeae | ||
|
|
0004db3d4a | ||
|
|
c133c472be | ||
|
|
0019ccdf51 | ||
|
|
5a1a469f35 | ||
|
|
4c28f34803 | ||
|
|
104be830fc | ||
|
|
24c70791cd | ||
|
|
216fdea290 | ||
|
|
40e4ce2ed3 | ||
|
|
b2508fc6f2 | ||
|
|
f73b37287f | ||
|
|
c09eb738c3 | ||
|
|
6de3a6ec28 | ||
|
|
c1d35cc8b3 | ||
|
|
f279b2bea2 | ||
|
|
6ffbf756f8 | ||
|
|
10837d7ba1 | ||
|
|
2d6eddf9d4 | ||
|
|
aadf43efc3 | ||
|
|
227f729ecd | ||
|
|
a5b15e37f6 | ||
|
|
2408b2966e | ||
|
|
dc391d1bea | ||
|
|
e2d0514344 | ||
|
|
98d25dd2af | ||
|
|
f289dec684 | ||
|
|
73d3df05f2 | ||
|
|
83554f44f8 | ||
|
|
320b2e384b | ||
|
|
9492e7375a | ||
|
|
8a6a10be9d | ||
|
|
5e486fca7f | ||
|
|
87d24c404b | ||
|
|
d4011f73d1 | ||
|
|
6fc3157977 | ||
|
|
9c1fe53e4b | ||
|
|
28c916ff45 | ||
|
|
7adb4b6bd6 | ||
|
|
b2f0236ee8 | ||
|
|
4b57059353 | ||
|
|
6926f9a4ae | ||
|
|
7dec580944 | ||
|
|
5cf95680ef | ||
|
|
f8d6bab4ae | ||
|
|
c9721c42bf | ||
|
|
4414134bb2 | ||
|
|
44ba54e44a | ||
|
|
9ec3109f72 | ||
|
|
e75793676a | ||
|
|
32006ded21 | ||
|
|
d4f1c579d8 | ||
|
|
95f3541807 | ||
|
|
da4ca73a1d | ||
|
|
e75bf8d003 | ||
|
|
236f68a459 | ||
|
|
9f07baaad2 | ||
|
|
c75464e1aa | ||
|
|
bc80585bb4 | ||
|
|
ad45b2b7bb | ||
|
|
63ac89e9f3 | ||
|
|
1b6ae0d3db | ||
|
|
7d67819604 | ||
|
|
7b9b332c42 | ||
|
|
01feae978a | ||
|
|
2452d51e14 | ||
|
|
bbdc258083 | ||
|
|
27b103e3f7 | ||
|
|
2a751b9892 | ||
|
|
175e93fbba | ||
|
|
0b5fc07ee5 | ||
|
|
a0d4710a11 | ||
|
|
63af1a160d | ||
|
|
85d52b3480 | ||
|
|
219de1f471 | ||
|
|
f110fa7732 | ||
|
|
513a1b0c4b | ||
|
|
96c9eede18 | ||
|
|
f4a97501e5 | ||
|
|
2ff5ce62db | ||
|
|
79176dfe79 | ||
|
|
4880e0fb02 | ||
|
|
ceea563af5 | ||
|
|
992c5a8e54 | ||
|
|
850e9bb2bd | ||
|
|
e5bf2d0823 | ||
|
|
2708d26495 | ||
|
|
ccab6f01cf | ||
|
|
384c3cada4 | ||
|
|
c94e8ff6e5 | ||
|
|
04584cc4ac | ||
|
|
7d20dd976b | ||
|
|
2b4f511b55 | ||
|
|
7d379b796c | ||
|
|
4e8777c728 | ||
|
|
9916c84e5d | ||
|
|
462f2482e9 | ||
|
|
2ff8f5a7ff | ||
|
|
0b8d05b696 | ||
|
|
425a23774d | ||
|
|
fea11871ae | ||
|
|
b616c7b50d | ||
|
|
9ac1bcf008 | ||
|
|
1bb4db5414 | ||
|
|
536de89a38 | ||
|
|
199eb8bf50 | ||
|
|
f1c6119f8b | ||
|
|
a10b22a382 | ||
|
|
206e845731 | ||
|
|
1e7072d5f2 | ||
|
|
70722b5e30 | ||
|
|
cba76c43f6 | ||
|
|
373b3180d3 | ||
|
|
c35d8fc702 | ||
|
|
e6a043cdc5 | ||
|
|
5afddb2ede | ||
|
|
3b2c21e26d | ||
|
|
0e72f1c5d3 | ||
|
|
81b063351d | ||
|
|
bfb1992e45 | ||
|
|
690a121218 | ||
|
|
3fe0a5a9b0 | ||
|
|
f0e1e92178 | ||
|
|
fcb012cb1a | ||
|
|
1aea452f3b | ||
|
|
e311e2fcf9 | ||
|
|
9c4f63f444 | ||
|
|
f261675602 | ||
|
|
67df4959fb | ||
|
|
095beae0c2 | ||
|
|
51e929bd1e | ||
|
|
af892c1fec | ||
|
|
1380f865a9 | ||
|
|
487b5ceccc | ||
|
|
6c9eeb4691 | ||
|
|
a34dc73c38 | ||
|
|
07fee04526 | ||
|
|
5a9b7a382e | ||
|
|
7bffea0692 | ||
|
|
b1ffab1890 | ||
|
|
60582de4d6 | ||
|
|
4ef3746a09 | ||
|
|
d2462af821 | ||
|
|
5405648a54 | ||
|
|
4ba7dd9535 | ||
|
|
0733cdd63c | ||
|
|
8a61b4e4ad | ||
|
|
e0cbee79a6 | ||
|
|
141e605c17 | ||
|
|
3a426fa36b | ||
|
|
52d40949a6 | ||
|
|
685bb1745a | ||
|
|
9006acb4c5 | ||
|
|
1e1a049990 | ||
|
|
3a2566b4e7 | ||
|
|
c9db5fb276 | ||
|
|
1cbf67872b | ||
|
|
38947fe760 | ||
|
|
31f72a541b | ||
|
|
287f64cf5c | ||
|
|
822f9e5fe2 | ||
|
|
6781fce58e | ||
|
|
4398fef38f | ||
|
|
ef65943659 | ||
|
|
19a53cd999 | ||
|
|
b28ad2865d | ||
|
|
0d4108937f | ||
|
|
6da8e2c84d | ||
|
|
ea015d16f2 | ||
|
|
d40d81ca6a | ||
|
|
dba6abadf5 | ||
|
|
4131268b8e | ||
|
|
c365a5ec33 | ||
|
|
ee92df1554 | ||
|
|
f1ccd7a166 | ||
|
|
7c43023860 | ||
|
|
843d5e790a | ||
|
|
40a7cea6f6 | ||
|
|
e023a02037 | ||
|
|
7add2a916c | ||
|
|
5f36437c3f | ||
|
|
152e84bc25 | ||
|
|
3b3f281319 | ||
|
|
17eceebcf5 | ||
|
|
6112c8a2a2 | ||
|
|
2adb0a3dfb | ||
|
|
86fa969d6b | ||
|
|
28b24036c6 | ||
|
|
a1a9d9f041 | ||
|
|
9b2fe0bdca | ||
|
|
cb00e0baa5 | ||
|
|
4ccbd0c3ba | ||
|
|
7fcecc22d9 | ||
|
|
404d7a60e5 | ||
|
|
b6b094acb3 | ||
|
|
49462fdb3b | ||
|
|
3c112b5746 | ||
|
|
e5c5bad7ab | ||
|
|
9eee45a8f1 | ||
|
|
e5ced44d75 | ||
|
|
cdbf440e9c | ||
|
|
5426e48872 | ||
|
|
64dd09f2cc | ||
|
|
6fd9808e7d | ||
|
|
c3d8bc7ed5 | ||
|
|
3f1d7ab310 | ||
|
|
60d7bc4532 | ||
|
|
76dbc9fa18 | ||
|
|
006fe9b325 | ||
|
|
50c9d9973a | ||
|
|
af6add09a0 | ||
|
|
cf5c67ec88 | ||
|
|
eaf8d5a39c | ||
|
|
02dedcbc6e | ||
|
|
d8c4ebc243 | ||
|
|
2b15979e93 | ||
|
|
6211dff044 | ||
|
|
655b79569b | ||
|
|
4e2c5c1e84 | ||
|
|
e43d9e03f1 | ||
|
|
35d398406d | ||
|
|
217a700832 | ||
|
|
55eb03c237 | ||
|
|
a6d8613bdd | ||
|
|
2ca3bf8da3 | ||
|
|
236c3030e1 | ||
|
|
753e5f3423 | ||
|
|
3091ebc158 | ||
|
|
912a19d66d | ||
|
|
4890ad1172 |
2
.env
Normal file
@@ -0,0 +1,2 @@
|
||||
REACT_APP_CHAIN_ID="1"
|
||||
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
5
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -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
@@ -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
|
||||
5
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@@ -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.**
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/something-else.md
vendored
@@ -1,10 +0,0 @@
|
||||
---
|
||||
name: Something Else
|
||||
about: Tell us something else
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
27
.github/ISSUE_TEMPLATE/token-request.md
vendored
@@ -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?:
|
||||
3
.github/pull_request_template.md
vendored
@@ -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
@@ -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
|
||||
136
.github/workflows/release.yaml
vendored
@@ -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
@@ -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
@@ -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
|
||||
13
.travis.yml
@@ -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
|
||||
67
README.md
@@ -1,28 +1,33 @@
|
||||
# Uniswap Frontend
|
||||
# Uniswap Interface
|
||||
|
||||
[](https://app.netlify.com/sites/uniswap/deploys)
|
||||
[](https://travis-ci.org/Uniswap/uniswap-frontend)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions?query=workflow%3ALint)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions?query=workflow%3ATests)
|
||||
[](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
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"pluginsFile": false,
|
||||
"fixturesFolder": false,
|
||||
"supportFile": "cypress/support/index.js",
|
||||
"video": false,
|
||||
"defaultCommandTimeout": 10000
|
||||
}
|
||||
50
cypress/integration/add-liquidity.test.ts
Normal 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'
|
||||
)
|
||||
})
|
||||
})
|
||||
22
cypress/integration/landing.test.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
11
cypress/integration/lists.test.ts
Normal 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()
|
||||
})
|
||||
})
|
||||
8
cypress/integration/migrate-v1.test.ts
Normal 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')
|
||||
})
|
||||
})
|
||||
})
|
||||
12
cypress/integration/pool.test.ts
Normal 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')
|
||||
})
|
||||
})
|
||||
39
cypress/integration/remove-liquidity.test.ts
Normal 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')
|
||||
})
|
||||
})
|
||||
11
cypress/integration/send.test.ts
Normal 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')
|
||||
})
|
||||
})
|
||||
75
cypress/integration/swap.test.ts
Normal 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
@@ -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
|
||||
// }
|
||||
// }
|
||||
82
cypress/support/commands.js
Normal 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
@@ -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
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"baseUrl": "../node_modules",
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
20
netlify.toml
@@ -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"
|
||||
128
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 31 KiB |
BIN
public/favicon.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/images/192x192_App_Icon.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
public/images/512x512_App_Icon.png
Normal file
|
After Width: | Height: | Size: 378 KiB |
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
@@ -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": "הכניסו כתובת טוקן כדי להמשיך"
|
||||
}
|
||||
@@ -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ă"
|
||||
}
|
||||
|
||||
@@ -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 "
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
BIN
src/assets/images/big_unicorn.png
Normal file
|
After Width: | Height: | Size: 883 KiB |
3
src/assets/images/blue-loader.svg
Normal 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 |
BIN
src/assets/images/ethereum-logo.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
@@ -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 |
1
src/assets/images/link.svg
Normal 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 |
5
src/assets/images/menu.svg
Normal 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
|
After Width: | Height: | Size: 1.3 MiB |
@@ -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 |
BIN
src/assets/images/token-list-logo.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
src/assets/images/token-list/lists-dark.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
src/assets/images/token-list/lists-light.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
src/assets/images/token-logo.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
src/assets/images/tokenlistsgrouped.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
src/assets/images/xl_uni.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
3
src/assets/svg/QR.svg
Normal 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 |
@@ -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
|
||||
11
src/assets/svg/lightcircle.svg
Normal 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
@@ -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 |
13
src/assets/svg/logo_pink.svg
Normal 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 |
11
src/assets/svg/logo_white.svg
Normal 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 |
30
src/assets/svg/tokenlist.svg
Normal 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 |
8
src/assets/svg/wordmark.svg
Normal file
|
After Width: | Height: | Size: 106 KiB |
8
src/assets/svg/wordmark_pink.svg
Normal file
|
After Width: | Height: | Size: 107 KiB |
8
src/assets/svg/wordmark_white.svg
Normal file
|
After Width: | Height: | Size: 107 KiB |
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
62
src/components/AccountDetails/Transaction.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
411
src/components/AccountDetails/index.tsx
Normal 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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
128
src/components/AddressInputPanel/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
12
src/components/Blocklist/index.tsx
Normal 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}</>
|
||||
}
|
||||
339
src/components/Button/index.tsx
Normal 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} />
|
||||
}
|
||||
}
|
||||
60
src/components/Card/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
23
src/components/Column/index.tsx
Normal 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
|
||||
32
src/components/Confetti/index.tsx
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
249
src/components/CurrencyInputPanel/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
56
src/components/CurrencyLogo/index.tsx
Normal 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} />
|
||||
}
|
||||
40
src/components/DoubleLogo/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
22
src/components/FormattedCurrencyAmount/index.tsx
Normal 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)}`}
|
||||
</>
|
||||
)
|
||||
}
|
||||
94
src/components/Header/Polling.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
47
src/components/Header/URLWarning.tsx
Normal 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
|
||||
}
|
||||
127
src/components/Header/UniBalanceContent.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
420
src/components/Header/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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} />
|
||||
}
|
||||
26
src/components/ListLogo/index.tsx
Normal 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} />
|
||||
}
|
||||
46
src/components/Loader/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
34
src/components/Logo/index.tsx
Normal 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} />
|
||||
}
|
||||
137
src/components/Menu/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||