Compare commits
165 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e569dc2152 | ||
|
|
1aa042c5ef | ||
|
|
1450315b98 | ||
|
|
aefbb3d812 | ||
|
|
c3f12398cd | ||
|
|
2272f2a01a | ||
|
|
fb71078ea2 | ||
|
|
1c7c93191e | ||
|
|
0713f730b3 | ||
|
|
5f7a18b411 | ||
|
|
020c8d181a | ||
|
|
ab3f024031 | ||
|
|
d989c61de5 | ||
|
|
5dd8059734 | ||
|
|
b50e5511ea | ||
|
|
1efe5e9cd5 | ||
|
|
2944dc4d0b | ||
|
|
29ae755f2a | ||
|
|
27b831b301 | ||
|
|
6d9d38819e | ||
|
|
2de29129ed | ||
|
|
52af0e506b | ||
|
|
4d69c946bf | ||
|
|
542bf0bf66 | ||
|
|
a4fbfae4ba | ||
|
|
b2288258f2 | ||
|
|
8703013b2d | ||
|
|
4f6173675d | ||
|
|
2469eb58b9 | ||
|
|
e0a8ac2408 | ||
|
|
0a736b5e62 | ||
|
|
b44eb8877c | ||
|
|
92e61fa34b | ||
|
|
ef62fd33b2 | ||
|
|
96a42f66d4 | ||
|
|
c446f20d2f | ||
|
|
5a1ef8fb7d | ||
|
|
2863971640 | ||
|
|
dcaf10ec29 | ||
|
|
bca5113569 | ||
|
|
6779c1a024 | ||
|
|
f79ef12494 | ||
|
|
7bcda46934 | ||
|
|
f4ba24cfd5 | ||
|
|
59c6ab16dd | ||
|
|
db17dcbf2c | ||
|
|
1835de7f5f | ||
|
|
00f158209c | ||
|
|
2108ceedd5 | ||
|
|
ad080470da | ||
|
|
fc34912b53 | ||
|
|
c25d2b894c | ||
|
|
83c99b8c04 | ||
|
|
ccdf1e7575 | ||
|
|
c9faafee5e | ||
|
|
26a44fb51b | ||
|
|
1e16ac8449 | ||
|
|
5b5e76573d | ||
|
|
27cdbd0d5f | ||
|
|
b2a30b9bf1 | ||
|
|
dfad7b89ab | ||
|
|
4fe35ea42e | ||
|
|
0d852b6165 | ||
|
|
8ac3b836bd | ||
|
|
12bc5957b4 | ||
|
|
a33187c33b | ||
|
|
248bc07cf1 | ||
|
|
369f8c94e3 | ||
|
|
de5f0541ee | ||
|
|
48b3efc612 | ||
|
|
90c59f31f3 | ||
|
|
0e709c257b | ||
|
|
7a3bb8de1d | ||
|
|
8018d1b9dc | ||
|
|
1297aa57d3 | ||
|
|
30e30189e1 | ||
|
|
6a602cf6d7 | ||
|
|
4c966caa2a | ||
|
|
a60ea703b0 | ||
|
|
ae664dc264 | ||
|
|
b152b11515 | ||
|
|
0f51991109 | ||
|
|
da8884d87d | ||
|
|
79bdc0c5ee | ||
|
|
82c30681ea | ||
|
|
41ef961679 | ||
|
|
7de63ab462 | ||
|
|
59c5989721 | ||
|
|
b042d2b3b4 | ||
|
|
897e7f4581 | ||
|
|
a7fb7dc906 | ||
|
|
5fe89b9d6c | ||
|
|
acbcd3763c | ||
|
|
01c467b48c | ||
|
|
636abe3b7b | ||
|
|
8404c6076c | ||
|
|
b4aac94c2c | ||
|
|
f47fcc9c17 | ||
|
|
b5d27e2063 | ||
|
|
26275ca580 | ||
|
|
92b7ca8f55 | ||
|
|
c5ea01ce19 | ||
|
|
88712b5065 | ||
|
|
1af34ae016 | ||
|
|
9cb19dd0ea | ||
|
|
02a77254c7 | ||
|
|
69ed7015ab | ||
|
|
ff16d3f18f | ||
|
|
5175cb6d1f | ||
|
|
b33686855d | ||
|
|
75ecc5810e | ||
|
|
c30eb89725 | ||
|
|
108feace02 | ||
|
|
e2c013a4d8 | ||
|
|
66308257d6 | ||
|
|
fd160531cc | ||
|
|
da36e638c2 | ||
|
|
fad55b8dbc | ||
|
|
c9c59698de | ||
|
|
828967031f | ||
|
|
440ac0cba0 | ||
|
|
b5a72cd63b | ||
|
|
37f273aab4 | ||
|
|
3acd993ec0 | ||
|
|
58778b5775 | ||
|
|
5bc21bebc3 | ||
|
|
c3d6727438 | ||
|
|
290f4bc1cb | ||
|
|
f95275d5ac | ||
|
|
0ec2dd4173 | ||
|
|
3b3db6f6d0 | ||
|
|
707abd0071 | ||
|
|
2efc1fb372 | ||
|
|
55b37825f3 | ||
|
|
bb27b7a2ef | ||
|
|
c595ba951b | ||
|
|
96a122d7b8 | ||
|
|
610f7d3581 | ||
|
|
781e774ce7 | ||
|
|
2aa1e40481 | ||
|
|
1c278d5012 | ||
|
|
a323a5c48b | ||
|
|
43931dd689 | ||
|
|
efa3d5529c | ||
|
|
5c0246cfc6 | ||
|
|
ee32418ff8 | ||
|
|
6e22389791 | ||
|
|
8064dd8ede | ||
|
|
921310ef52 | ||
|
|
7b90fe137e | ||
|
|
05b2711a8a | ||
|
|
d060782242 | ||
|
|
e19e8492c9 | ||
|
|
800b5e0bda | ||
|
|
fc637071f9 | ||
|
|
1b78ceec10 | ||
|
|
e5be3ebf8f | ||
|
|
1c73719766 | ||
|
|
14c91f9bba | ||
|
|
4b762ef5c9 | ||
|
|
c82b4fae64 | ||
|
|
ab8c1e3e90 | ||
|
|
7055d60406 | ||
|
|
c641cec651 | ||
|
|
b6a47c734f |
3
.env
@@ -1 +1,2 @@
|
||||
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||
REACT_APP_LOCALES="locales"
|
||||
2
.github/workflows/integration-tests.yaml
vendored
@@ -38,10 +38,10 @@ jobs:
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- run: yarn cypress install
|
||||
|
||||
- run: yarn build
|
||||
env:
|
||||
CI: false
|
||||
REACT_APP_NETWORK_URL: 'https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847'
|
||||
REACT_APP_SERVICE_WORKER: false
|
||||
|
||||
- run: yarn test:e2e
|
||||
|
||||
6
.gitignore
vendored
@@ -17,11 +17,11 @@
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
# builds
|
||||
/build
|
||||
|
||||
# widgets
|
||||
/cosmos-export
|
||||
/dist
|
||||
/dts
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
],
|
||||
"webpack": {
|
||||
"configPath": "react-scripts/config/webpack.config",
|
||||
"overridePath": "cosmos.override.js"
|
||||
}
|
||||
}
|
||||
"overridePath": "cosmos.override.cjs"
|
||||
},
|
||||
"port": 5001
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ module.exports = (webpackConfig) => ({
|
||||
'process.env': {
|
||||
...plugin.definitions['process.env'],
|
||||
REACT_APP_IS_WIDGET: true,
|
||||
REACT_APP_LOCALES: '"../locales"',
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -2,16 +2,27 @@ describe('Swap', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/swap')
|
||||
})
|
||||
|
||||
it('starts with ETH selected by default', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
|
||||
cy.get('#swap-currency-input .token-symbol-container').should('contain.text', 'ETH')
|
||||
cy.get('#swap-currency-output .token-amount-input').should('not.have.value')
|
||||
cy.get('#swap-currency-output .token-symbol-container').should('contain.text', 'Select a token')
|
||||
})
|
||||
|
||||
it('can enter an amount into input', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input').type('0.001', { delay: 200 }).should('have.value', '0.001')
|
||||
cy.get('#swap-currency-input .token-amount-input')
|
||||
.clear()
|
||||
.type('0.001', { delay: 200 })
|
||||
.should('have.value', '0.001')
|
||||
})
|
||||
|
||||
it('zero swap amount', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input').type('0.0', { delay: 200 }).should('have.value', '0.0')
|
||||
cy.get('#swap-currency-input .token-amount-input').clear().type('0.0', { delay: 200 }).should('have.value', '0.0')
|
||||
})
|
||||
|
||||
it('invalid swap amount', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input').type('\\', { delay: 200 }).should('have.value', '')
|
||||
cy.get('#swap-currency-input .token-amount-input').clear().type('\\', { delay: 200 }).should('have.value', '')
|
||||
})
|
||||
|
||||
it('can enter an amount into output', () => {
|
||||
|
||||
116
package.json
@@ -1,31 +1,56 @@
|
||||
{
|
||||
"name": "@uniswap/interface",
|
||||
"name": "@uniswap/widgets",
|
||||
"version": "0.0.22-beta",
|
||||
"description": "Uniswap Interface",
|
||||
"homepage": ".",
|
||||
"main": "dist/interface.js",
|
||||
"module": "dist/interface.esm.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"lib",
|
||||
"dist"
|
||||
],
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"types": "dist/index.d.ts",
|
||||
"main": "dist/cjs/index.cjs",
|
||||
"module": "dist/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/cjs/index.cjs"
|
||||
},
|
||||
"./locales/*": {
|
||||
"import": "./dist/locales/*.js",
|
||||
"require": "./dist/cjs/locales/*.cjs"
|
||||
},
|
||||
"./fonts.css": {
|
||||
"import": "./dist/fonts.css",
|
||||
"require": "./dist/fonts.css"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ethersproject/experimental": "^5.4.0",
|
||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@gnosis.pm/safe-apps-web3-react": "^0.6.0",
|
||||
"@graphql-codegen/cli": "1.21.5",
|
||||
"@graphql-codegen/typescript": "1.22.3",
|
||||
"@graphql-codegen/typescript-operations": "^1.18.2",
|
||||
"@graphql-codegen/typescript-rtk-query": "^1.1.1",
|
||||
"@lingui/cli": "^3.9.0",
|
||||
"@lingui/core": "^3.9.0",
|
||||
"@lingui/macro": "^3.9.0",
|
||||
"@lingui/react": "^3.9.0",
|
||||
"@metamask/jazzicon": "^2.0.0",
|
||||
"@reach/dialog": "^0.10.3",
|
||||
"@reach/portal": "^0.10.3",
|
||||
"@react-hook/window-scroll": "^1.3.0",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@rollup/plugin-alias": "^3.1.9",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"@rollup/plugin-commonjs": "^21.0.1",
|
||||
"@rollup/plugin-eslint": "^8.0.1",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
||||
"@rollup/plugin-replace": "^3.0.1",
|
||||
"@rollup/plugin-typescript": "^8.3.0",
|
||||
"@rollup/plugin-url": "^6.1.0",
|
||||
"@svgr/rollup": "^6.2.0",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
@@ -60,18 +85,15 @@
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
"@uniswap/smart-order-router": "^2.5.10",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@web3-react/fortmatic-connector": "^6.0.9",
|
||||
"@web3-react/injected-connector": "^6.0.7",
|
||||
"@web3-react/portis-connector": "^6.0.9",
|
||||
"@web3-react/walletconnect-connector": "^7.0.2-alpha.0",
|
||||
"@web3-react/walletlink-connector": "^6.2.8",
|
||||
"@web3-react/metamask": "8.0.13-beta.0",
|
||||
"@web3-react/walletconnect": "8.0.18-beta.0",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
"array.prototype.flatmap": "^1.2.4",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^7.7.0",
|
||||
@@ -92,30 +114,45 @@
|
||||
"polyfill-object.fromentries": "^1.0.1",
|
||||
"prettier": "^2.2.1",
|
||||
"qs": "^6.9.4",
|
||||
"react": "^17.0.1",
|
||||
"react-confetti": "^6.0.0",
|
||||
"react-cosmos": "^5.6.6",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-ga": "^2.5.7",
|
||||
"react-is": "^17.0.2",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-use-gesture": "^6.0.14",
|
||||
"redux": "^4.1.2",
|
||||
"redux-localstorage-simple": "^2.3.1",
|
||||
"rollup": "^2.63.0",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-delete": "^2.0.0",
|
||||
"rollup-plugin-dts": "^4.1.0",
|
||||
"rollup-plugin-multi-input": "^1.3.1",
|
||||
"rollup-plugin-node-externals": "^3.1.2",
|
||||
"rollup-plugin-scss": "^3.0.0",
|
||||
"rollup-plugin-typescript2": "^0.31.1",
|
||||
"sass": "^1.45.1",
|
||||
"serve": "^11.3.2",
|
||||
"start-server-and-test": "^1.11.0",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.2.3",
|
||||
"typescript": "^4.4.3",
|
||||
"ua-parser-js": "^0.7.28",
|
||||
"use-count-up": "^2.2.5",
|
||||
"use-resize-observer": "^8.0.0",
|
||||
"wcag-contrast": "^3.0.0",
|
||||
"web-vitals": "^2.1.0",
|
||||
"web3-react-abstract-connector": "npm:@web3-react/abstract-connector@^6.0.7",
|
||||
"web3-react-fortmatic-connector": "npm:@web3-react/fortmatic-connector@^6.0.9",
|
||||
"web3-react-injected-connector": "npm:@web3-react/injected-connector@^6.0.7",
|
||||
"web3-react-portis-connector": "npm:@web3-react/portis-connector@^6.0.9",
|
||||
"web3-react-types": "npm:@web3-react/types@^6.0.7",
|
||||
"web3-react-walletconnect-connector": "npm:@web3-react/walletconnect-connector@^7.0.2-alpha.0",
|
||||
"web3-react-walletlink-connector": "npm:@web3-react/walletlink-connector@^6.2.13",
|
||||
"workbox-core": "^6.1.0",
|
||||
"workbox-precaching": "^6.1.0",
|
||||
"workbox-routing": "^6.1.0"
|
||||
@@ -132,10 +169,11 @@
|
||||
"i18n:extract": "lingui extract --locale en-US",
|
||||
"i18n:compile": "yarn i18n:extract && lingui compile",
|
||||
"i18n:pseudo": "lingui extract --locale pseudo && lingui compile",
|
||||
"postinstall": "yarn contracts:compile && yarn graphql:generate && yarn i18n:compile",
|
||||
"prepare": "yarn contracts:compile && yarn graphql:generate && yarn i18n:compile",
|
||||
"prepublishOnly": "yarn widgets:build",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=./custom-test-env.js",
|
||||
"test": "react-scripts test --env=./custom-test-env.cjs",
|
||||
"test:e2e": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run --record'",
|
||||
"widgets:start": "cosmos",
|
||||
"widgets:build": "rollup --config --failAfterWarnings --configPlugin typescript2"
|
||||
@@ -154,32 +192,38 @@
|
||||
},
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.0",
|
||||
"@ethersproject/abi": "^5.4.1",
|
||||
"@ethersproject/abstract-provider": "^5.4.1",
|
||||
"@ethersproject/abstract-signer": "^5.4.1",
|
||||
"@ethersproject/address": "^5.4.0",
|
||||
"@ethersproject/bignumber": "^5.4.2",
|
||||
"@ethersproject/bytes": "^5.4.0",
|
||||
"@ethersproject/constants": "^5.4.0",
|
||||
"@ethersproject/contracts": "^5.4.1",
|
||||
"@ethersproject/experimental": "^5.4.0",
|
||||
"@ethersproject/hash": "^5.4.0",
|
||||
"@ethersproject/providers": "^5.4.5",
|
||||
"@ethersproject/providers": "^5.4.0",
|
||||
"@ethersproject/solidity": "^5.4.0",
|
||||
"@ethersproject/strings": "^5.4.0",
|
||||
"@ethersproject/units": "^5.4.0",
|
||||
"@ethersproject/wallet": "^5.4.0",
|
||||
"@fontsource/ibm-plex-mono": "^4.5.1",
|
||||
"@fontsource/inter": "^4.5.1",
|
||||
"@lingui/core": "^3.9.0",
|
||||
"@lingui/macro": "^3.9.0",
|
||||
"@lingui/react": "^3.9.0",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@uniswap/redux-multicall": "^1.0.0",
|
||||
"@uniswap/router-sdk": "^1.0.3",
|
||||
"@uniswap/sdk-core": "^3.0.1",
|
||||
"@uniswap/smart-order-router": "^2.5.10",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.27",
|
||||
"@uniswap/v2-sdk": "^3.0.1",
|
||||
"@uniswap/v3-sdk": "^3.7.1",
|
||||
"@web3-react/core": "^6.0.9",
|
||||
"@uniswap/v3-sdk": "^3.8.2",
|
||||
"@web3-react/core": "8.0.17-beta.0",
|
||||
"@web3-react/eip1193": "8.0.12-beta.0",
|
||||
"@web3-react/empty": "8.0.10-beta.0",
|
||||
"@web3-react/types": "8.0.10-beta.0",
|
||||
"@web3-react/url": "8.0.12-beta.0",
|
||||
"ajv": "^6.12.3",
|
||||
"cids": "^1.0.0",
|
||||
"immer": "^9.0.6",
|
||||
@@ -192,24 +236,28 @@
|
||||
"node-vibrant": "^3.2.1-alpha.1",
|
||||
"polished": "^3.3.2",
|
||||
"popper-max-size-modifier": "^0.2.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-feather": "^2.0.8",
|
||||
"react-popper": "^2.2.3",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
"react-window": "^1.8.5",
|
||||
"rebass": "^4.0.7",
|
||||
"redux": "^4.1.2",
|
||||
"setimmediate": "^1.0.5",
|
||||
"styled-components": "^5.3.0",
|
||||
"tiny-invariant": "^1.2.0",
|
||||
"wcag-contrast": "^3.0.0",
|
||||
"wicg-inert": "^3.1.1",
|
||||
"widgets-web3-react/core": "npm:@web3-react/core@8.0.16-alpha.0",
|
||||
"widgets-web3-react/eip1193": "npm:@web3-react/eip1193@8.0.16-alpha.0",
|
||||
"widgets-web3-react/empty": "npm:@web3-react/empty@8.0.17-alpha.0",
|
||||
"widgets-web3-react/metamask": "npm:@web3-react/metamask@8.0.16-alpha.0",
|
||||
"widgets-web3-react/types": "npm:@web3-react/types@8.0.16-alpha.0",
|
||||
"widgets-web3-react/url": "npm:@web3-react/url@8.0.17-alpha.0"
|
||||
"web3-react-core": "npm:@web3-react/core@^6.0.9",
|
||||
"wicg-inert": "^3.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": "^7.17.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-redux": "^7.2.2",
|
||||
"redux": "^4.1.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bufferutil": "^4.0.6",
|
||||
"encoding": "^0.1.13",
|
||||
"utf-8-validate": "^5.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
181
rollup.config.ts
@@ -3,61 +3,174 @@
|
||||
* This library lives in src/lib, but shares code with the interface application.
|
||||
*/
|
||||
|
||||
import eslint from '@rollup/plugin-eslint'
|
||||
import alias from '@rollup/plugin-alias'
|
||||
import babel from '@rollup/plugin-babel'
|
||||
import commonjs from '@rollup/plugin-commonjs'
|
||||
import json from '@rollup/plugin-json'
|
||||
import resolve from '@rollup/plugin-node-resolve'
|
||||
import replace from '@rollup/plugin-replace'
|
||||
import typescript from '@rollup/plugin-typescript'
|
||||
import url from '@rollup/plugin-url'
|
||||
import svgr from '@svgr/rollup'
|
||||
import path from 'path'
|
||||
import { RollupWarning } from 'rollup'
|
||||
import copy from 'rollup-plugin-copy'
|
||||
import del from 'rollup-plugin-delete'
|
||||
import dts from 'rollup-plugin-dts'
|
||||
// @ts-ignore // missing types
|
||||
import multi from 'rollup-plugin-multi-input'
|
||||
import externals from 'rollup-plugin-node-externals'
|
||||
import sass from 'rollup-plugin-scss'
|
||||
import typescript from 'rollup-plugin-typescript2'
|
||||
import { CompilerOptions } from 'typescript'
|
||||
|
||||
import { dependencies } from './package.json'
|
||||
|
||||
const deps = Object.keys(dependencies)
|
||||
|
||||
const replacements = {
|
||||
const REPLACEMENTS = {
|
||||
'process.env.REACT_APP_IS_WIDGET': true,
|
||||
'process.env.REACT_APP_LOCALES': '"./locales"',
|
||||
// esm requires fully-specified paths:
|
||||
'react/jsx-runtime': 'react/jsx-runtime.js',
|
||||
}
|
||||
|
||||
const library = {
|
||||
const EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx']
|
||||
const ASSET_EXTENSIONS = ['.png', '.svg']
|
||||
function isAsset(source: string) {
|
||||
const extname = path.extname(source)
|
||||
return extname && [...ASSET_EXTENSIONS, '.css', '.scss'].includes(extname)
|
||||
}
|
||||
|
||||
const TS_CONFIG = './tsconfig.lib.json'
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { baseUrl, paths }: CompilerOptions = require(TS_CONFIG).compilerOptions
|
||||
const aliases = Object.entries({ ...paths }).flatMap(([find, replacements]) => {
|
||||
return replacements.map((replacement) => ({
|
||||
find: path.dirname(find),
|
||||
replacement: path.join(__dirname, baseUrl || '.', path.dirname(replacement)),
|
||||
}))
|
||||
})
|
||||
|
||||
const plugins = [
|
||||
// Dependency resolution
|
||||
resolve({ extensions: EXTENSIONS }), // resolves third-party modules within node_modules/
|
||||
alias({ entries: aliases }), // resolves paths aliased through the tsconfig (babel does not use tsconfig path resolution)
|
||||
|
||||
// Source code transformation
|
||||
replace({ ...REPLACEMENTS, preventAssignment: true }),
|
||||
json(), // imports json as ES6; doing so enables type-checking and module resolution
|
||||
]
|
||||
|
||||
const check = {
|
||||
input: 'src/lib/index.tsx',
|
||||
output: { file: 'dist/widgets.tsc', inlineDynamicImports: true },
|
||||
external: isAsset,
|
||||
plugins: [
|
||||
externals({ exclude: ['constants'], deps: true, peerDeps: true }), // marks builtins, dependencies, and peerDependencies external
|
||||
...plugins,
|
||||
typescript({ tsconfig: TS_CONFIG }),
|
||||
],
|
||||
onwarn: squelchTranspilationWarnings, // this pipeline is only for typechecking and generating definitions
|
||||
}
|
||||
|
||||
const type = {
|
||||
input: 'dist/dts/lib/index.d.ts',
|
||||
output: { file: 'dist/index.d.ts' },
|
||||
external: isAsset,
|
||||
plugins: [
|
||||
externals({ exclude: ['constants'], deps: true, peerDeps: true }),
|
||||
dts({ compilerOptions: { baseUrl: 'dist/dts' } }),
|
||||
process.env.ROLLUP_WATCH ? undefined : del({ hook: 'buildEnd', targets: ['dist/widgets.tsc', 'dist/dts'] }),
|
||||
],
|
||||
}
|
||||
|
||||
/**
|
||||
* This exports scheme works for nextjs and for CRA5.
|
||||
*
|
||||
* It will also work for CRA4 if you use direct imports:
|
||||
* instead of `import { SwapWidget } from '@uniswap/widgets'`,
|
||||
* `import { SwapWidget } from '@uniswap/widgets/dist/index.js'`.
|
||||
* I do not know why CRA4 does not seem to use exports for resolution.
|
||||
*
|
||||
* Note that chunks are enabled. This is so the tokenlist spec can be loaded async,
|
||||
* to improve first load time (due to ajv). Locales are also in separate chunks.
|
||||
*
|
||||
* Lastly, note that JSON and lingui are bundled into the library, as neither are fully
|
||||
* supported/compatible with ES Modules. Both _could_ be bundled only with esm, but this
|
||||
* yields a less complex pipeline.
|
||||
*/
|
||||
|
||||
const transpile = {
|
||||
input: 'src/lib/index.tsx',
|
||||
output: [
|
||||
{
|
||||
file: 'dist/widgets.js',
|
||||
format: 'cjs',
|
||||
inlineDynamicImports: true,
|
||||
sourcemap: true,
|
||||
dir: 'dist',
|
||||
format: 'esm',
|
||||
sourcemap: false,
|
||||
},
|
||||
{
|
||||
file: 'dist/widgets.esm.js',
|
||||
format: 'esm',
|
||||
inlineDynamicImports: true,
|
||||
sourcemap: true,
|
||||
dir: 'dist/cjs',
|
||||
entryFileNames: '[name].cjs',
|
||||
chunkFileNames: '[name]-[hash].cjs',
|
||||
format: 'cjs',
|
||||
sourcemap: false,
|
||||
},
|
||||
],
|
||||
// necessary because some nested imports (eg jotai/*) would otherwise not resolve.
|
||||
external: (source: string) => Boolean(deps.find((dep) => source === dep || source.startsWith(dep + '/'))),
|
||||
plugins: [
|
||||
eslint({ include: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'] }),
|
||||
json(), // imports json
|
||||
replace({ ...replacements, preventAssignment: true }),
|
||||
url(), // imports files (including svgs) as data URIs
|
||||
externals({
|
||||
exclude: [
|
||||
'constants',
|
||||
/@lingui\/(core|react)/, // @lingui incorrectly exports esm, so it must be bundled in
|
||||
/\.json$/, // esm does not support JSON loading, so it must be bundled in
|
||||
],
|
||||
deps: true,
|
||||
peerDeps: true,
|
||||
}),
|
||||
...plugins,
|
||||
|
||||
// Source code transformation
|
||||
url({ include: ASSET_EXTENSIONS.map((extname) => '**/*' + extname), limit: Infinity }), // imports assets as data URIs
|
||||
svgr({ exportType: 'named', svgo: false }), // imports svgs as React components
|
||||
sass(), // imports sass styles
|
||||
typescript({ tsconfig: './tsconfig.lib.json', useTsconfigDeclarationDir: true }),
|
||||
sass({ output: 'dist/fonts.css' }), // generates fonts.css
|
||||
commonjs(), // transforms cjs dependencies into tree-shakeable ES modules
|
||||
|
||||
babel({
|
||||
babelHelpers: 'runtime',
|
||||
presets: ['@babel/preset-env', ['@babel/preset-react', { runtime: 'automatic' }], '@babel/preset-typescript'],
|
||||
extensions: EXTENSIONS,
|
||||
plugins: [
|
||||
'macros', // enables @lingui and styled-components macros
|
||||
'@babel/plugin-transform-runtime', // embeds the babel runtime for library distribution
|
||||
],
|
||||
}),
|
||||
],
|
||||
onwarn: squelchTypeWarnings, // this pipeline is only for transpilation
|
||||
}
|
||||
|
||||
const locales = {
|
||||
input: 'src/locales/*.js',
|
||||
output: [
|
||||
{
|
||||
dir: 'dist',
|
||||
format: 'esm',
|
||||
sourcemap: false,
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
copy({
|
||||
copyOnce: true,
|
||||
targets: [{ src: 'src/locales/*.js', dest: 'dist/cjs/locales', rename: (name) => `${name}.cjs` }],
|
||||
}),
|
||||
commonjs(),
|
||||
multi(),
|
||||
],
|
||||
}
|
||||
|
||||
const typings = {
|
||||
input: 'dist/dts/lib/index.d.ts',
|
||||
output: {
|
||||
file: 'dist/widgets.d.ts',
|
||||
format: 'es',
|
||||
},
|
||||
external: (source: string) => source.endsWith('.scss'),
|
||||
plugins: [dts({ compilerOptions: { baseUrl: 'dist/dts' } })],
|
||||
const config = [check, type, transpile, locales]
|
||||
export default config
|
||||
|
||||
function squelchTranspilationWarnings(warning: RollupWarning, warn: (warning: RollupWarning) => void) {
|
||||
if (warning.pluginCode === 'TS5055') return
|
||||
warn(warning)
|
||||
}
|
||||
|
||||
const config = [library, typings]
|
||||
export default config
|
||||
function squelchTypeWarnings(warning: RollupWarning, warn: (warning: RollupWarning) => void) {
|
||||
if (warning.code === 'UNUSED_EXTERNAL_IMPORT') return
|
||||
warn(warning)
|
||||
}
|
||||
|
||||
BIN
src/assets/images/ukraine.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
@@ -1,3 +1,3 @@
|
||||
<svg width="100%" height="35" viewBox="800 0 300 200" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="0" x2="2000" y1="100" y2="100" stroke="currentColor" stroke-width="20" stroke-linecap="round" stroke-dasharray="1, 45"/>
|
||||
<svg width="100%" height="35" viewBox="850 0 300 200" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="0" x2="3000" y1="100" y2="100" stroke="currentColor" stroke-width="20" stroke-linecap="round" stroke-dasharray="1, 45"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 233 B After Width: | Height: | Size: 233 B |
@@ -1,16 +1,5 @@
|
||||
<svg width="170" height="168" viewBox="0 0 170 168" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path opacity="0.6" d="M85.05 168C132.022 168 170.1 130.105 170.1 83.3593C170.1 36.6135 0 36.6135 0 83.3593C0 130.105 38.0782 168 85.05 168Z" fill="#FF505F"/>
|
||||
<path opacity="0.6" d="M85.05 168C132.022 168 170.1 130.105 170.1 83.3593C170.1 36.6135 0 36.6135 0 83.3593C0 130.105 38.0782 168 85.05 168Z" fill="#FF0320"/>
|
||||
<path d="M85.05 0C132.022 0 170.1 37.8949 170.1 84.6407C170.1 131.386 0 131.386 0 84.6407C0 37.8949 38.0782 0 85.05 0Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M144.665 64.0394L112.444 12.3742L89.0263 78.9477L144.665 64.0394Z" fill="#FF4E65"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M143.777 64.215L112.444 12.3742L165.349 58.4347L143.777 64.215Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M144.551 63.613L142.479 124.467L88.912 78.5213L144.551 63.613Z" fill="#D0001A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M143.663 63.7886L142.479 124.467L165.235 58.0083L143.663 63.7886Z" fill="#FF697B"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="170" height="168" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="250" cy="250" r="250" fill="#FF0420"/>
|
||||
<path d="M177.133 316.446C162.247 316.446 150.051 312.943 140.544 305.938C131.162 298.808 126.471 288.676 126.471 275.541C126.471 272.789 126.784 269.411 127.409 265.408C129.036 256.402 131.35 245.581 134.352 232.947C142.858 198.547 164.812 181.347 200.213 181.347C209.845 181.347 218.476 182.973 226.107 186.225C233.738 189.352 239.742 194.106 244.12 200.486C248.498 206.74 250.688 214.246 250.688 223.002C250.688 225.629 250.375 228.944 249.749 232.947C247.873 244.08 245.621 254.901 242.994 265.408C238.616 282.546 231.048 295.368 220.29 303.874C209.532 312.255 195.147 316.446 177.133 316.446ZM179.76 289.426C186.766 289.426 192.707 287.362 197.586 283.234C202.59 279.106 206.155 272.789 208.281 264.283C211.158 252.524 213.348 242.266 214.849 233.51C215.349 230.883 215.599 228.194 215.599 225.441C215.599 214.058 209.657 208.366 197.774 208.366C190.768 208.366 184.764 210.43 179.76 214.558C174.882 218.687 171.379 225.004 169.253 233.51C167.001 241.891 164.749 252.149 162.498 264.283C161.997 266.784 161.747 269.411 161.747 272.163C161.747 283.672 167.752 289.426 179.76 289.426Z" fill="white"/>
|
||||
<path d="M259.303 314.57C257.927 314.57 256.863 314.132 256.113 313.256C255.487 312.255 255.3 311.13 255.55 309.879L281.444 187.914C281.694 186.538 282.382 185.412 283.508 184.536C284.634 183.661 285.822 183.223 287.073 183.223H336.985C350.87 183.223 362.003 186.1 370.384 191.854C378.891 197.609 383.144 205.927 383.144 216.81C383.144 219.937 382.769 223.19 382.018 226.567C378.891 240.953 372.574 251.586 363.067 258.466C353.685 265.346 340.8 268.786 324.413 268.786H299.082L290.451 309.879C290.2 311.255 289.512 312.38 288.387 313.256C287.261 314.132 286.072 314.57 284.822 314.57H259.303ZM325.727 242.892C330.98 242.892 335.546 241.453 339.424 238.576C343.427 235.699 346.054 231.571 347.305 226.192C347.68 224.065 347.868 222.189 347.868 220.563C347.868 216.935 346.805 214.183 344.678 212.307C342.551 210.305 338.924 209.305 333.795 209.305H311.278L304.148 242.892H325.727Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -1,11 +1,11 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { AbstractConnector } from '@web3-react/abstract-connector'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useCallback, useContext } from 'react'
|
||||
import { ExternalLink as LinkIcon } from 'react-feather'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { Connector } from 'widgets-web3-react/types'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
|
||||
import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
import { injected, portis, walletlink } from '../../connectors'
|
||||
|
||||
@@ -32,6 +32,7 @@ const BLOCKED_ADDRESSES: string[] = [
|
||||
'0x48549a34ae37b12f6a30566245176994e17c6b4a',
|
||||
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0',
|
||||
'0xc455f7fd3e0e12afd51fba5c106909934d8a0e4a',
|
||||
'0x629e7Da20197a5429d30da36E77d06CdF796b71A',
|
||||
]
|
||||
|
||||
export default function Blocklist({ children }: { children: ReactNode }) {
|
||||
|
||||
@@ -51,6 +51,16 @@ const FlyoutHeader = styled.div`
|
||||
font-weight: 400;
|
||||
`
|
||||
const FlyoutMenu = styled.div`
|
||||
position: absolute;
|
||||
top: 54px;
|
||||
width: 272px;
|
||||
z-index: 99;
|
||||
padding-top: 10px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
top: 40px;
|
||||
}
|
||||
`
|
||||
const FlyoutMenuContents = styled.div`
|
||||
align-items: flex-start;
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
|
||||
@@ -61,16 +71,9 @@ const FlyoutMenu = styled.div`
|
||||
font-size: 16px;
|
||||
overflow: auto;
|
||||
padding: 16px;
|
||||
position: absolute;
|
||||
top: 64px;
|
||||
width: 272px;
|
||||
z-index: 99;
|
||||
& > *:not(:last-child) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
top: 50px;
|
||||
}
|
||||
`
|
||||
const FlyoutRow = styled.div<{ active: boolean }>`
|
||||
align-items: center;
|
||||
@@ -305,21 +308,23 @@ export default function NetworkSelector() {
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectorWrapper ref={node as any}>
|
||||
<SelectorControls onClick={toggle} interactive>
|
||||
<SelectorWrapper ref={node as any} onMouseEnter={toggle} onMouseLeave={toggle}>
|
||||
<SelectorControls interactive>
|
||||
<SelectorLogo interactive src={info.logoUrl} />
|
||||
<SelectorLabel>{info.label}</SelectorLabel>
|
||||
<StyledChevronDown />
|
||||
</SelectorControls>
|
||||
{open && (
|
||||
<FlyoutMenu onMouseLeave={toggle}>
|
||||
<FlyoutHeader>
|
||||
<Trans>Select a network</Trans>
|
||||
</FlyoutHeader>
|
||||
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.MAINNET} />
|
||||
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.POLYGON} />
|
||||
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.OPTIMISM} />
|
||||
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.ARBITRUM_ONE} />
|
||||
<FlyoutMenu>
|
||||
<FlyoutMenuContents>
|
||||
<FlyoutHeader>
|
||||
<Trans>Select a network</Trans>
|
||||
</FlyoutHeader>
|
||||
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.MAINNET} />
|
||||
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.POLYGON} />
|
||||
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.OPTIMISM} />
|
||||
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.ARBITRUM_ONE} />
|
||||
</FlyoutMenuContents>
|
||||
</FlyoutMenu>
|
||||
)}
|
||||
</SelectorWrapper>
|
||||
|
||||
@@ -264,9 +264,7 @@ export default function Header() {
|
||||
|
||||
const {
|
||||
infoLink,
|
||||
addNetworkInfo: {
|
||||
nativeCurrency: { symbol: nativeCurrencySymbol },
|
||||
},
|
||||
nativeCurrency: { symbol: nativeCurrencySymbol },
|
||||
} = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AbstractConnector } from '@web3-react/abstract-connector'
|
||||
import { Connector } from 'widgets-web3-react/types'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
|
||||
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
|
||||
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
|
||||
|
||||
139
src/components/Popups/DonationLink.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import FlagImage from 'assets/images/ukraine.png'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { RowBetween, RowFixed } from 'components/Row'
|
||||
import { X } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useDarkModeManager, useShowDonationLink } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText, Z_INDEX } from 'theme'
|
||||
|
||||
const darkGradient = `radial-gradient(87.53% 3032.45% at 5.16% 10.13%, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%),
|
||||
linear-gradient(0deg, rgba(0, 91, 187, 0.35), rgba(0, 91, 187, 0.35)), #000000;`
|
||||
const lightGradient = `radial-gradient(87.53% 3032.45% at 5.16% 10.13%, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%), linear-gradient(0deg, #CBE4FF, #CBE4FF), linear-gradient(0deg, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.09)), radial-gradient(100% 93.36% at 0% 6.64%, #8BC4FF 0%, #FFF5BF 100%);`
|
||||
|
||||
const Wrapper = styled(AutoColumn)<{ darkMode: boolean }>`
|
||||
background: #edeef2;
|
||||
position: fixed;
|
||||
bottom: 40px;
|
||||
border-radius: 12px;
|
||||
padding: 18px;
|
||||
max-width: 360px;
|
||||
background: ${({ darkMode }) => (darkMode ? darkGradient : lightGradient)};
|
||||
color: ${({ theme }) => theme.text1};
|
||||
z-index: ${Z_INDEX.deprecated_content};
|
||||
|
||||
:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
& > * {
|
||||
z-index: ${Z_INDEX.fixed};
|
||||
}
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
:before {
|
||||
background-image: url(${FlagImage});
|
||||
background-repeat: no-repeat;
|
||||
overflow: hidden;
|
||||
background-size: 300px;
|
||||
content: '';
|
||||
height: 1200px;
|
||||
width: 400px;
|
||||
opacity: 0.1;
|
||||
position: absolute;
|
||||
transform: rotate(25deg) translate(-140px, -60px);
|
||||
width: 300px;
|
||||
z-index: ${Z_INDEX.deprecated_zero};
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
max-width: 100%;
|
||||
`}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
position: relative;
|
||||
bottom: unset;
|
||||
`}
|
||||
`
|
||||
|
||||
const WrappedCloseIcon = styled(X)`
|
||||
stroke: ${({ theme }) => theme.text2};
|
||||
z-index: ${Z_INDEX.tooltip};
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledFlagImage = styled.div`
|
||||
margin-right: 12px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 100%;
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
width: 9px;
|
||||
height: 18px;
|
||||
}
|
||||
&:before {
|
||||
float: left;
|
||||
border-top-left-radius: 9px;
|
||||
border-bottom-left-radius: 9px;
|
||||
background: #005bbb;
|
||||
}
|
||||
&:after {
|
||||
float: right;
|
||||
border-top-right-radius: 9px;
|
||||
border-bottom-right-radius: 9px;
|
||||
background: #ffd500;
|
||||
}
|
||||
transform: rotate(90deg);
|
||||
`
|
||||
|
||||
const StyledLink = styled(ExternalLink)`
|
||||
text-decoration: none !important;
|
||||
`
|
||||
|
||||
export default function DonationLink() {
|
||||
const [darkMode] = useDarkModeManager()
|
||||
const [, setVisible] = useShowDonationLink()
|
||||
|
||||
return (
|
||||
<Wrapper
|
||||
gap="10px"
|
||||
darkMode={darkMode}
|
||||
as={StyledLink}
|
||||
target="https://donate.uniswap.org/#/swap"
|
||||
href="https://donate.uniswap.org/#/swap"
|
||||
onClickCapture={() => {
|
||||
ReactGA.event({
|
||||
category: 'Donate',
|
||||
action: 'Link to Ukraine site.',
|
||||
})
|
||||
}}
|
||||
>
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<StyledFlagImage />
|
||||
<ThemedText.Body fontWeight={600} fontSize={'18px'}>
|
||||
<Trans>Donate to Ukraine</Trans>
|
||||
</ThemedText.Body>
|
||||
</RowFixed>
|
||||
<WrappedCloseIcon
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setVisible(false)
|
||||
return false
|
||||
}}
|
||||
/>
|
||||
</RowBetween>
|
||||
<ThemedText.Body fontWeight={400} fontSize="12px" color="text2">
|
||||
<Trans>Directly support the Ukrainian government by donating tokens.</Trans>
|
||||
</ThemedText.Body>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,25 +1,27 @@
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { MEDIA_WIDTHS } from 'theme'
|
||||
|
||||
import { useActivePopups } from '../../state/application/hooks'
|
||||
import { useShowSurveyPopup, useURLWarningVisible } from '../../state/user/hooks'
|
||||
import { useShowDonationLink, useURLWarningVisible } from '../../state/user/hooks'
|
||||
import { AutoColumn } from '../Column'
|
||||
import ClaimPopup from './ClaimPopup'
|
||||
import DonationLink from './DonationLink'
|
||||
import PopupItem from './PopupItem'
|
||||
import SurveyPopup from './SurveyPopup'
|
||||
|
||||
const MobilePopupWrapper = styled.div<{ height: string | number }>`
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
height: ${({ height }) => height};
|
||||
margin: ${({ height }) => (height ? '0 auto;' : 0)};
|
||||
margin-bottom: ${({ height }) => (height ? '20px' : 0)}};
|
||||
margin-bottom: ${({ height }) => (height ? '20px' : 0)};
|
||||
|
||||
display: none;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
display: block;
|
||||
padding-top: 20px;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -60,27 +62,29 @@ export default function Popups() {
|
||||
// get all popups
|
||||
const activePopups = useActivePopups()
|
||||
|
||||
// show survey popup if user has not closed
|
||||
const [showSurveyPopup] = useShowSurveyPopup()
|
||||
|
||||
const urlWarningActive = useURLWarningVisible()
|
||||
|
||||
// need extra padding if network is not L1 Ethereum
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const isNotOnMainnet = Boolean(chainId && chainId !== SupportedChainId.MAINNET)
|
||||
|
||||
const location = useLocation()
|
||||
const isOnSwapPage = location.pathname.includes('swap')
|
||||
const [donationVisible] = useShowDonationLink()
|
||||
const showDonation = donationVisible && isOnSwapPage
|
||||
|
||||
return (
|
||||
<>
|
||||
<FixedPopupColumn gap="20px" extraPadding={urlWarningActive} xlPadding={isNotOnMainnet}>
|
||||
<ClaimPopup />
|
||||
<SurveyPopup />
|
||||
{activePopups.map((item) => (
|
||||
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
|
||||
))}
|
||||
{showDonation ? <DonationLink /> : null}
|
||||
</FixedPopupColumn>
|
||||
<MobilePopupWrapper height={activePopups?.length > 0 || showSurveyPopup ? 'fit-content' : 0}>
|
||||
<MobilePopupWrapper height={activePopups?.length > 0 || showDonation ? 'fit-content' : 0}>
|
||||
<MobilePopupInner>
|
||||
<SurveyPopup />
|
||||
{showDonation ? <DonationLink /> : null}
|
||||
{activePopups // reverse so new items up front
|
||||
.slice(0)
|
||||
.reverse()
|
||||
|
||||
@@ -19,7 +19,7 @@ import { PositionDetails } from 'types/position'
|
||||
import { formatTickPrice } from 'utils/formatTickPrice'
|
||||
import { unwrappedToken } from 'utils/unwrappedToken'
|
||||
|
||||
import { DAI, USDC, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
|
||||
import { DAI, USDC_MAINNET, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
|
||||
|
||||
const LinkRow = styled(Link)`
|
||||
align-items: center;
|
||||
@@ -145,7 +145,7 @@ export function getPriceOrderingFromPositionForUI(position?: Position): {
|
||||
const token1 = position.amount1.currency
|
||||
|
||||
// if token0 is a dollar-stable asset, set it as the quote token
|
||||
const stables = [DAI, USDC, USDT]
|
||||
const stables = [DAI, USDC_MAINNET, USDT]
|
||||
if (stables.some((stable) => stable.equals(token0))) {
|
||||
return {
|
||||
priceLower: position.token0PriceUpper.invert(),
|
||||
@@ -157,7 +157,7 @@ export function getPriceOrderingFromPositionForUI(position?: Position): {
|
||||
|
||||
// if token1 is an ETH-/BTC-stable asset, set it as the base token
|
||||
const bases = [...Object.values(WRAPPED_NATIVE_CURRENCY), WBTC]
|
||||
if (bases.some((base) => base.equals(token1))) {
|
||||
if (bases.some((base) => base && base.equals(token1))) {
|
||||
return {
|
||||
priceLower: position.token0PriceUpper.invert(),
|
||||
priceUpper: position.token0PriceLower.invert(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Protocol } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { DAI, USDC, WBTC } from 'constants/tokens'
|
||||
import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens'
|
||||
import { render } from 'test-utils'
|
||||
|
||||
import RoutingDiagram, { RoutingDiagramEntry } from './RoutingDiagram'
|
||||
@@ -10,16 +10,16 @@ const percent = (strings: TemplateStringsArray) => new Percent(parseInt(strings[
|
||||
|
||||
const singleRoute: RoutingDiagramEntry = {
|
||||
percent: percent`100`,
|
||||
path: [[USDC, DAI, FeeAmount.LOW]],
|
||||
path: [[USDC_MAINNET, DAI, FeeAmount.LOW]],
|
||||
protocol: Protocol.V3,
|
||||
}
|
||||
|
||||
const multiRoute: RoutingDiagramEntry[] = [
|
||||
{ percent: percent`75`, path: [[USDC, DAI, FeeAmount.LOWEST]], protocol: Protocol.V2 },
|
||||
{ percent: percent`75`, path: [[USDC_MAINNET, DAI, FeeAmount.LOWEST]], protocol: Protocol.V2 },
|
||||
{
|
||||
percent: percent`25`,
|
||||
path: [
|
||||
[USDC, WBTC, FeeAmount.MEDIUM],
|
||||
[USDC_MAINNET, WBTC, FeeAmount.MEDIUM],
|
||||
[WBTC, DAI, FeeAmount.HIGH],
|
||||
],
|
||||
protocol: Protocol.V3,
|
||||
@@ -47,16 +47,16 @@ jest.mock('hooks/useTokenInfoFromActiveList', () => ({
|
||||
}))
|
||||
|
||||
it('renders when no routes are provided', () => {
|
||||
const { asFragment } = render(<RoutingDiagram currencyIn={DAI} currencyOut={USDC} routes={[]} />)
|
||||
const { asFragment } = render(<RoutingDiagram currencyIn={DAI} currencyOut={USDC_MAINNET} routes={[]} />)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders single route', () => {
|
||||
const { asFragment } = render(<RoutingDiagram currencyIn={USDC} currencyOut={DAI} routes={[singleRoute]} />)
|
||||
const { asFragment } = render(<RoutingDiagram currencyIn={USDC_MAINNET} currencyOut={DAI} routes={[singleRoute]} />)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders multi route', () => {
|
||||
const { asFragment } = render(<RoutingDiagram currencyIn={USDC} currencyOut={DAI} routes={multiRoute} />)
|
||||
const { asFragment } = render(<RoutingDiagram currencyIn={USDC_MAINNET} currencyOut={DAI} routes={multiRoute} />)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Protocol } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent } from '@uniswap/sdk-core'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import Badge from 'components/Badge'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
import Row, { AutoRow } from 'components/Row'
|
||||
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
|
||||
import { RoutingDiagramEntry } from 'lib/components/Swap/RoutingDiagram/utils'
|
||||
import { Box } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText, Z_INDEX } from 'theme'
|
||||
@@ -14,12 +14,6 @@ import { ThemedText, Z_INDEX } from 'theme'
|
||||
import { ReactComponent as DotLine } from '../../assets/svg/dot_line.svg'
|
||||
import { MouseoverTooltip } from '../Tooltip'
|
||||
|
||||
export interface RoutingDiagramEntry {
|
||||
percent: Percent
|
||||
path: [Currency, Currency, FeeAmount][]
|
||||
protocol: Protocol
|
||||
}
|
||||
|
||||
const Wrapper = styled(Box)`
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
@@ -3,45 +3,45 @@
|
||||
exports[`renders multi route 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="RoutingDiagram__Wrapper-sc-o1ook0-0 ePDWDk css-vurnku"
|
||||
class="RoutingDiagram__Wrapper-sc-i2tbb-0 ivndgC css-vurnku"
|
||||
>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-nrd8cx-0 RoutingDiagram__RouteContainerRow-sc-o1ook0-1 lmTMKd itvFNV ibRCpr"
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteContainerRow-sc-i2tbb-1 lmTMKd hLLNig hDkZVB"
|
||||
>
|
||||
CurrencyLogo currency=USDC
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-nrd8cx-0 RoutingDiagram__RouteRow-sc-o1ook0-2 lmTMKd itvFNV fzMiot"
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteRow-sc-i2tbb-2 lmTMKd hLLNig hUDqOH"
|
||||
>
|
||||
<div
|
||||
class="RoutingDiagram__DottedLine-sc-o1ook0-4 kkXINS"
|
||||
class="RoutingDiagram__DottedLine-sc-i2tbb-4 cKqYfU"
|
||||
>
|
||||
<svg
|
||||
class="RoutingDiagram__DotColor-sc-o1ook0-5 kgYqrO"
|
||||
class="RoutingDiagram__DotColor-sc-i2tbb-5 fhSaBA"
|
||||
>
|
||||
dot_line.svg
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="Badge-sc-1mhw5si-0 RoutingDiagram__OpaqueBadge-sc-o1ook0-6 gayll OurGh"
|
||||
class="Badge-sc-3epor3-0 RoutingDiagram__OpaqueBadge-sc-i2tbb-6 knpfHF gGARxH"
|
||||
>
|
||||
<div
|
||||
class="Badge-sc-1mhw5si-0 RoutingDiagram__ProtocolBadge-sc-o1ook0-7 gayll bNVqMw"
|
||||
class="Badge-sc-3epor3-0 RoutingDiagram__ProtocolBadge-sc-i2tbb-7 knpfHF lbdUti"
|
||||
>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 cWOfab RoutingDiagram__BadgeText-sc-o1ook0-8 dYpdfO css-15li2d9"
|
||||
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-15li2d9"
|
||||
>
|
||||
V2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 cWOfab RoutingDiagram__BadgeText-sc-o1ook0-8 dYpdfO css-1aekuku"
|
||||
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-1aekuku"
|
||||
style="min-width: auto;"
|
||||
>
|
||||
75%
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-nrd8cx-0 Row__AutoRow-sc-nrd8cx-3 iqvZFe itvFNV kkMfuq"
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__AutoRow-sc-u7azg8-3 iqvZFe hLLNig cUhARX"
|
||||
style="justify-content: space-evenly; z-index: 2;"
|
||||
width="100%"
|
||||
>
|
||||
@@ -51,42 +51,42 @@ exports[`renders multi route 1`] = `
|
||||
CurrencyLogo currency=DAI
|
||||
</div>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-nrd8cx-0 RoutingDiagram__RouteContainerRow-sc-o1ook0-1 lmTMKd itvFNV ibRCpr"
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteContainerRow-sc-i2tbb-1 lmTMKd hLLNig hDkZVB"
|
||||
>
|
||||
CurrencyLogo currency=USDC
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-nrd8cx-0 RoutingDiagram__RouteRow-sc-o1ook0-2 lmTMKd itvFNV fzMiot"
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteRow-sc-i2tbb-2 lmTMKd hLLNig hUDqOH"
|
||||
>
|
||||
<div
|
||||
class="RoutingDiagram__DottedLine-sc-o1ook0-4 kkXINS"
|
||||
class="RoutingDiagram__DottedLine-sc-i2tbb-4 cKqYfU"
|
||||
>
|
||||
<svg
|
||||
class="RoutingDiagram__DotColor-sc-o1ook0-5 kgYqrO"
|
||||
class="RoutingDiagram__DotColor-sc-i2tbb-5 fhSaBA"
|
||||
>
|
||||
dot_line.svg
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="Badge-sc-1mhw5si-0 RoutingDiagram__OpaqueBadge-sc-o1ook0-6 gayll OurGh"
|
||||
class="Badge-sc-3epor3-0 RoutingDiagram__OpaqueBadge-sc-i2tbb-6 knpfHF gGARxH"
|
||||
>
|
||||
<div
|
||||
class="Badge-sc-1mhw5si-0 RoutingDiagram__ProtocolBadge-sc-o1ook0-7 gayll bNVqMw"
|
||||
class="Badge-sc-3epor3-0 RoutingDiagram__ProtocolBadge-sc-i2tbb-7 knpfHF lbdUti"
|
||||
>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 cWOfab RoutingDiagram__BadgeText-sc-o1ook0-8 dYpdfO css-15li2d9"
|
||||
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-15li2d9"
|
||||
>
|
||||
V3
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 cWOfab RoutingDiagram__BadgeText-sc-o1ook0-8 dYpdfO css-1aekuku"
|
||||
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-1aekuku"
|
||||
style="min-width: auto;"
|
||||
>
|
||||
25%
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-nrd8cx-0 Row__AutoRow-sc-nrd8cx-3 iqvZFe itvFNV kkMfuq"
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__AutoRow-sc-u7azg8-3 iqvZFe hLLNig cUhARX"
|
||||
style="justify-content: space-evenly; z-index: 2;"
|
||||
width="100%"
|
||||
>
|
||||
@@ -102,45 +102,45 @@ exports[`renders multi route 1`] = `
|
||||
exports[`renders single route 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="RoutingDiagram__Wrapper-sc-o1ook0-0 ePDWDk css-vurnku"
|
||||
class="RoutingDiagram__Wrapper-sc-i2tbb-0 ivndgC css-vurnku"
|
||||
>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-nrd8cx-0 RoutingDiagram__RouteContainerRow-sc-o1ook0-1 lmTMKd itvFNV ibRCpr"
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteContainerRow-sc-i2tbb-1 lmTMKd hLLNig hDkZVB"
|
||||
>
|
||||
CurrencyLogo currency=USDC
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-nrd8cx-0 RoutingDiagram__RouteRow-sc-o1ook0-2 lmTMKd itvFNV fzMiot"
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteRow-sc-i2tbb-2 lmTMKd hLLNig hUDqOH"
|
||||
>
|
||||
<div
|
||||
class="RoutingDiagram__DottedLine-sc-o1ook0-4 kkXINS"
|
||||
class="RoutingDiagram__DottedLine-sc-i2tbb-4 cKqYfU"
|
||||
>
|
||||
<svg
|
||||
class="RoutingDiagram__DotColor-sc-o1ook0-5 kgYqrO"
|
||||
class="RoutingDiagram__DotColor-sc-i2tbb-5 fhSaBA"
|
||||
>
|
||||
dot_line.svg
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="Badge-sc-1mhw5si-0 RoutingDiagram__OpaqueBadge-sc-o1ook0-6 gayll OurGh"
|
||||
class="Badge-sc-3epor3-0 RoutingDiagram__OpaqueBadge-sc-i2tbb-6 knpfHF gGARxH"
|
||||
>
|
||||
<div
|
||||
class="Badge-sc-1mhw5si-0 RoutingDiagram__ProtocolBadge-sc-o1ook0-7 gayll bNVqMw"
|
||||
class="Badge-sc-3epor3-0 RoutingDiagram__ProtocolBadge-sc-i2tbb-7 knpfHF lbdUti"
|
||||
>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 cWOfab RoutingDiagram__BadgeText-sc-o1ook0-8 dYpdfO css-15li2d9"
|
||||
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-15li2d9"
|
||||
>
|
||||
V3
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 cWOfab RoutingDiagram__BadgeText-sc-o1ook0-8 dYpdfO css-1aekuku"
|
||||
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-1aekuku"
|
||||
style="min-width: auto;"
|
||||
>
|
||||
100%
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-nrd8cx-0 Row__AutoRow-sc-nrd8cx-3 iqvZFe itvFNV kkMfuq"
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__AutoRow-sc-u7azg8-3 iqvZFe hLLNig cUhARX"
|
||||
style="justify-content: space-evenly; z-index: 2;"
|
||||
width="100%"
|
||||
>
|
||||
@@ -156,7 +156,7 @@ exports[`renders single route 1`] = `
|
||||
exports[`renders when no routes are provided 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="RoutingDiagram__Wrapper-sc-o1ook0-0 ePDWDk css-vurnku"
|
||||
class="RoutingDiagram__Wrapper-sc-i2tbb-0 ivndgC css-vurnku"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
@@ -48,7 +48,7 @@ export function ImportToken(props: ImportProps) {
|
||||
<RowBetween>
|
||||
{onBack ? <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} /> : <div />}
|
||||
<ThemedText.MediumHeader>
|
||||
<Plural value={tokens.length} one="Import token" other="Import tokens" />
|
||||
<Plural value={tokens.length} _1="Import token" other="Import tokens" />
|
||||
</ThemedText.MediumHeader>
|
||||
{onDismiss ? <CloseIcon onClick={onDismiss} /> : <div />}
|
||||
</RowBetween>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'lib/hooks/routing/clientSideSmartOrderRouter'
|
||||
import { useContext, useRef, useState } from 'react'
|
||||
import { Settings, X } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Text } from 'rebass'
|
||||
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'state/routing/clientSideSmartOrderRouter/constants'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { AbstractConnector } from '@web3-react/abstract-connector'
|
||||
import { darken } from 'polished'
|
||||
import styled from 'styled-components/macro'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
|
||||
import { injected } from '../../connectors'
|
||||
import { SUPPORTED_WALLETS } from '../../constants/wallet'
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { AbstractConnector } from '@web3-react/abstract-connector'
|
||||
import { UnsupportedChainIdError, useWeb3React } from '@web3-react/core'
|
||||
import { WalletConnectConnector } from '@web3-react/walletconnect-connector'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { PrivacyPolicy } from 'components/PrivacyPolicy'
|
||||
import Row, { AutoRow, RowBetween } from 'components/Row'
|
||||
@@ -10,6 +7,9 @@ import { useEffect, useState } from 'react'
|
||||
import { ArrowLeft, ArrowRight, Info } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import styled from 'styled-components/macro'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
import { UnsupportedChainIdError, useWeb3React } from 'web3-react-core'
|
||||
import { WalletConnectConnector } from 'web3-react-walletconnect-connector'
|
||||
|
||||
import MetamaskIcon from '../../assets/images/metamask.png'
|
||||
import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useEffect } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useWeb3React } from 'web3-react-core'
|
||||
|
||||
import { network } from '../../connectors'
|
||||
import { NetworkContextName } from '../../constants/misc'
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { AbstractConnector } from '@web3-react/abstract-connector'
|
||||
import { UnsupportedChainIdError, useWeb3React } from '@web3-react/core'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { darken } from 'polished'
|
||||
import { useMemo } from 'react'
|
||||
import { Activity } from 'react-feather'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { Connector } from 'widgets-web3-react/types'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
import { UnsupportedChainIdError, useWeb3React } from 'web3-react-core'
|
||||
|
||||
import { NetworkContextName } from '../../constants/misc'
|
||||
import useENSName from '../../hooks/useENSName'
|
||||
|
||||
@@ -4,6 +4,8 @@ import ReactGA from 'react-ga'
|
||||
import { RouteComponentProps } from 'react-router-dom'
|
||||
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
|
||||
|
||||
import { GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY } from './index'
|
||||
|
||||
function reportWebVitals({ name, delta, id }: Metric) {
|
||||
ReactGA.timing({
|
||||
category: 'Web Vitals',
|
||||
@@ -31,5 +33,15 @@ export default function GoogleAnalyticsReporter({ location: { pathname, search }
|
||||
useEffect(() => {
|
||||
ReactGA.pageview(`${pathname}${search}`)
|
||||
}, [pathname, search])
|
||||
|
||||
useEffect(() => {
|
||||
// typed as 'any' in react-ga -.-
|
||||
ReactGA.ga((tracker: any) => {
|
||||
if (!tracker) return
|
||||
|
||||
const clientId = tracker.get('clientId')
|
||||
window.localStorage.setItem(GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY, clientId)
|
||||
})
|
||||
}, [])
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import ReactGA from 'react-ga'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
|
||||
export const GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY = 'ga_client_id'
|
||||
const GOOGLE_ANALYTICS_ID: string | undefined = process.env.REACT_APP_GOOGLE_ANALYTICS_ID
|
||||
|
||||
const storedClientId = window.localStorage.getItem(GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY)
|
||||
|
||||
if (typeof GOOGLE_ANALYTICS_ID === 'string') {
|
||||
ReactGA.initialize(GOOGLE_ANALYTICS_ID, {
|
||||
gaOptions: {
|
||||
storage: 'none',
|
||||
storeGac: false,
|
||||
clientId: storedClientId ?? undefined,
|
||||
},
|
||||
})
|
||||
ReactGA.set({
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import StakingRewardsJson from '@uniswap/liquidity-staker/build/StakingRewards.json'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { useStakingContract } from '../../hooks/useContract'
|
||||
import { useContract } from '../../hooks/useContract'
|
||||
import { StakingInfo } from '../../state/stake/hooks'
|
||||
import { TransactionType } from '../../state/transactions/actions'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
@@ -15,6 +16,12 @@ import Modal from '../Modal'
|
||||
import { LoadingView, SubmittedView } from '../ModalViews'
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
const { abi: STAKING_REWARDS_ABI } = StakingRewardsJson
|
||||
|
||||
function useStakingContract(stakingAddress?: string, withSignerIfPossible?: boolean) {
|
||||
return useContract(stakingAddress, STAKING_REWARDS_ABI, withSignerIfPossible)
|
||||
}
|
||||
|
||||
const ContentWrapper = styled(AutoColumn)`
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import StakingRewardsJson from '@uniswap/liquidity-staker/build/StakingRewards.json'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
@@ -8,7 +9,7 @@ import { useCallback, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
|
||||
import { usePairContract, useStakingContract, useV2RouterContract } from '../../hooks/useContract'
|
||||
import { useContract, usePairContract, useV2RouterContract } from '../../hooks/useContract'
|
||||
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
|
||||
import { StakingInfo, useDerivedStakeInfo } from '../../state/stake/hooks'
|
||||
import { TransactionType } from '../../state/transactions/actions'
|
||||
@@ -24,6 +25,12 @@ import { LoadingView, SubmittedView } from '../ModalViews'
|
||||
import ProgressCircles from '../ProgressSteps'
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
const { abi: STAKING_REWARDS_ABI } = StakingRewardsJson
|
||||
|
||||
function useStakingContract(stakingAddress?: string, withSignerIfPossible?: boolean) {
|
||||
return useContract(stakingAddress, STAKING_REWARDS_ABI, withSignerIfPossible)
|
||||
}
|
||||
|
||||
const HypotheticalRewardRate = styled.div<{ dim: boolean }>`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import StakingRewardsJson from '@uniswap/liquidity-staker/build/StakingRewards.json'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { useStakingContract } from '../../hooks/useContract'
|
||||
import { useContract } from '../../hooks/useContract'
|
||||
import { StakingInfo } from '../../state/stake/hooks'
|
||||
import { TransactionType } from '../../state/transactions/actions'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
@@ -16,6 +17,12 @@ import Modal from '../Modal'
|
||||
import { LoadingView, SubmittedView } from '../ModalViews'
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
const { abi: STAKING_REWARDS_ABI } = StakingRewardsJson
|
||||
|
||||
function useStakingContract(stakingAddress?: string, withSignerIfPossible?: boolean) {
|
||||
return useContract(stakingAddress, STAKING_REWARDS_ABI, withSignerIfPossible)
|
||||
}
|
||||
|
||||
const ContentWrapper = styled(AutoColumn)`
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { Currency, TradeType } from '@uniswap/sdk-core'
|
||||
import AnimatedDropdown from 'components/AnimatedDropdown'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingRows } from 'components/Loader/styled'
|
||||
import RoutingDiagram, { RoutingDiagramEntry } from 'components/RoutingDiagram/RoutingDiagram'
|
||||
import RoutingDiagram from 'components/RoutingDiagram/RoutingDiagram'
|
||||
import { AutoRow, RowBetween } from 'components/Row'
|
||||
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useAutoRouterSupported from 'hooks/useAutoRouterSupported'
|
||||
import { getTokenPath } from 'lib/components/Swap/RoutingDiagram/utils'
|
||||
import { memo, useState } from 'react'
|
||||
import { Plus } from 'react-feather'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
@@ -39,8 +38,6 @@ const OpenCloseIcon = styled(Plus)<{ open?: boolean }>`
|
||||
}
|
||||
`
|
||||
|
||||
const V2_DEFAULT_FEE_TIER = 3000
|
||||
|
||||
interface SwapRouteProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
trade: InterfaceTrade<Currency, Currency, TradeType>
|
||||
syncing: boolean
|
||||
@@ -109,35 +106,3 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r
|
||||
</Wrapper>
|
||||
)
|
||||
})
|
||||
|
||||
function getTokenPath(trade: Trade<Currency, Currency, TradeType>): RoutingDiagramEntry[] {
|
||||
return trade.swaps.map(({ route: { path: tokenPath, pools, protocol }, inputAmount, outputAmount }) => {
|
||||
const portion =
|
||||
trade.tradeType === TradeType.EXACT_INPUT
|
||||
? inputAmount.divide(trade.inputAmount)
|
||||
: outputAmount.divide(trade.outputAmount)
|
||||
|
||||
const percent = new Percent(portion.numerator, portion.denominator)
|
||||
|
||||
const path: RoutingDiagramEntry['path'] = []
|
||||
for (let i = 0; i < pools.length; i++) {
|
||||
const nextPool = pools[i]
|
||||
const tokenIn = tokenPath[i]
|
||||
const tokenOut = tokenPath[i + 1]
|
||||
|
||||
const entry: RoutingDiagramEntry['path'][0] = [
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
nextPool instanceof Pair ? V2_DEFAULT_FEE_TIER : nextPool.fee,
|
||||
]
|
||||
|
||||
path.push(entry)
|
||||
}
|
||||
|
||||
return {
|
||||
percent,
|
||||
path,
|
||||
protocol,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FortmaticConnector as FortmaticConnectorCore } from '@web3-react/fortmatic-connector'
|
||||
import { FortmaticConnector as FortmaticConnectorCore } from 'web3-react-fortmatic-connector'
|
||||
|
||||
export const OVERLAY_READY = 'OVERLAY_READY'
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AbstractConnector } from '@web3-react/abstract-connector'
|
||||
import { ConnectorUpdate } from '@web3-react/types'
|
||||
import invariant from 'tiny-invariant'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
import { ConnectorUpdate } from 'web3-react-types'
|
||||
|
||||
interface NetworkConnectorArguments {
|
||||
urls: { [chainId: number]: string }
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Web3Provider } from '@ethersproject/providers'
|
||||
import { SafeAppConnector } from '@gnosis.pm/safe-apps-web3-react'
|
||||
import { InjectedConnector } from '@web3-react/injected-connector'
|
||||
import { PortisConnector } from '@web3-react/portis-connector'
|
||||
import { WalletConnectConnector } from '@web3-react/walletconnect-connector'
|
||||
import { WalletLinkConnector } from '@web3-react/walletlink-connector'
|
||||
import { INFURA_NETWORK_URLS } from 'constants/chainInfo'
|
||||
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains'
|
||||
import { INFURA_NETWORK_URLS } from 'constants/infura'
|
||||
import { InjectedConnector } from 'web3-react-injected-connector'
|
||||
import { PortisConnector } from 'web3-react-portis-connector'
|
||||
import { WalletConnectConnector } from 'web3-react-walletconnect-connector'
|
||||
import { WalletLinkConnector } from 'web3-react-walletlink-connector'
|
||||
|
||||
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
|
||||
import getLibrary from '../utils/getLibrary'
|
||||
@@ -53,5 +53,5 @@ export const walletlink = new WalletLinkConnector({
|
||||
url: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
|
||||
appName: 'Uniswap',
|
||||
appLogoUrl: UNISWAP_LOGO_URL,
|
||||
supportedChainIds: [SupportedChainId.MAINNET, SupportedChainId.POLYGON],
|
||||
supportedChainIds: ALL_SUPPORTED_CHAIN_IDS,
|
||||
})
|
||||
|
||||
@@ -1,46 +1,12 @@
|
||||
import ethereumLogoUrl from 'assets/images/ethereum-logo.png'
|
||||
import arbitrumLogoUrl from 'assets/svg/arbitrum_logo.svg'
|
||||
import optimismLogoUrl from 'assets/svg/optimistic_ethereum.svg'
|
||||
import polygonMaticLogo from 'assets/svg/polygon-matic-logo.svg'
|
||||
import ms from 'ms.macro'
|
||||
|
||||
import ethereumLogoUrl from '../assets/images/ethereum-logo.png'
|
||||
import arbitrumLogoUrl from '../assets/svg/arbitrum_logo.svg'
|
||||
import optimismLogoUrl from '../assets/svg/optimistic_ethereum.svg'
|
||||
import polygonMaticLogo from '../assets/svg/polygon-matic-logo.svg'
|
||||
import { SupportedChainId, SupportedL1ChainId, SupportedL2ChainId } from './chains'
|
||||
import { ARBITRUM_LIST, OPTIMISM_LIST } from './lists'
|
||||
|
||||
const INFURA_KEY = process.env.REACT_APP_INFURA_KEY
|
||||
if (typeof INFURA_KEY === 'undefined') {
|
||||
throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`)
|
||||
}
|
||||
|
||||
/**
|
||||
* These are the network URLs used by the interface when there is not another available source of chain data
|
||||
*/
|
||||
export const INFURA_NETWORK_URLS: { [key in SupportedChainId]: string } = {
|
||||
[SupportedChainId.MAINNET]: `https://mainnet.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.RINKEBY]: `https://rinkeby.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.ROPSTEN]: `https://ropsten.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.GOERLI]: `https://goerli.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.KOVAN]: `https://kovan.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.OPTIMISM]: `https://optimism-mainnet.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: `https://optimism-kovan.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.ARBITRUM_ONE]: `https://arbitrum-mainnet.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: `https://arbitrum-rinkeby.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.POLYGON]: `https://polygon-mainnet.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.POLYGON_MUMBAI]: `https://polygon-mumbai.infura.io/v3/${INFURA_KEY}`,
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to call the add network RPC
|
||||
*/
|
||||
interface AddNetworkInfo {
|
||||
readonly rpcUrl: string
|
||||
readonly nativeCurrency: {
|
||||
name: string // e.g. 'Goerli ETH',
|
||||
symbol: string // e.g. 'gorETH',
|
||||
decimals: number // e.g. 18,
|
||||
}
|
||||
}
|
||||
|
||||
export enum NetworkType {
|
||||
L1,
|
||||
L2,
|
||||
@@ -56,7 +22,11 @@ interface BaseChainInfo {
|
||||
readonly logoUrl: string
|
||||
readonly label: string
|
||||
readonly helpCenterUrl?: string
|
||||
readonly addNetworkInfo: AddNetworkInfo
|
||||
readonly nativeCurrency: {
|
||||
name: string // e.g. 'Goerli ETH',
|
||||
symbol: string // e.g. 'gorETH',
|
||||
decimals: number // e.g. 18,
|
||||
}
|
||||
}
|
||||
|
||||
export interface L1ChainInfo extends BaseChainInfo {
|
||||
@@ -83,10 +53,7 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: 'Ethereum',
|
||||
logoUrl: ethereumLogoUrl,
|
||||
addNetworkInfo: {
|
||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
||||
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
|
||||
},
|
||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.RINKEBY]: {
|
||||
networkType: NetworkType.L1,
|
||||
@@ -95,10 +62,7 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: 'Rinkeby',
|
||||
logoUrl: ethereumLogoUrl,
|
||||
addNetworkInfo: {
|
||||
nativeCurrency: { name: 'Rinkeby Ether', symbol: 'rETH', decimals: 18 },
|
||||
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.RINKEBY],
|
||||
},
|
||||
nativeCurrency: { name: 'Rinkeby Ether', symbol: 'rETH', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.ROPSTEN]: {
|
||||
networkType: NetworkType.L1,
|
||||
@@ -107,10 +71,7 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: 'Ropsten',
|
||||
logoUrl: ethereumLogoUrl,
|
||||
addNetworkInfo: {
|
||||
nativeCurrency: { name: 'Ropsten Ether', symbol: 'ropETH', decimals: 18 },
|
||||
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.ROPSTEN],
|
||||
},
|
||||
nativeCurrency: { name: 'Ropsten Ether', symbol: 'ropETH', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.KOVAN]: {
|
||||
networkType: NetworkType.L1,
|
||||
@@ -119,10 +80,7 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: 'Kovan',
|
||||
logoUrl: ethereumLogoUrl,
|
||||
addNetworkInfo: {
|
||||
nativeCurrency: { name: 'Kovan Ether', symbol: 'kovETH', decimals: 18 },
|
||||
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.KOVAN],
|
||||
},
|
||||
nativeCurrency: { name: 'Kovan Ether', symbol: 'kovETH', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.GOERLI]: {
|
||||
networkType: NetworkType.L1,
|
||||
@@ -131,10 +89,7 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: 'Görli',
|
||||
logoUrl: ethereumLogoUrl,
|
||||
addNetworkInfo: {
|
||||
nativeCurrency: { name: 'Görli Ether', symbol: 'görETH', decimals: 18 },
|
||||
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.GOERLI],
|
||||
},
|
||||
nativeCurrency: { name: 'Görli Ether', symbol: 'görETH', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.OPTIMISM]: {
|
||||
networkType: NetworkType.L2,
|
||||
@@ -148,10 +103,7 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
logoUrl: optimismLogoUrl,
|
||||
statusPage: 'https://optimism.io/status',
|
||||
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ',
|
||||
addNetworkInfo: {
|
||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
||||
rpcUrl: 'https://mainnet.optimism.io',
|
||||
},
|
||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: {
|
||||
networkType: NetworkType.L2,
|
||||
@@ -165,10 +117,7 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
logoUrl: optimismLogoUrl,
|
||||
statusPage: 'https://optimism.io/status',
|
||||
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ',
|
||||
addNetworkInfo: {
|
||||
nativeCurrency: { name: 'Optimistic Kovan Ether', symbol: 'kovOpETH', decimals: 18 },
|
||||
rpcUrl: 'https://kovan.optimism.io',
|
||||
},
|
||||
nativeCurrency: { name: 'Optimistic Kovan Ether', symbol: 'kovOpETH', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.ARBITRUM_ONE]: {
|
||||
networkType: NetworkType.L2,
|
||||
@@ -181,10 +130,7 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
logoUrl: arbitrumLogoUrl,
|
||||
defaultListUrl: ARBITRUM_LIST,
|
||||
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137787-uniswap-on-arbitrum',
|
||||
addNetworkInfo: {
|
||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
||||
rpcUrl: 'https://arb1.arbitrum.io/rpc',
|
||||
},
|
||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: {
|
||||
networkType: NetworkType.L2,
|
||||
@@ -197,10 +143,7 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
logoUrl: arbitrumLogoUrl,
|
||||
defaultListUrl: ARBITRUM_LIST,
|
||||
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137787-uniswap-on-arbitrum',
|
||||
addNetworkInfo: {
|
||||
nativeCurrency: { name: 'Rinkeby Arbitrum Ether', symbol: 'rinkArbETH', decimals: 18 },
|
||||
rpcUrl: 'https://rinkeby.arbitrum.io/rpc',
|
||||
},
|
||||
nativeCurrency: { name: 'Rinkeby Arbitrum Ether', symbol: 'rinkArbETH', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.POLYGON]: {
|
||||
networkType: NetworkType.L1,
|
||||
@@ -211,10 +154,7 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
infoLink: 'https://info.uniswap.org/#/polygon/',
|
||||
label: 'Polygon',
|
||||
logoUrl: polygonMaticLogo,
|
||||
addNetworkInfo: {
|
||||
rpcUrl: 'https://polygon-rpc.com/',
|
||||
nativeCurrency: { name: 'Polygon Matic', symbol: 'MATIC', decimals: 18 },
|
||||
},
|
||||
nativeCurrency: { name: 'Polygon Matic', symbol: 'MATIC', decimals: 18 },
|
||||
},
|
||||
[SupportedChainId.POLYGON_MUMBAI]: {
|
||||
networkType: NetworkType.L1,
|
||||
@@ -225,9 +165,6 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
infoLink: 'https://info.uniswap.org/#/polygon/',
|
||||
label: 'Polygon Mumbai',
|
||||
logoUrl: polygonMaticLogo,
|
||||
addNetworkInfo: {
|
||||
nativeCurrency: { name: 'Polygon Mumbai Matic', symbol: 'mMATIC', decimals: 18 },
|
||||
rpcUrl: 'https://rpc-endpoints.superfluid.dev/mumbai',
|
||||
},
|
||||
nativeCurrency: { name: 'Polygon Mumbai Matic', symbol: 'mMATIC', decimals: 18 },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ export const COMMON_CONTRACT_NAMES: Record<number, { [address: string]: string }
|
||||
[TIMELOCK_ADDRESS[SupportedChainId.MAINNET]]: 'Timelock',
|
||||
[GOVERNANCE_ALPHA_V0_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance (V0)',
|
||||
[GOVERNANCE_ALPHA_V1_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance',
|
||||
'0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e': 'ENS Registry',
|
||||
'0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41': 'ENS Public Resolver',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -21,3 +23,5 @@ export const DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS = 13
|
||||
export const AVERAGE_BLOCK_TIME_IN_SECS: { [chainId: number]: number } = {
|
||||
1: DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS,
|
||||
}
|
||||
|
||||
export const LATEST_GOVERNOR_INDEX = 2
|
||||
|
||||
23
src/constants/infura.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { SupportedChainId } from './chains'
|
||||
|
||||
const INFURA_KEY = process.env.REACT_APP_INFURA_KEY
|
||||
if (typeof INFURA_KEY === 'undefined') {
|
||||
throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`)
|
||||
}
|
||||
|
||||
/**
|
||||
* These are the network URLs used by the interface when there is not another available source of chain data
|
||||
*/
|
||||
export const INFURA_NETWORK_URLS: { [key in SupportedChainId]: string } = {
|
||||
[SupportedChainId.MAINNET]: `https://mainnet.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.RINKEBY]: `https://rinkeby.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.ROPSTEN]: `https://ropsten.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.GOERLI]: `https://goerli.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.KOVAN]: `https://kovan.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.OPTIMISM]: `https://optimism-mainnet.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: `https://optimism-kovan.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.ARBITRUM_ONE]: `https://arbitrum-mainnet.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: `https://arbitrum-rinkeby.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.POLYGON]: `https://polygon-mainnet.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.POLYGON_MUMBAI]: `https://polygon-mumbai.infura.io/v3/${INFURA_KEY}`,
|
||||
}
|
||||
@@ -32,11 +32,11 @@ export const SUPPORTED_LOCALES = [
|
||||
'vi-VN',
|
||||
'zh-CN',
|
||||
'zh-TW',
|
||||
] as const
|
||||
]
|
||||
export type SupportedLocale = typeof SUPPORTED_LOCALES[number] | 'pseudo'
|
||||
|
||||
// eslint-disable-next-line import/first
|
||||
import * as enUS from '../locales/en-US'
|
||||
import * as enUS from 'locales/en-US'
|
||||
export const DEFAULT_LOCALE: SupportedLocale = 'en-US'
|
||||
export const DEFAULT_CATALOG = enUS
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
sETH2,
|
||||
SWISE,
|
||||
TRIBE,
|
||||
USDC,
|
||||
USDC_ARBITRUM,
|
||||
USDC_MAINNET,
|
||||
USDC_OPTIMISM,
|
||||
USDC_POLYGON,
|
||||
USDT,
|
||||
@@ -44,13 +44,21 @@ type ChainCurrencyList = {
|
||||
}
|
||||
|
||||
const WRAPPED_NATIVE_CURRENCIES_ONLY: ChainTokenList = Object.fromEntries(
|
||||
Object.entries(WRAPPED_NATIVE_CURRENCY).map(([key, value]) => [key, [value]])
|
||||
Object.entries(WRAPPED_NATIVE_CURRENCY)
|
||||
.map(([key, value]) => [key, [value]])
|
||||
.filter(Boolean)
|
||||
)
|
||||
|
||||
// used to construct intermediary pairs for trading
|
||||
export const BASES_TO_CHECK_TRADES_AGAINST: ChainTokenList = {
|
||||
...WRAPPED_NATIVE_CURRENCIES_ONLY,
|
||||
[SupportedChainId.MAINNET]: [...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC],
|
||||
[SupportedChainId.MAINNET]: [
|
||||
...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET],
|
||||
DAI,
|
||||
USDC_MAINNET,
|
||||
USDT,
|
||||
WBTC,
|
||||
],
|
||||
[SupportedChainId.OPTIMISM]: [
|
||||
...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.OPTIMISM],
|
||||
DAI_OPTIMISM,
|
||||
@@ -90,7 +98,7 @@ export const ADDITIONAL_BASES: { [chainId: number]: { [tokenAddress: string]: To
|
||||
*/
|
||||
export const CUSTOM_BASES: { [chainId: number]: { [tokenAddress: string]: Token[] } } = {
|
||||
[SupportedChainId.MAINNET]: {
|
||||
[AMPL.address]: [DAI, WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET]],
|
||||
[AMPL.address]: [DAI, WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET] as Token],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -101,32 +109,38 @@ export const COMMON_BASES: ChainCurrencyList = {
|
||||
[SupportedChainId.MAINNET]: [
|
||||
nativeOnChain(SupportedChainId.MAINNET),
|
||||
DAI,
|
||||
USDC,
|
||||
USDC_MAINNET,
|
||||
USDT,
|
||||
WBTC,
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET],
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET] as Token,
|
||||
],
|
||||
[SupportedChainId.ROPSTEN]: [
|
||||
nativeOnChain(SupportedChainId.ROPSTEN),
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ROPSTEN],
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ROPSTEN] as Token,
|
||||
],
|
||||
[SupportedChainId.RINKEBY]: [
|
||||
nativeOnChain(SupportedChainId.RINKEBY),
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.RINKEBY],
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.RINKEBY] as Token,
|
||||
],
|
||||
[SupportedChainId.GOERLI]: [
|
||||
nativeOnChain(SupportedChainId.GOERLI),
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.GOERLI] as Token,
|
||||
],
|
||||
[SupportedChainId.KOVAN]: [
|
||||
nativeOnChain(SupportedChainId.KOVAN),
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.KOVAN] as Token,
|
||||
],
|
||||
[SupportedChainId.GOERLI]: [nativeOnChain(SupportedChainId.GOERLI), WRAPPED_NATIVE_CURRENCY[SupportedChainId.GOERLI]],
|
||||
[SupportedChainId.KOVAN]: [nativeOnChain(SupportedChainId.KOVAN), WRAPPED_NATIVE_CURRENCY[SupportedChainId.KOVAN]],
|
||||
[SupportedChainId.ARBITRUM_ONE]: [
|
||||
nativeOnChain(SupportedChainId.ARBITRUM_ONE),
|
||||
DAI_ARBITRUM_ONE,
|
||||
USDC_ARBITRUM,
|
||||
USDT_ARBITRUM_ONE,
|
||||
WBTC_ARBITRUM_ONE,
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_ONE],
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_ONE] as Token,
|
||||
],
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: [
|
||||
nativeOnChain(SupportedChainId.ARBITRUM_RINKEBY),
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_RINKEBY],
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_RINKEBY] as Token,
|
||||
],
|
||||
[SupportedChainId.OPTIMISM]: [
|
||||
nativeOnChain(SupportedChainId.OPTIMISM),
|
||||
@@ -146,7 +160,7 @@ export const COMMON_BASES: ChainCurrencyList = {
|
||||
],
|
||||
[SupportedChainId.POLYGON_MUMBAI]: [
|
||||
nativeOnChain(SupportedChainId.POLYGON_MUMBAI),
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.POLYGON_MUMBAI],
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.POLYGON_MUMBAI] as Token,
|
||||
WETH_POLYGON_MUMBAI,
|
||||
],
|
||||
}
|
||||
@@ -154,7 +168,13 @@ export const COMMON_BASES: ChainCurrencyList = {
|
||||
// used to construct the list of all pairs we consider by default in the frontend
|
||||
export const BASES_TO_TRACK_LIQUIDITY_FOR: ChainTokenList = {
|
||||
...WRAPPED_NATIVE_CURRENCIES_ONLY,
|
||||
[SupportedChainId.MAINNET]: [...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC],
|
||||
[SupportedChainId.MAINNET]: [
|
||||
...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET],
|
||||
DAI,
|
||||
USDC_MAINNET,
|
||||
USDT,
|
||||
WBTC,
|
||||
],
|
||||
}
|
||||
export const PINNED_PAIRS: { readonly [chainId: number]: [Token, Token][] } = {
|
||||
[SupportedChainId.MAINNET]: [
|
||||
@@ -168,7 +188,7 @@ export const PINNED_PAIRS: { readonly [chainId: number]: [Token, Token][] } = {
|
||||
'Compound USD Coin'
|
||||
),
|
||||
],
|
||||
[USDC, USDT],
|
||||
[USDC_MAINNET, USDT],
|
||||
[DAI, USDT],
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1101,6 +1101,13 @@
|
||||
"name": "BMEX",
|
||||
"symbol": "BMEX",
|
||||
"decimals": 18
|
||||
},
|
||||
{
|
||||
"chainId": 1,
|
||||
"address": "0x322A46E88fa3C78F9c9E3DBb0254b61664a06109",
|
||||
"name": "Ukraine DAO",
|
||||
"symbol": "Ukraine",
|
||||
"decimals": 18
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
import { Currency, Ether, NativeCurrency, Token, WETH9 } from '@uniswap/sdk-core'
|
||||
import {
|
||||
USDC_ARBITRUM,
|
||||
USDC_ARBITRUM_RINKEBY,
|
||||
USDC_GÖRLI,
|
||||
USDC_KOVAN,
|
||||
USDC_MAINNET,
|
||||
USDC_OPTIMISM,
|
||||
USDC_OPTIMISTIC_KOVAN,
|
||||
USDC_POLYGON,
|
||||
USDC_POLYGON_MUMBAI,
|
||||
USDC_RINKEBY,
|
||||
USDC_ROPSTEN,
|
||||
} from '@uniswap/smart-order-router'
|
||||
import invariant from 'tiny-invariant'
|
||||
|
||||
import { UNI_ADDRESS } from './addresses'
|
||||
import { SupportedChainId } from './chains'
|
||||
|
||||
export { USDC_ARBITRUM, USDC_MAINNET, USDC_OPTIMISM, USDC_POLYGON }
|
||||
|
||||
export const AMPL = new Token(
|
||||
SupportedChainId.MAINNET,
|
||||
'0xD46bA6D942050d489DBd938a2C909A5d5039A161',
|
||||
@@ -31,27 +47,19 @@ export const DAI_OPTIMISM = new Token(
|
||||
'DAI',
|
||||
'Dai stable coin'
|
||||
)
|
||||
export const USDC = new Token(
|
||||
SupportedChainId.MAINNET,
|
||||
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
||||
6,
|
||||
'USDC',
|
||||
'USD//C'
|
||||
)
|
||||
export const USDC_ARBITRUM = new Token(
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
'0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8',
|
||||
6,
|
||||
'USDC',
|
||||
'USD//C'
|
||||
)
|
||||
export const USDC_POLYGON = new Token(
|
||||
SupportedChainId.POLYGON,
|
||||
'0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
|
||||
6,
|
||||
'USDC',
|
||||
'USD//C'
|
||||
)
|
||||
export const USDC: { [chainId in SupportedChainId]: Token } = {
|
||||
[SupportedChainId.MAINNET]: USDC_MAINNET,
|
||||
[SupportedChainId.ARBITRUM_ONE]: USDC_ARBITRUM,
|
||||
[SupportedChainId.OPTIMISM]: USDC_OPTIMISM,
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: USDC_ARBITRUM_RINKEBY,
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: USDC_OPTIMISTIC_KOVAN,
|
||||
[SupportedChainId.POLYGON]: USDC_POLYGON,
|
||||
[SupportedChainId.POLYGON_MUMBAI]: USDC_POLYGON_MUMBAI,
|
||||
[SupportedChainId.GOERLI]: USDC_GÖRLI,
|
||||
[SupportedChainId.RINKEBY]: USDC_RINKEBY,
|
||||
[SupportedChainId.KOVAN]: USDC_KOVAN,
|
||||
[SupportedChainId.ROPSTEN]: USDC_ROPSTEN,
|
||||
}
|
||||
export const DAI_POLYGON = new Token(
|
||||
SupportedChainId.POLYGON,
|
||||
'0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063',
|
||||
@@ -73,13 +81,6 @@ export const WBTC_POLYGON = new Token(
|
||||
'WBTC',
|
||||
'Wrapped BTC'
|
||||
)
|
||||
export const USDC_OPTIMISM = new Token(
|
||||
SupportedChainId.OPTIMISM,
|
||||
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
|
||||
6,
|
||||
'USDC',
|
||||
'USD//C'
|
||||
)
|
||||
export const USDT = new Token(
|
||||
SupportedChainId.MAINNET,
|
||||
'0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
||||
@@ -208,8 +209,8 @@ export const UNI: { [chainId: number]: Token } = {
|
||||
[SupportedChainId.KOVAN]: new Token(SupportedChainId.KOVAN, UNI_ADDRESS[42], 18, 'UNI', 'Uniswap'),
|
||||
}
|
||||
|
||||
export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token } = {
|
||||
...WETH9,
|
||||
export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token | undefined } = {
|
||||
...(WETH9 as Record<SupportedChainId, Token>),
|
||||
[SupportedChainId.OPTIMISM]: new Token(
|
||||
SupportedChainId.OPTIMISM,
|
||||
'0x4200000000000000000000000000000000000006',
|
||||
@@ -265,7 +266,9 @@ class MaticNativeCurrency extends NativeCurrency {
|
||||
|
||||
get wrapped(): Token {
|
||||
if (!isMatic(this.chainId)) throw new Error('Not matic')
|
||||
return WRAPPED_NATIVE_CURRENCY[this.chainId]
|
||||
const wrapped = WRAPPED_NATIVE_CURRENCY[this.chainId]
|
||||
invariant(wrapped instanceof Token)
|
||||
return wrapped
|
||||
}
|
||||
|
||||
public constructor(chainId: number) {
|
||||
@@ -276,7 +279,8 @@ class MaticNativeCurrency extends NativeCurrency {
|
||||
|
||||
export class ExtendedEther extends Ether {
|
||||
public get wrapped(): Token {
|
||||
if (this.chainId in WRAPPED_NATIVE_CURRENCY) return WRAPPED_NATIVE_CURRENCY[this.chainId]
|
||||
const wrapped = WRAPPED_NATIVE_CURRENCY[this.chainId]
|
||||
if (wrapped) return wrapped
|
||||
throw new Error('Unsupported chain ID')
|
||||
}
|
||||
|
||||
@@ -296,3 +300,19 @@ export function nativeOnChain(chainId: number): NativeCurrency {
|
||||
: ExtendedEther.onChain(chainId))
|
||||
)
|
||||
}
|
||||
|
||||
export const TOKEN_SHORTHANDS: { [shorthand: string]: { [chainId in SupportedChainId]?: string } } = {
|
||||
USDC: {
|
||||
[SupportedChainId.MAINNET]: USDC_MAINNET.address,
|
||||
[SupportedChainId.ARBITRUM_ONE]: USDC_ARBITRUM.address,
|
||||
[SupportedChainId.OPTIMISM]: USDC_OPTIMISM.address,
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: USDC_ARBITRUM_RINKEBY.address,
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: USDC_OPTIMISTIC_KOVAN.address,
|
||||
[SupportedChainId.POLYGON]: USDC_POLYGON.address,
|
||||
[SupportedChainId.POLYGON_MUMBAI]: USDC_POLYGON_MUMBAI.address,
|
||||
[SupportedChainId.GOERLI]: USDC_GÖRLI.address,
|
||||
[SupportedChainId.RINKEBY]: USDC_RINKEBY.address,
|
||||
[SupportedChainId.KOVAN]: USDC_KOVAN.address,
|
||||
[SupportedChainId.ROPSTEN]: USDC_ROPSTEN.address,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AbstractConnector } from '@web3-react/abstract-connector'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
|
||||
import INJECTED_ICON_URL from '../assets/images/arrow-right.svg'
|
||||
import COINBASE_ICON_URL from '../assets/images/coinbaseWalletIcon.svg'
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import { CHAIN_INFO } from 'constants/chainInfo'
|
||||
import { L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useCurrencyFromMap, useTokenFromMap } from 'lib/hooks/useCurrency'
|
||||
import { useCurrencyFromMap, useTokenFromMapOrNetwork } from 'lib/hooks/useCurrency'
|
||||
import { getTokenFilter } from 'lib/hooks/useTokenList/filtering'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
@@ -159,7 +159,7 @@ export function useIsUserAddedToken(currency: Currency | undefined | null): bool
|
||||
// otherwise returns the token
|
||||
export function useToken(tokenAddress?: string | null): Token | null | undefined {
|
||||
const tokens = useAllTokens()
|
||||
return useTokenFromMap(tokens, tokenAddress)
|
||||
return useTokenFromMapOrNetwork(tokens, tokenAddress)
|
||||
}
|
||||
|
||||
export function useCurrency(currencyId?: string | null): Currency | null | undefined {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { Web3Provider } from '@ethersproject/providers'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { default as useWidgetsWeb3React } from 'lib/hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from 'web3-react-core'
|
||||
|
||||
import { NetworkContextName } from '../constants/misc'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'state/routing/clientSideSmartOrderRouter/constants'
|
||||
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'lib/hooks/routing/clientSideSmartOrderRouter'
|
||||
|
||||
export default function useAutoRouterSupported(): boolean {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { renderHook } from '@testing-library/react-hooks'
|
||||
import { CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { DAI, USDC } from 'constants/tokens'
|
||||
import { DAI, USDC_MAINNET } from 'constants/tokens'
|
||||
import { TradeState } from 'state/routing/types'
|
||||
|
||||
import { useRoutingAPITrade } from '../state/routing/useRoutingAPITrade'
|
||||
@@ -10,7 +10,7 @@ import { useClientSideV3Trade } from './useClientSideV3Trade'
|
||||
import useDebounce from './useDebounce'
|
||||
import useIsWindowVisible from './useIsWindowVisible'
|
||||
|
||||
const USDCAmount = CurrencyAmount.fromRawAmount(USDC, '10000')
|
||||
const USDCAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '10000')
|
||||
const DAIAmount = CurrencyAmount.fromRawAmount(DAI, '10000')
|
||||
|
||||
jest.mock('./useAutoRouterSupported')
|
||||
@@ -126,10 +126,10 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
expectRouterMock(TradeState.INVALID)
|
||||
expectClientSideMock(TradeState.VALID)
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC)
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC)
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC_MAINNET)
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
})
|
||||
|
||||
@@ -138,17 +138,17 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
expectRouterMock(TradeState.NO_ROUTE_FOUND)
|
||||
expectClientSideMock(TradeState.VALID)
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC)
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC)
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC_MAINNET)
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
})
|
||||
describe('when routing api is in non-error state', () => {
|
||||
it('does not compute client side v3 trade if routing api is LOADING', () => {
|
||||
expectRouterMock(TradeState.LOADING)
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
||||
expect(result.current).toEqual({ state: TradeState.LOADING, trade: undefined })
|
||||
@@ -157,7 +157,7 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
it('does not compute client side v3 trade if routing api is VALID', () => {
|
||||
expectRouterMock(TradeState.VALID)
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
@@ -166,7 +166,7 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
it('does not compute client side v3 trade if routing api is SYNCING', () => {
|
||||
expectRouterMock(TradeState.SYNCING)
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
||||
expect(result.current).toEqual({ state: TradeState.SYNCING, trade: undefined })
|
||||
@@ -178,7 +178,7 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
expectRouterMock(TradeState.INVALID)
|
||||
expectClientSideMock(TradeState.VALID)
|
||||
|
||||
renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
|
||||
renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
||||
})
|
||||
@@ -187,9 +187,9 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
expectRouterMock(TradeState.NO_ROUTE_FOUND)
|
||||
expectClientSideMock(TradeState.VALID)
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC)
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import uriToHttp from 'lib/utils/uriToHttp'
|
||||
import Vibrant from 'node-vibrant/lib/bundle'
|
||||
import Vibrant from 'node-vibrant/lib/bundle.js'
|
||||
import { shade } from 'polished'
|
||||
import { useLayoutEffect, useState } from 'react'
|
||||
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { Contract } from '@ethersproject/contracts'
|
||||
import { abi as GOVERNANCE_ABI } from '@uniswap/governance/build/GovernorAlpha.json'
|
||||
import { abi as UNI_ABI } from '@uniswap/governance/build/Uni.json'
|
||||
import { abi as STAKING_REWARDS_ABI } from '@uniswap/liquidity-staker/build/StakingRewards.json'
|
||||
import { abi as MERKLE_DISTRIBUTOR_ABI } from '@uniswap/merkle-distributor/build/MerkleDistributor.json'
|
||||
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
|
||||
import { abi as IUniswapV2Router02ABI } from '@uniswap/v2-periphery/build/IUniswapV2Router02.json'
|
||||
import { abi as QuoterABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/Quoter.sol/Quoter.json'
|
||||
import { abi as TickLensABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/TickLens.sol/TickLens.json'
|
||||
import { abi as MulticallABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/UniswapInterfaceMulticall.sol/UniswapInterfaceMulticall.json'
|
||||
import { abi as NFTPositionManagerABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
|
||||
import { abi as V2MigratorABI } from '@uniswap/v3-periphery/artifacts/contracts/V3Migrator.sol/V3Migrator.json'
|
||||
import IUniswapV2PairJson from '@uniswap/v2-core/build/IUniswapV2Pair.json'
|
||||
import IUniswapV2Router02Json from '@uniswap/v2-periphery/build/IUniswapV2Router02.json'
|
||||
import QuoterJson from '@uniswap/v3-periphery/artifacts/contracts/lens/Quoter.sol/Quoter.json'
|
||||
import TickLensJson from '@uniswap/v3-periphery/artifacts/contracts/lens/TickLens.sol/TickLens.json'
|
||||
import UniswapInterfaceMulticallJson from '@uniswap/v3-periphery/artifacts/contracts/lens/UniswapInterfaceMulticall.sol/UniswapInterfaceMulticall.json'
|
||||
import NonfungiblePositionManagerJson from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
|
||||
import V3MigratorJson from '@uniswap/v3-periphery/artifacts/contracts/V3Migrator.sol/V3Migrator.json'
|
||||
import ARGENT_WALLET_DETECTOR_ABI from 'abis/argent-wallet-detector.json'
|
||||
import EIP_2612 from 'abis/eip_2612.json'
|
||||
import ENS_PUBLIC_RESOLVER_ABI from 'abis/ens-public-resolver.json'
|
||||
@@ -18,16 +14,11 @@ import ERC20_ABI from 'abis/erc20.json'
|
||||
import ERC20_BYTES32_ABI from 'abis/erc20_bytes32.json'
|
||||
import ERC721_ABI from 'abis/erc721.json'
|
||||
import ERC1155_ABI from 'abis/erc1155.json'
|
||||
import GOVERNOR_BRAVO_ABI from 'abis/governor-bravo.json'
|
||||
import { ArgentWalletDetector, EnsPublicResolver, EnsRegistrar, Erc20, Erc721, Erc1155, Weth } from 'abis/types'
|
||||
import WETH_ABI from 'abis/weth.json'
|
||||
import {
|
||||
ARGENT_WALLET_DETECTOR_ADDRESS,
|
||||
ENS_REGISTRAR_ADDRESSES,
|
||||
GOVERNANCE_ALPHA_V0_ADDRESSES,
|
||||
GOVERNANCE_ALPHA_V1_ADDRESSES,
|
||||
GOVERNANCE_BRAVO_ADDRESSES,
|
||||
MERKLE_DISTRIBUTOR_ADDRESS,
|
||||
MULTICALL_ADDRESS,
|
||||
NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,
|
||||
QUOTER_ADDRESSES,
|
||||
@@ -35,7 +26,7 @@ import {
|
||||
V2_ROUTER_ADDRESS,
|
||||
V3_MIGRATOR_ADDRESSES,
|
||||
} from 'constants/addresses'
|
||||
import { UNI, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
|
||||
import { WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useMemo } from 'react'
|
||||
import { NonfungiblePositionManager, Quoter, TickLens, UniswapInterfaceMulticall } from 'types/v3'
|
||||
@@ -43,6 +34,14 @@ import { V3Migrator } from 'types/v3/V3Migrator'
|
||||
|
||||
import { getContract } from '../utils'
|
||||
|
||||
const { abi: IUniswapV2PairABI } = IUniswapV2PairJson
|
||||
const { abi: IUniswapV2Router02ABI } = IUniswapV2Router02Json
|
||||
const { abi: QuoterABI } = QuoterJson
|
||||
const { abi: TickLensABI } = TickLensJson
|
||||
const { abi: MulticallABI } = UniswapInterfaceMulticallJson
|
||||
const { abi: NFTPositionManagerABI } = NonfungiblePositionManagerJson
|
||||
const { abi: V2MigratorABI } = V3MigratorJson
|
||||
|
||||
// returns null on errors
|
||||
export function useContract<T extends Contract = Contract>(
|
||||
addressOrAddressMap: string | { [chainId: number]: string } | undefined,
|
||||
@@ -123,33 +122,6 @@ export function useInterfaceMulticall() {
|
||||
return useContract<UniswapInterfaceMulticall>(MULTICALL_ADDRESS, MulticallABI, false) as UniswapInterfaceMulticall
|
||||
}
|
||||
|
||||
export function useMerkleDistributorContract() {
|
||||
return useContract(MERKLE_DISTRIBUTOR_ADDRESS, MERKLE_DISTRIBUTOR_ABI, true)
|
||||
}
|
||||
|
||||
export function useGovernanceV0Contract(): Contract | null {
|
||||
return useContract(GOVERNANCE_ALPHA_V0_ADDRESSES, GOVERNANCE_ABI, false)
|
||||
}
|
||||
|
||||
export function useGovernanceV1Contract(): Contract | null {
|
||||
return useContract(GOVERNANCE_ALPHA_V1_ADDRESSES, GOVERNANCE_ABI, false)
|
||||
}
|
||||
|
||||
export function useGovernanceBravoContract(): Contract | null {
|
||||
return useContract(GOVERNANCE_BRAVO_ADDRESSES, GOVERNOR_BRAVO_ABI, true)
|
||||
}
|
||||
|
||||
export const useLatestGovernanceContract = useGovernanceBravoContract
|
||||
|
||||
export function useUniContract() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
return useContract(chainId ? UNI[chainId]?.address : undefined, UNI_ABI, true)
|
||||
}
|
||||
|
||||
export function useStakingContract(stakingAddress?: string, withSignerIfPossible?: boolean) {
|
||||
return useContract(stakingAddress, STAKING_REWARDS_ABI, withSignerIfPossible)
|
||||
}
|
||||
|
||||
export function useV3NFTPositionManagerContract(withSignerIfPossible?: boolean): NonfungiblePositionManager | null {
|
||||
return useContract<NonfungiblePositionManager>(
|
||||
NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useSingleCallResult } from 'lib/hooks/multicall'
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
import { SWAP_ROUTER_ADDRESSES, V3_ROUTER_ADDRESS } from '../constants/addresses'
|
||||
import { DAI, UNI, USDC } from '../constants/tokens'
|
||||
import { DAI, UNI, USDC_MAINNET } from '../constants/tokens'
|
||||
import { useEIP2612Contract } from './useContract'
|
||||
import useIsArgentWallet from './useIsArgentWallet'
|
||||
|
||||
@@ -36,7 +36,7 @@ const PERMITTABLE_TOKENS: {
|
||||
}
|
||||
} = {
|
||||
1: {
|
||||
[USDC.address]: { type: PermitType.AMOUNT, name: 'USD Coin', version: '2' },
|
||||
[USDC_MAINNET.address]: { type: PermitType.AMOUNT, name: 'USD Coin', version: '2' },
|
||||
[DAI.address]: { type: PermitType.ALLOWED, name: 'Dai Stablecoin', version: '1' },
|
||||
[UNI[1].address]: { type: PermitType.AMOUNT, name: 'Uniswap' },
|
||||
},
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
const VISIBILITY_STATE_SUPPORTED = 'visibilityState' in document
|
||||
function isVisibilityStateSupported() {
|
||||
return 'visibilityState' in document
|
||||
}
|
||||
|
||||
function isWindowVisible() {
|
||||
return !VISIBILITY_STATE_SUPPORTED || document.visibilityState !== 'hidden'
|
||||
return !isVisibilityStateSupported() || document.visibilityState !== 'hidden'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -16,7 +18,7 @@ export default function useIsWindowVisible(): boolean {
|
||||
}, [setFocused])
|
||||
|
||||
useEffect(() => {
|
||||
if (!VISIBILITY_STATE_SUPPORTED) return undefined
|
||||
if (!isVisibilityStateSupported()) return undefined
|
||||
|
||||
document.addEventListener('visibilitychange', listener)
|
||||
return () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Interface } from '@ethersproject/abi'
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import { abi as IUniswapV3PoolStateABI } from '@uniswap/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState.sol/IUniswapV3PoolState.json'
|
||||
import IUniswapV3PoolStateJson from '@uniswap/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState.sol/IUniswapV3PoolState.json'
|
||||
import { computePoolAddress } from '@uniswap/v3-sdk'
|
||||
import { FeeAmount, Pool } from '@uniswap/v3-sdk'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
@@ -10,6 +10,8 @@ import { useMemo } from 'react'
|
||||
import { V3_CORE_FACTORY_ADDRESSES } from '../constants/addresses'
|
||||
import { IUniswapV3PoolStateInterface } from '../types/v3/IUniswapV3PoolState'
|
||||
|
||||
const { abi: IUniswapV3PoolStateABI } = IUniswapV3PoolStateJson
|
||||
|
||||
const POOL_STATE_INTERFACE = new Interface(IUniswapV3PoolStateABI) as IUniswapV3PoolStateInterface
|
||||
|
||||
export enum PoolState {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { SwapRouter, Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { Router as V2SwapRouter, Trade as V2Trade } from '@uniswap/v2-sdk'
|
||||
import { SwapRouter as V3SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
|
||||
import { FeeOptions, SwapRouter as V3SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
|
||||
import { SWAP_ROUTER_ADDRESSES, V3_ROUTER_ADDRESS } from 'constants/addresses'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useMemo } from 'react'
|
||||
@@ -36,7 +36,8 @@ export function useSwapCallArguments(
|
||||
allowedSlippage: Percent,
|
||||
recipientAddressOrName: string | null | undefined,
|
||||
signatureData: SignatureData | null | undefined,
|
||||
deadline: BigNumber | undefined
|
||||
deadline: BigNumber | undefined,
|
||||
feeOptions: FeeOptions | undefined
|
||||
): SwapCall[] {
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
|
||||
@@ -98,6 +99,7 @@ export function useSwapCallArguments(
|
||||
} else {
|
||||
// swap options shared by v3 and v2+v3 swap routers
|
||||
const sharedSwapOptions = {
|
||||
fee: feeOptions,
|
||||
recipient,
|
||||
slippageTolerance: allowedSlippage,
|
||||
...(signatureData
|
||||
@@ -167,15 +169,16 @@ export function useSwapCallArguments(
|
||||
]
|
||||
}
|
||||
}, [
|
||||
trade,
|
||||
recipient,
|
||||
library,
|
||||
account,
|
||||
chainId,
|
||||
deadline,
|
||||
routerContract,
|
||||
allowedSlippage,
|
||||
argentWalletContract,
|
||||
chainId,
|
||||
deadline,
|
||||
feeOptions,
|
||||
library,
|
||||
recipient,
|
||||
routerContract,
|
||||
signatureData,
|
||||
trade,
|
||||
])
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export function useSwapCallback(
|
||||
state,
|
||||
callback: libCallback,
|
||||
error,
|
||||
} = useLibSwapCallBack(trade, allowedSlippage, recipient, signatureData, deadline)
|
||||
} = useLibSwapCallBack({ trade, allowedSlippage, recipientAddressOrName: recipient, signatureData, deadline })
|
||||
|
||||
const callback = useMemo(() => {
|
||||
if (!libCallback || !trade) {
|
||||
|
||||
@@ -4,14 +4,14 @@ import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { SupportedChainId } from '../constants/chains'
|
||||
import { DAI_OPTIMISM, USDC, USDC_ARBITRUM, USDC_POLYGON } from '../constants/tokens'
|
||||
import { DAI_OPTIMISM, USDC_ARBITRUM, USDC_MAINNET, USDC_POLYGON } from '../constants/tokens'
|
||||
import { useBestV2Trade } from './useBestV2Trade'
|
||||
import { useClientSideV3Trade } from './useClientSideV3Trade'
|
||||
|
||||
// Stablecoin amounts used when calculating spot price for a given currency.
|
||||
// The amount is large enough to filter low liquidity pairs.
|
||||
export const STABLECOIN_AMOUNT_OUT: { [chainId: number]: CurrencyAmount<Token> } = {
|
||||
[SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(USDC, 100_000e6),
|
||||
[SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(USDC_MAINNET, 100_000e6),
|
||||
[SupportedChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(USDC_ARBITRUM, 10_000e6),
|
||||
[SupportedChainId.OPTIMISM]: CurrencyAmount.fromRawAmount(DAI_OPTIMISM, 10_000e18),
|
||||
[SupportedChainId.POLYGON]: CurrencyAmount.fromRawAmount(USDC_POLYGON, 10_000e6),
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Interface } from '@ethersproject/abi'
|
||||
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
|
||||
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
|
||||
import IUniswapV2PairJson from '@uniswap/v2-core/build/IUniswapV2Pair.json'
|
||||
import { computePairAddress, Pair } from '@uniswap/v2-sdk'
|
||||
import { useMultipleContractSingleData } from 'lib/hooks/multicall'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { V2_FACTORY_ADDRESSES } from '../constants/addresses'
|
||||
|
||||
const { abi: IUniswapV2PairABI } = IUniswapV2PairJson
|
||||
|
||||
const PAIR_INTERFACE = new Interface(IUniswapV2PairABI)
|
||||
|
||||
export enum PairState {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import type { EthereumProvider } from 'lib/ethereum'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useWeb3React } from 'web3-react-core'
|
||||
|
||||
import { gnosisSafe, injected } from '../connectors'
|
||||
import { IS_IN_IFRAME } from '../constants/misc'
|
||||
|
||||
@@ -3,13 +3,13 @@ import 'inter-ui'
|
||||
import 'polyfills'
|
||||
import 'components/analytics'
|
||||
|
||||
import { createWeb3ReactRoot, Web3ReactProvider } from '@web3-react/core'
|
||||
import { BlockUpdater } from 'lib/hooks/useBlockNumber'
|
||||
import { MulticallUpdater } from 'lib/state/multicall'
|
||||
import { StrictMode } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Provider } from 'react-redux'
|
||||
import { HashRouter } from 'react-router-dom'
|
||||
import { createWeb3ReactRoot, Web3ReactProvider } from 'web3-react-core'
|
||||
|
||||
import Blocklist from './components/Blocklist'
|
||||
import { NetworkContextName } from './constants/misc'
|
||||
@@ -73,4 +73,3 @@ ReactDOM.render(
|
||||
if (process.env.REACT_APP_SERVICE_WORKER !== 'false') {
|
||||
serviceWorkerRegistration.register()
|
||||
}
|
||||
export { INFURA_NETWORK_URLS } from 'constants/chainInfo'
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
@import "~@fontsource/ibm-plex-mono/400.css";
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: url("~@fontsource/inter/files/inter-cyrillic-ext-400-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-400-normal.woff") format("woff");
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: url("~@fontsource/inter/files/inter-cyrillic-400-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-400-normal.woff") format("woff");
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: url("~@fontsource/inter/files/inter-greek-ext-400-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-400-normal.woff") format("woff");
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: url("~@fontsource/inter/files/inter-greek-400-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-400-normal.woff") format("woff");
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: url("~@fontsource/inter/files/inter-vietnamese-400-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-400-normal.woff") format("woff");
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: url("~@fontsource/inter/files/inter-latin-ext-400-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-400-normal.woff") format("woff");
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: url("~@fontsource/inter/files/inter-latin-400-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-400-normal.woff") format("woff");
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 500;
|
||||
src: url("~@fontsource/inter/files/inter-cyrillic-ext-500-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-500-normal.woff") format("woff");
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 500;
|
||||
src: url("~@fontsource/inter/files/inter-cyrillic-500-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-500-normal.woff") format("woff");
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 500;
|
||||
src: url("~@fontsource/inter/files/inter-greek-ext-500-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-500-normal.woff") format("woff");
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 500;
|
||||
src: url("~@fontsource/inter/files/inter-greek-500-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-500-normal.woff") format("woff");
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 500;
|
||||
src: url("~@fontsource/inter/files/inter-vietnamese-500-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-500-normal.woff") format("woff");
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 500;
|
||||
src: url("~@fontsource/inter/files/inter-latin-ext-500-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-500-normal.woff") format("woff");
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 500;
|
||||
src: url("~@fontsource/inter/files/inter-latin-500-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-500-normal.woff") format("woff");
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 600;
|
||||
src: url("~@fontsource/inter/files/inter-cyrillic-ext-600-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-600-normal.woff") format("woff");
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 600;
|
||||
src: url("~@fontsource/inter/files/inter-cyrillic-600-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-600-normal.woff") format("woff");
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 600;
|
||||
src: url("~@fontsource/inter/files/inter-greek-ext-600-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-600-normal.woff") format("woff");
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 600;
|
||||
src: url("~@fontsource/inter/files/inter-greek-600-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-600-normal.woff") format("woff");
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 600;
|
||||
src: url("~@fontsource/inter/files/inter-vietnamese-600-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-600-normal.woff") format("woff");
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 600;
|
||||
src: url("~@fontsource/inter/files/inter-latin-ext-600-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-600-normal.woff") format("woff");
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 600;
|
||||
src: url("~@fontsource/inter/files/inter-latin-600-normal.woff2") format("woff2"), url("~@fontsource/inter/files/inter-all-600-normal.woff") format("woff");
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "InterVariable";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 100 900;
|
||||
src: url("~@fontsource/inter/files/inter-cyrillic-ext-variable-wghtOnly-normal.woff2") format("woff2");
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "InterVariable";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 100 900;
|
||||
src: url("~@fontsource/inter/files/inter-cyrillic-variable-wghtOnly-normal.woff2") format("woff2");
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "InterVariable";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 100 900;
|
||||
src: url("~@fontsource/inter/files/inter-greek-ext-variable-wghtOnly-normal.woff2") format("woff2");
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "InterVariable";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 100 900;
|
||||
src: url("~@fontsource/inter/files/inter-greek-variable-wghtOnly-normal.woff2") format("woff2");
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "InterVariable";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 100 900;
|
||||
src: url("~@fontsource/inter/files/inter-vietnamese-variable-wghtOnly-normal.woff2") format("woff2");
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "InterVariable";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 100 900;
|
||||
src: url("~@fontsource/inter/files/inter-latin-ext-variable-wghtOnly-normal.woff2") format("woff2");
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "InterVariable";
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 100 900;
|
||||
src: url("~@fontsource/inter/files/inter-latin-variable-wghtOnly-normal.woff2") format("woff2");
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
BIN
src/lib/assets/missing-token-image.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
@@ -1,12 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function SvgCheck(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<circle cx={10} cy={10} r={10} />
|
||||
<path d="M14 7l-5.5 5.5L6 10" stroke="#fff" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default SvgCheck
|
||||
@@ -1,19 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function SvgExpando(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path className="expando_svg__left" d="M18 15l-6-6" />
|
||||
<path className="expando_svg__right" d="M12 9l-6 6" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default SvgExpando
|
||||
@@ -1,18 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function SvgLogo(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg viewBox="0 0 14 15" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path d="M4.152 1.551c-.188-.029-.196-.032-.107-.045.17-.026.57.009.846.074.644.152 1.23.542 1.856 1.235l.166.184.238-.038c1.002-.16 2.02-.033 2.873.358.235.108.605.322.65.377.016.018.043.13.06.251.064.418.033.737-.096.976-.07.13-.074.171-.027.283a.274.274 0 00.246.154c.212 0 .44-.34.545-.814l.042-.189.083.094c.457.514.815 1.214.876 1.712l.016.13-.076-.118a1.462 1.462 0 00-.435-.453c-.306-.201-.63-.27-1.486-.315-.774-.04-1.212-.106-1.646-.247-.739-.24-1.111-.558-1.989-1.702-.39-.509-.63-.79-.87-1.016-.545-.515-1.08-.785-1.765-.89z" />
|
||||
<path d="M10.85 2.686c.019-.34.065-.565.159-.77a.825.825 0 01.077-.148c.005 0-.011.06-.036.133-.068.2-.08.472-.032.789.06.402.093.46.52.894.201.204.434.46.519.571l.154.2-.154-.143c-.188-.175-.62-.517-.716-.566-.064-.032-.074-.032-.113.007-.037.036-.044.09-.05.346-.007.399-.062.655-.194.91-.071.14-.082.11-.018-.047.048-.116.053-.168.053-.554 0-.775-.094-.962-.637-1.28a5.971 5.971 0 00-.504-.26 1.912 1.912 0 01-.246-.12c.015-.015.545.139.758.22.318.122.37.137.409.123.025-.01.038-.085.05-.305zM4.517 4.013c-.381-.522-.618-1.323-.566-1.922l.015-.185.087.015c.164.03.445.134.577.214.361.218.518.505.677 1.243.047.216.108.46.136.544.045.133.217.444.356.646.1.146.034.215-.188.195-.339-.03-.798-.345-1.094-.75zM10.386 7.9c-1.784-.713-2.412-1.333-2.412-2.378 0-.154.005-.28.012-.28.006 0 .075.05.153.113.362.288.767.411 1.889.574.66.096 1.03.173 1.373.286 1.09.359 1.763 1.087 1.924 2.08.046.288.02.828-.057 1.113-.06.225-.242.63-.29.646-.014.005-.027-.046-.03-.116-.018-.372-.208-.735-.526-1.007-.362-.309-.848-.555-2.036-1.03zM9.134 8.197a3.133 3.133 0 00-.086-.375l-.046-.135.085.095c.117.13.21.297.288.52.06.17.066.22.066.496 0 .271-.008.328-.064.48a1.518 1.518 0 01-.376.596c-.326.33-.745.512-1.35.588-.105.013-.411.035-.68.049-.679.035-1.126.108-1.527.248a.324.324 0 01-.115.027c-.016-.016.258-.178.483-.286.318-.153.635-.236 1.345-.353.35-.058.713-.129.805-.157.868-.264 1.315-.947 1.172-1.793z" />
|
||||
<path d="M9.952 9.641c-.237-.506-.292-.995-.162-1.451.014-.05.036-.089.05-.089.013 0 .07.03.124.067.11.073.328.196.912.512.728.395 1.144.7 1.426 1.05.247.305.4.654.474 1.078.042.24.017.82-.045 1.062-.196.764-.65 1.364-1.3 1.714-.095.051-.18.093-.19.093-.009 0 .026-.087.077-.194.219-.454.244-.895.079-1.386-.102-.301-.308-.668-.724-1.289-.484-.72-.602-.913-.721-1.167zM3.25 12.374c.663-.556 1.486-.95 2.237-1.072a3.51 3.51 0 011.161.045c.48.122.91.396 1.133.721.218.319.312.596.41 1.214.038.243.08.488.092.543.073.32.216.576.392.704.28.204.764.217 1.239.033a.618.618 0 01.155-.048c.017.017-.222.176-.39.26a1.334 1.334 0 01-.648.156c-.435 0-.796-.22-1.098-.668a5.3 5.3 0 01-.296-.588c-.318-.721-.475-.94-.844-1.181-.322-.21-.737-.247-1.049-.095-.41.2-.524.72-.23 1.05a.911.911 0 00.512.266.545.545 0 00.619-.544c0-.217-.084-.34-.295-.436-.289-.129-.598.022-.597.291 0 .115.051.187.167.24.074.033.076.035.015.023-.264-.055-.326-.372-.114-.582.256-.252.784-.141.965.204.076.145.085.433.019.607-.15.39-.582.595-1.022.483-.3-.076-.421-.158-.782-.527-.627-.642-.87-.767-1.774-.907l-.174-.027.197-.165z" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M.308.884C2.402 3.41 3.845 4.452 4.005 4.672c.132.182.082.346-.144.474a1.381 1.381 0 01-.515.143c-.147 0-.198-.056-.198-.056-.085-.08-.133-.066-.57-.837A132.96 132.96 0 001.45 2.67c-.032-.03-.031-.03 1.067 1.923.177.407.035.556.035.614 0 .118-.033.18-.179.343-.244.27-.353.574-.432 1.203-.088.705-.336 1.203-1.024 2.056-.402.499-.468.59-.57.792-.128.253-.163.395-.177.714-.015.339.014.557.118.88.09.284.186.47.429.844.21.323.33.563.33.657 0 .074.014.074.34.001.776-.174 1.407-.48 1.762-.857.22-.233.271-.361.273-.68.001-.208-.006-.252-.063-.372-.092-.195-.26-.358-.63-.61-.486-.33-.694-.595-.75-.96-.048-.3.007-.511.275-1.07.278-.58.347-.827.394-1.41.03-.377.071-.526.18-.646.114-.124.216-.166.498-.204.459-.063.75-.18.99-.4a.853.853 0 00.31-.652l.01-.21-.117-.134C4.098 4.004.026.5 0 .5-.005.5.133.673.308.884zm.976 9.815a.37.37 0 00-.115-.489c-.15-.1-.385-.052-.385.077 0 .04.022.069.072.094.084.043.09.091.024.19-.067.099-.061.186.015.246.123.095.297.043.389-.118zM4.925 5.999c-.215.065-.424.292-.49.53-.039.145-.016.4.043.478.096.127.188.16.439.159.49-.003.916-.212.966-.474.04-.214-.147-.51-.405-.641a.965.965 0 00-.553-.052zm.574.445c.075-.107.042-.222-.087-.3-.244-.149-.615-.026-.615.204 0 .115.193.24.37.24.118 0 .28-.07.332-.144z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default SvgLogo
|
||||
@@ -1,17 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function SvgSpinner(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<mask id="spinner_svg__a">
|
||||
<path fill="#fff" strokeWidth={0} d="M0 0h24v24H0z" />
|
||||
<path fill="#000" strokeWidth={0} d="M0 0h12v12H0z" />
|
||||
<circle cx={2} cy={12} r={1} fill="#fff" strokeWidth={0} />
|
||||
<circle cx={12} cy={2} r={1} fill="#fff" strokeWidth={0} />
|
||||
</mask>
|
||||
<circle cx={12} cy={12} r={10} mask="url(#spinner_svg__a)" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default SvgSpinner
|
||||
10
src/lib/assets/svg/auto_router.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="23" height="20" viewBox="0 0 23 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="gradient" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(95)">
|
||||
<stop id="stop1" offset="0" stop-color="#2274E2"/>
|
||||
<stop id="stop1" offset="0.5" stop-color="#2274E2"/>
|
||||
<stop id="stop2" offset="1" stop-color="#3FB672" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M16 16C10 16 9 10 5 10M16 16C16 17.6569 17.3431 19 19 19C20.6569 19 22 17.6569 22 16C22 14.3431 20.6569 13 19 13C17.3431 13 16 14.3431 16 16ZM5 10C9 10 10 4 16 4M5 10H1.5M16 4C16 5.65685 17.3431 7 19 7C20.6569 7 22 5.65685 22 4C22 2.34315 20.6569 1 19 1C17.3431 1 16 2.34315 16 4Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="url(#gradient)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 780 B |
35
src/lib/assets/svg/inline_spinner.svg
Normal file
@@ -0,0 +1,35 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask">
|
||||
<circle cx="12" cy="12" r="10" fill="black" stroke="black" stroke-width="2" />
|
||||
<rect width="12" height="12" fill="white" stroke-width="0" />
|
||||
<circle cx="2" cy="12" r="1" fill="white" stroke-width="0" />
|
||||
<circle cx="12" cy="2" r="1" fill="white" stroke-width="0" />
|
||||
</mask>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="6"
|
||||
stroke="none"
|
||||
/>
|
||||
<circle
|
||||
id="track"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
fill="none"
|
||||
/>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
fill="none"
|
||||
mask="url(#mask)"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 931 B |
@@ -1,12 +1,11 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask">
|
||||
<rect width="24" height="24" fill="white" stroke-width="0" />
|
||||
<circle cx="12" cy="12" r="10" stroke="white" stroke-width="2" />
|
||||
<rect width="12" height="12" fill="black" stroke-width="0" />
|
||||
<circle cx="2" cy="12" r="1" fill="white" stroke-width="0" />
|
||||
<circle cx="12" cy="2" r="1" fill="white" stroke-width="0" />
|
||||
</mask>
|
||||
<circle
|
||||
id="circle"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
|
||||
|
Before Width: | Height: | Size: 608 B After Width: | Height: | Size: 592 B |
5
src/lib/assets/svg/wallet.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="M2 7C2 5.89543 2.89543 5 4 5H20C21.1046 5 22 5.89543 22 7V18C22 19.1046 21.1046 20 20 20H4C2.89543 20 2 19.1046 2 18V7Z" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M4 19H20C21.1046 19 22 18.1046 22 17V14C22 12.8954 21.1046 12 20 12H16C15.4477 12 14.9935 12.4624 14.7645 12.965C14.4438 13.6688 13.789 14.5 12 14.5C10.29 14.5 9.48213 13.7406 9.1936 13.0589C8.96576 12.5206 8.49905 12 7.91447 12H4C2.89543 12 2 12.8954 2 14V17C2 18.1046 2.89543 19 4 19Z" fill="currentColor"/>
|
||||
<path d="M22 13V11C22 9.89543 21.1034 9 19.9989 9C14.0294 9 9.97062 9 4.00115 9C2.89658 9 2 9.89543 2 11V13" stroke="currentColor" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 743 B |
@@ -1,6 +1,6 @@
|
||||
import { AlertTriangle, Icon, LargeIcon } from 'lib/icons'
|
||||
import styled, { Color, css, keyframes, ThemedText } from 'lib/theme'
|
||||
import { ReactNode } from 'react'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
|
||||
import Button from './Button'
|
||||
import Row from './Row'
|
||||
@@ -15,7 +15,7 @@ const StyledButton = styled(Button)`
|
||||
}
|
||||
`
|
||||
|
||||
const UpdateRow = styled(Row)``
|
||||
const ActionRow = styled(Row)``
|
||||
|
||||
const grow = keyframes`
|
||||
from {
|
||||
@@ -28,12 +28,12 @@ const grow = keyframes`
|
||||
}
|
||||
`
|
||||
|
||||
const updateCss = css`
|
||||
const actionCss = css`
|
||||
border: 1px solid ${({ theme }) => theme.outline};
|
||||
padding: calc(0.25em - 1px);
|
||||
padding-left: calc(0.75em - 1px);
|
||||
|
||||
${UpdateRow} {
|
||||
${ActionRow} {
|
||||
animation: ${grow} 0.25s ease-in;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -45,44 +45,43 @@ const updateCss = css`
|
||||
}
|
||||
`
|
||||
|
||||
export const Overlay = styled(Row)<{ update?: boolean }>`
|
||||
export const Overlay = styled(Row)<{ hasAction: boolean }>`
|
||||
border-radius: ${({ theme }) => theme.borderRadius}em;
|
||||
flex-direction: row-reverse;
|
||||
min-height: 3.5em;
|
||||
transition: padding 0.25s ease-out;
|
||||
|
||||
${({ update }) => update && updateCss}
|
||||
${({ hasAction }) => hasAction && actionCss}
|
||||
`
|
||||
|
||||
export interface ActionButtonProps {
|
||||
color?: Color
|
||||
disabled?: boolean
|
||||
update?: { message: ReactNode; action: ReactNode; icon?: Icon }
|
||||
export interface Action {
|
||||
message: ReactNode
|
||||
icon?: Icon
|
||||
onClick: () => void
|
||||
onUpdate?: () => void
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default function ActionButton({
|
||||
color = 'accent',
|
||||
disabled,
|
||||
update,
|
||||
onClick,
|
||||
onUpdate,
|
||||
children,
|
||||
}: ActionButtonProps) {
|
||||
export interface BaseProps {
|
||||
color?: Color
|
||||
action?: Action
|
||||
}
|
||||
|
||||
export type ActionButtonProps = BaseProps & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof BaseProps>
|
||||
|
||||
export default function ActionButton({ color = 'accent', disabled, action, onClick, children }: ActionButtonProps) {
|
||||
const textColor = useMemo(() => (color === 'accent' && !disabled ? 'onAccent' : 'currentColor'), [color, disabled])
|
||||
return (
|
||||
<Overlay update={Boolean(update)} flex align="stretch">
|
||||
<StyledButton color={color} disabled={disabled} onClick={update ? onUpdate : onClick}>
|
||||
<ThemedText.TransitionButton buttonSize={update ? 'medium' : 'large'} color="currentColor">
|
||||
{update ? update.action : children}
|
||||
<Overlay hasAction={Boolean(action)} flex align="stretch">
|
||||
<StyledButton color={color} disabled={disabled} onClick={action ? action.onClick : onClick}>
|
||||
<ThemedText.TransitionButton buttonSize={action ? 'medium' : 'large'} color={textColor}>
|
||||
{action ? action.children : children}
|
||||
</ThemedText.TransitionButton>
|
||||
</StyledButton>
|
||||
{update && (
|
||||
<UpdateRow gap={0.5}>
|
||||
<LargeIcon color="currentColor" icon={update.icon || AlertTriangle} />
|
||||
<ThemedText.Subhead2>{update?.message}</ThemedText.Subhead2>
|
||||
</UpdateRow>
|
||||
{action && (
|
||||
<ActionRow gap={0.5}>
|
||||
<LargeIcon color="currentColor" icon={action.icon || AlertTriangle} />
|
||||
<ThemedText.Subhead2>{action?.message}</ThemedText.Subhead2>
|
||||
</ActionRow>
|
||||
)}
|
||||
</Overlay>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import Row from 'lib/components/Row'
|
||||
import { Logo } from 'lib/icons'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
import styled, { brand, ThemedText } from 'lib/theme'
|
||||
|
||||
import ExternalLink from './ExternalLink'
|
||||
|
||||
@@ -13,26 +13,28 @@ const UniswapA = styled(ExternalLink)`
|
||||
${Logo} {
|
||||
fill: ${({ theme }) => theme.secondary};
|
||||
height: 1em;
|
||||
transition: transform 0.25s ease;
|
||||
transition: transform 0.25s ease, fill 0.25s ease;
|
||||
width: 1em;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
:hover {
|
||||
fill: ${({ theme }) => theme.onHover(theme.secondary)};
|
||||
transform: rotate(-5deg);
|
||||
}
|
||||
:hover ${Logo} {
|
||||
fill: ${brand};
|
||||
transform: rotate(-5deg);
|
||||
}
|
||||
`
|
||||
|
||||
export default function BrandedFooter() {
|
||||
return (
|
||||
<UniswapA href={`https://app.uniswap.org/`}>
|
||||
<Row gap={0.4} justify="center">
|
||||
<Logo />
|
||||
<ThemedText.Caption color="secondary">
|
||||
<Trans>Powered by the Uniswap protocol</Trans>
|
||||
</ThemedText.Caption>
|
||||
</Row>
|
||||
</UniswapA>
|
||||
<Row justify="center">
|
||||
<UniswapA href={`https://uniswap.org/`}>
|
||||
<Row gap={0.25}>
|
||||
<Logo />
|
||||
<ThemedText.Caption>
|
||||
<Trans>Powered by the Uniswap protocol</Trans>
|
||||
</ThemedText.Caption>
|
||||
</Row>
|
||||
</UniswapA>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Icon } from 'lib/icons'
|
||||
import styled, { Color } from 'lib/theme'
|
||||
import { ComponentProps } from 'react'
|
||||
import { ComponentProps, forwardRef } from 'react'
|
||||
|
||||
export const BaseButton = styled.button`
|
||||
background-color: transparent;
|
||||
@@ -10,6 +10,7 @@ export const BaseButton = styled.button`
|
||||
cursor: pointer;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
height: inherit;
|
||||
line-height: inherit;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -55,10 +56,12 @@ interface IconButtonProps {
|
||||
iconProps?: ComponentProps<Icon>
|
||||
}
|
||||
|
||||
export function IconButton({ icon: Icon, iconProps, ...props }: IconButtonProps & ComponentProps<typeof BaseButton>) {
|
||||
return (
|
||||
<SecondaryButton {...props}>
|
||||
<Icon {...iconProps} />
|
||||
</SecondaryButton>
|
||||
)
|
||||
}
|
||||
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps & ComponentProps<typeof BaseButton>>(
|
||||
function IconButton({ icon: Icon, iconProps, ...props }: IconButtonProps & ComponentProps<typeof BaseButton>, ref) {
|
||||
return (
|
||||
<SecondaryButton {...props} ref={ref}>
|
||||
<Icon {...iconProps} />
|
||||
</SecondaryButton>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -40,7 +40,10 @@ export function Provider({ value, children }: ProviderProps) {
|
||||
}
|
||||
}, [active])
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<div
|
||||
ref={ref}
|
||||
style={{ isolation: 'isolate' }} // creates a new stacking context, preventing the dialog from intercepting non-dialog clicks
|
||||
>
|
||||
<Context.Provider value={context}>{children}</Context.Provider>
|
||||
</div>
|
||||
)
|
||||
@@ -73,13 +76,12 @@ export const Modal = styled.div<{ color: Color }>`
|
||||
border-radius: ${({ theme }) => theme.borderRadius * 0.75}em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 0.5em);
|
||||
height: 100%;
|
||||
left: 0;
|
||||
margin: 0.25em;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: calc(100% - 0.5em);
|
||||
width: 100%;
|
||||
z-index: ${Layer.DIALOG};
|
||||
`
|
||||
|
||||
@@ -106,7 +108,7 @@ export default function Dialog({ color, children, onClose = () => void 0 }: Dial
|
||||
context.element &&
|
||||
createPortal(
|
||||
<ThemeProvider>
|
||||
<Modal className="dialog" color={color} ref={dialog}>
|
||||
<Modal color={color} ref={dialog}>
|
||||
<OnCloseContext.Provider value={onClose}>{children}</OnCloseContext.Provider>
|
||||
</Modal>
|
||||
</ThemeProvider>,
|
||||
|
||||
@@ -36,7 +36,7 @@ export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, E
|
||||
error={this.state.error}
|
||||
header={<Trans>Something went wrong.</Trans>}
|
||||
action={<Trans>Reload the page</Trans>}
|
||||
onAction={() => window.location.reload()}
|
||||
onClick={() => window.location.reload()}
|
||||
/>
|
||||
</Dialog>
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ import Rule from '../Rule'
|
||||
|
||||
const HeaderIcon = styled(LargeIcon)`
|
||||
flex-grow: 1;
|
||||
transition: height 0.25s, width 0.25s;
|
||||
|
||||
svg {
|
||||
transition: height 0.25s, width 0.25s;
|
||||
@@ -66,7 +67,7 @@ const ExpandoColumn = styled(Column)<{ open: boolean }>`
|
||||
transition: flex-grow 0.25s;
|
||||
|
||||
${Column} {
|
||||
height: 100%;
|
||||
height: 6.825em;
|
||||
padding: ${({ open }) => (open ? '0.5em 0' : 0)};
|
||||
transition: padding 0.25s;
|
||||
|
||||
@@ -87,10 +88,10 @@ interface ErrorDialogProps {
|
||||
header?: ReactNode
|
||||
error: Error
|
||||
action: ReactNode
|
||||
onAction: () => void
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
export default function ErrorDialog({ header, error, action, onAction }: ErrorDialogProps) {
|
||||
export default function ErrorDialog({ header, error, action, onClick }: ErrorDialogProps) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [details, setDetails] = useState<HTMLDivElement | null>(null)
|
||||
const scrollbar = useScrollbar(details)
|
||||
@@ -117,13 +118,13 @@ export default function ErrorDialog({ header, error, action, onAction }: ErrorDi
|
||||
<Rule />
|
||||
<ErrorColumn>
|
||||
<Column gap={0.5} ref={setDetails} css={scrollbar}>
|
||||
<ThemedText.Code>
|
||||
<ThemedText.Code userSelect>
|
||||
{error.name}
|
||||
{error.message ? `: ${error.message}` : ''}
|
||||
</ThemedText.Code>
|
||||
</Column>
|
||||
</ErrorColumn>
|
||||
<ActionButton onClick={onAction}>{action}</ActionButton>
|
||||
<ActionButton onClick={onClick}>{action}</ActionButton>
|
||||
</ExpandoColumn>
|
||||
</Column>
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SUPPORTED_LOCALES } from 'constants/locales'
|
||||
import { WidgetProps } from 'lib/components/Widget'
|
||||
import { IntegrationError } from 'lib/errors'
|
||||
import { PropsWithChildren, useEffect } from 'react'
|
||||
@@ -18,5 +19,12 @@ export default function WidgetsPropsValidator(props: PropsWithChildren<WidgetPro
|
||||
}
|
||||
}, [width])
|
||||
|
||||
const { locale } = props
|
||||
useEffect(() => {
|
||||
if (locale && locale !== 'pseudo' && !SUPPORTED_LOCALES.includes(locale)) {
|
||||
console.warn('Unsupported locale: ', locale)
|
||||
}
|
||||
}, [locale])
|
||||
|
||||
return <>{props.children}</>
|
||||
}
|
||||
|
||||
36
src/lib/components/EtherscanLink.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
|
||||
import { Link } from 'lib/icons'
|
||||
import styled, { Color } from 'lib/theme'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
import ExternalLink from './ExternalLink'
|
||||
import Row from './Row'
|
||||
|
||||
const StyledExternalLink = styled(ExternalLink)<{ color: Color }>`
|
||||
color: ${({ theme, color }) => theme[color]};
|
||||
text-decoration: none;
|
||||
`
|
||||
|
||||
interface EtherscanLinkProps {
|
||||
type: ExplorerDataType
|
||||
data?: string
|
||||
color?: Color
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default function EtherscanLink({ data, type, color = 'currentColor', children }: EtherscanLinkProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const url = useMemo(
|
||||
() => data && getExplorerLink(chainId || SupportedChainId.MAINNET, data, type),
|
||||
[chainId, data, type]
|
||||
)
|
||||
return (
|
||||
<StyledExternalLink href={url} color={color} target="_blank">
|
||||
<Row gap={0.25}>
|
||||
{children} <Link />
|
||||
</Row>
|
||||
</StyledExternalLink>
|
||||
)
|
||||
}
|
||||
@@ -8,7 +8,7 @@ export default function ExternalLink({
|
||||
href,
|
||||
rel = 'noopener noreferrer',
|
||||
...rest
|
||||
}: Omit<HTMLProps<HTMLAnchorElement>, 'as' | 'ref' | 'onClick'> & { href: string }) {
|
||||
}: Omit<HTMLProps<HTMLAnchorElement>, 'as' | 'ref' | 'onClick'> & { href?: string }) {
|
||||
return (
|
||||
<a target={target} rel={rel} href={href} {...rest}>
|
||||
{rest.children}
|
||||
|
||||
@@ -11,16 +11,19 @@ export const BoundaryProvider = BoundaryContext.Provider
|
||||
|
||||
const PopoverContainer = styled.div<{ show: boolean }>`
|
||||
background-color: ${({ theme }) => theme.dialog};
|
||||
border: 1px solid ${({ theme }) => theme.outline};
|
||||
border-radius: 0.5em;
|
||||
opacity: ${(props) => (props.show ? 1 : 0)};
|
||||
padding: 8px;
|
||||
padding: 10px 12px;
|
||||
transition: visibility 0.25s linear, opacity 0.25s linear;
|
||||
visibility: ${(props) => (props.show ? 'visible' : 'hidden')};
|
||||
z-index: ${Layer.TOOLTIP};
|
||||
`
|
||||
|
||||
const Reference = styled.div`
|
||||
align-self: flex-start;
|
||||
display: inline-block;
|
||||
height: 1em;
|
||||
`
|
||||
|
||||
const Arrow = styled.div`
|
||||
@@ -32,7 +35,6 @@ const Arrow = styled.div`
|
||||
background: ${({ theme }) => theme.dialog};
|
||||
border: 1px solid ${({ theme }) => theme.outline};
|
||||
content: '';
|
||||
|
||||
height: 8px;
|
||||
position: absolute;
|
||||
transform: rotate(45deg);
|
||||
@@ -40,32 +42,36 @@ const Arrow = styled.div`
|
||||
}
|
||||
|
||||
&.arrow-top {
|
||||
bottom: -5px;
|
||||
bottom: -4px;
|
||||
::before {
|
||||
border-radius: 1px;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.arrow-bottom {
|
||||
top: -5px;
|
||||
top: -5px; // includes -1px from border
|
||||
::before {
|
||||
border-bottom: none;
|
||||
border-right: none;
|
||||
border-radius: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&.arrow-left {
|
||||
right: -5px;
|
||||
right: -4px;
|
||||
::before {
|
||||
border-bottom: none;
|
||||
border-left: none;
|
||||
border-radius: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&.arrow-right {
|
||||
left: -5px;
|
||||
left: -5px; // includes -1px from border
|
||||
::before {
|
||||
border-radius: 1px;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
}
|
||||
@@ -77,10 +83,11 @@ export interface PopoverProps {
|
||||
show: boolean
|
||||
children: React.ReactNode
|
||||
placement: Placement
|
||||
offset?: number
|
||||
contained?: true
|
||||
}
|
||||
|
||||
export default function Popover({ content, show, children, placement, contained }: PopoverProps) {
|
||||
export default function Popover({ content, show, children, placement, offset, contained }: PopoverProps) {
|
||||
const boundary = useContext(BoundaryContext)
|
||||
const reference = useRef<HTMLDivElement>(null)
|
||||
|
||||
@@ -90,8 +97,8 @@ export default function Popover({ content, show, children, placement, contained
|
||||
|
||||
const options = useMemo((): Options => {
|
||||
const modifiers: Options['modifiers'] = [
|
||||
{ name: 'offset', options: { offset: [5, 5] } },
|
||||
{ name: 'arrow', options: { element: arrow, padding: 6 } },
|
||||
{ name: 'offset', options: { offset: [4, offset || 4] } },
|
||||
{ name: 'arrow', options: { element: arrow, padding: 4 } },
|
||||
]
|
||||
if (contained) {
|
||||
modifiers.push(
|
||||
@@ -118,7 +125,7 @@ export default function Popover({ content, show, children, placement, contained
|
||||
strategy: 'absolute',
|
||||
modifiers,
|
||||
}
|
||||
}, [arrow, boundary, placement, contained])
|
||||
}, [offset, arrow, contained, placement, boundary])
|
||||
|
||||
const { styles, attributes } = usePopper(reference.current, popover, options)
|
||||
|
||||
|
||||
@@ -1,17 +1,36 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useLingui } from '@lingui/react'
|
||||
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
|
||||
import { useUSDCValue } from 'hooks/useUSDCPrice'
|
||||
import { useSwapAmount, useSwapCurrency, useSwapInfo } from 'lib/hooks/swap'
|
||||
import { loadingTransitionCss } from 'lib/css/loading'
|
||||
import {
|
||||
useIsSwapFieldIndependent,
|
||||
useSwapAmount,
|
||||
useSwapCurrency,
|
||||
useSwapCurrencyAmount,
|
||||
useSwapInfo,
|
||||
} from 'lib/hooks/swap'
|
||||
import { usePrefetchCurrencyColor } from 'lib/hooks/useCurrencyColor'
|
||||
import { Field } from 'lib/state/swap'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
import { useCallback } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { TradeState } from 'state/routing/types'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
import { maxAmountSpend } from 'utils/maxAmountSpend'
|
||||
|
||||
import Column from '../Column'
|
||||
import Row from '../Row'
|
||||
import TokenImg from '../TokenImg'
|
||||
import TokenInput from './TokenInput'
|
||||
|
||||
export const USDC = styled(Row)`
|
||||
${loadingTransitionCss};
|
||||
`
|
||||
|
||||
export const Balance = styled(ThemedText.Body2)<{ focused: boolean }>`
|
||||
opacity: ${({ focused }) => (focused ? 1 : 0)};
|
||||
transition: opacity 0.25s ${({ focused }) => (focused ? 'ease-in' : 'ease-out')};
|
||||
`
|
||||
|
||||
const InputColumn = styled(Column)<{ approved?: boolean }>`
|
||||
margin: 0.75em;
|
||||
position: relative;
|
||||
@@ -22,54 +41,92 @@ const InputColumn = styled(Column)<{ approved?: boolean }>`
|
||||
}
|
||||
`
|
||||
|
||||
interface InputProps {
|
||||
disabled?: boolean
|
||||
export interface InputProps {
|
||||
disabled: boolean
|
||||
focused: boolean
|
||||
}
|
||||
|
||||
export default function Input({ disabled }: InputProps) {
|
||||
interface UseFormattedFieldAmountArguments {
|
||||
disabled: boolean
|
||||
currencyAmount?: CurrencyAmount<Currency>
|
||||
fieldAmount?: string
|
||||
}
|
||||
|
||||
export function useFormattedFieldAmount({ disabled, currencyAmount, fieldAmount }: UseFormattedFieldAmountArguments) {
|
||||
return useMemo(() => {
|
||||
if (disabled) {
|
||||
return ''
|
||||
}
|
||||
if (fieldAmount !== undefined) {
|
||||
return fieldAmount
|
||||
}
|
||||
if (currencyAmount) {
|
||||
return currencyAmount.toSignificant(6)
|
||||
}
|
||||
return ''
|
||||
}, [disabled, currencyAmount, fieldAmount])
|
||||
}
|
||||
|
||||
export default function Input({ disabled, focused }: InputProps) {
|
||||
const { i18n } = useLingui()
|
||||
const {
|
||||
currencyBalances: { [Field.INPUT]: balance },
|
||||
currencyAmounts: { [Field.INPUT]: inputCurrencyAmount },
|
||||
trade: { state: tradeState },
|
||||
tradeCurrencyAmounts: { [Field.INPUT]: swapInputCurrencyAmount },
|
||||
} = useSwapInfo()
|
||||
const inputUSDC = useUSDCValue(inputCurrencyAmount)
|
||||
const inputUSDC = useUSDCValue(swapInputCurrencyAmount)
|
||||
|
||||
const [swapInputAmount, updateSwapInputAmount] = useSwapAmount(Field.INPUT)
|
||||
const [swapInputCurrency, updateSwapInputCurrency] = useSwapCurrency(Field.INPUT)
|
||||
const inputCurrencyAmount = useSwapCurrencyAmount(Field.INPUT)
|
||||
|
||||
// extract eagerly in case of reversal
|
||||
usePrefetchCurrencyColor(swapInputCurrency)
|
||||
|
||||
const isRouteLoading = tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
|
||||
const isDependentField = !useIsSwapFieldIndependent(Field.INPUT)
|
||||
const isLoading = isRouteLoading && isDependentField
|
||||
|
||||
//TODO(ianlapham): mimic logic from app swap page
|
||||
const mockApproved = true
|
||||
|
||||
const onMax = useCallback(() => {
|
||||
if (balance) {
|
||||
updateSwapInputAmount(balance.toExact())
|
||||
}
|
||||
}, [balance, updateSwapInputAmount])
|
||||
// account for gas needed if using max on native token
|
||||
const max = useMemo(() => {
|
||||
const maxAmount = maxAmountSpend(balance)
|
||||
return maxAmount?.greaterThan(0) ? maxAmount.toExact() : undefined
|
||||
}, [balance])
|
||||
|
||||
const balanceColor = useMemo(() => {
|
||||
const insufficientBalance =
|
||||
balance &&
|
||||
(inputCurrencyAmount ? inputCurrencyAmount.greaterThan(balance) : swapInputCurrencyAmount?.greaterThan(balance))
|
||||
return insufficientBalance ? 'error' : undefined
|
||||
}, [balance, inputCurrencyAmount, swapInputCurrencyAmount])
|
||||
|
||||
const amount = useFormattedFieldAmount({
|
||||
disabled,
|
||||
currencyAmount: swapInputCurrencyAmount,
|
||||
fieldAmount: swapInputAmount,
|
||||
})
|
||||
|
||||
return (
|
||||
<InputColumn gap={0.5} approved={mockApproved}>
|
||||
<Row>
|
||||
<ThemedText.Subhead2 color="secondary">
|
||||
<Trans>Trading</Trans>
|
||||
</ThemedText.Subhead2>
|
||||
</Row>
|
||||
<TokenInput
|
||||
currency={swapInputCurrency}
|
||||
amount={(swapInputAmount !== undefined ? swapInputAmount : inputCurrencyAmount?.toSignificant(6)) ?? ''}
|
||||
amount={amount}
|
||||
max={max}
|
||||
disabled={disabled}
|
||||
onMax={onMax}
|
||||
onChangeInput={updateSwapInputAmount}
|
||||
onChangeCurrency={updateSwapInputCurrency}
|
||||
loading={isLoading}
|
||||
>
|
||||
<ThemedText.Body2 color="secondary">
|
||||
<ThemedText.Body2 color="secondary" userSelect>
|
||||
<Row>
|
||||
{inputUSDC ? `$${inputUSDC.toFixed(2)}` : '-'}
|
||||
<USDC isLoading={isRouteLoading}>{inputUSDC ? `$${inputUSDC.toFixed(2)}` : '-'}</USDC>
|
||||
{balance && (
|
||||
<ThemedText.Body2 color={inputCurrencyAmount?.greaterThan(balance) ? 'error' : undefined}>
|
||||
Balance: <span style={{ userSelect: 'text' }}>{formatCurrencyAmount(balance, 4)}</span>
|
||||
</ThemedText.Body2>
|
||||
<Balance color={balanceColor} focused={focused}>
|
||||
Balance: <span>{formatCurrencyAmount(balance, 4, i18n.locale)}</span>
|
||||
</Balance>
|
||||
)}
|
||||
</Row>
|
||||
</ThemedText.Body2>
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useUSDCValue } from 'hooks/useUSDCPrice'
|
||||
import { useLingui } from '@lingui/react'
|
||||
import { atom } from 'jotai'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import BrandedFooter from 'lib/components/BrandedFooter'
|
||||
import { useSwapAmount, useSwapCurrency, useSwapInfo } from 'lib/hooks/swap'
|
||||
import { useIsSwapFieldIndependent, useSwapAmount, useSwapCurrency, useSwapInfo } from 'lib/hooks/swap'
|
||||
import useCurrencyColor from 'lib/hooks/useCurrencyColor'
|
||||
import useUSDCPriceImpact, { toHumanReadablePriceImpact } from 'lib/hooks/useUSDCPriceImpact'
|
||||
import { Field } from 'lib/state/swap'
|
||||
import styled, { DynamicThemeProvider, ThemedText } from 'lib/theme'
|
||||
import { ReactNode, useCallback, useMemo } from 'react'
|
||||
import { computeFiatValuePriceImpact } from 'utils/computeFiatValuePriceImpact'
|
||||
import { PropsWithChildren, useMemo } from 'react'
|
||||
import { TradeState } from 'state/routing/types'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
import { getPriceImpactWarning } from 'utils/prices'
|
||||
|
||||
import Column from '../Column'
|
||||
import Row from '../Row'
|
||||
import { Balance, InputProps, USDC, useFormattedFieldAmount } from './Input'
|
||||
import TokenInput from './TokenInput'
|
||||
|
||||
export const colorAtom = atom<string | undefined>(undefined)
|
||||
@@ -21,31 +24,34 @@ const OutputColumn = styled(Column)<{ hasColor: boolean | null }>`
|
||||
background-color: ${({ theme }) => theme.module};
|
||||
border-radius: ${({ theme }) => theme.borderRadius - 0.25}em;
|
||||
padding: 0.75em;
|
||||
padding-bottom: 0.5em;
|
||||
position: relative;
|
||||
|
||||
// Set transitions to reduce color flashes when switching color/token.
|
||||
// When color loads, transition the background so that it transitions from the empty or last state, but not _to_ the empty state.
|
||||
transition: ${({ hasColor }) => (hasColor ? 'background-color 0.25s ease-out' : undefined)};
|
||||
* {
|
||||
> {
|
||||
// When color is loading, delay the color/stroke so that it seems to transition from the last state.
|
||||
transition: ${({ hasColor }) => (hasColor === null ? 'color 0.25s ease-in, stroke 0.25s ease-in' : undefined)};
|
||||
}
|
||||
`
|
||||
|
||||
interface OutputProps {
|
||||
disabled?: boolean
|
||||
children: ReactNode
|
||||
}
|
||||
export default function Output({ disabled, focused, children }: PropsWithChildren<InputProps>) {
|
||||
const { i18n } = useLingui()
|
||||
|
||||
export default function Output({ disabled, children }: OutputProps) {
|
||||
const {
|
||||
currencyBalances: { [Field.OUTPUT]: balance },
|
||||
currencyAmounts: { [Field.INPUT]: inputCurrencyAmount, [Field.OUTPUT]: outputCurrencyAmount },
|
||||
trade: { state: tradeState },
|
||||
tradeCurrencyAmounts: { [Field.INPUT]: inputCurrencyAmount, [Field.OUTPUT]: outputCurrencyAmount },
|
||||
} = useSwapInfo()
|
||||
|
||||
const [swapOutputAmount, updateSwapOutputAmount] = useSwapAmount(Field.OUTPUT)
|
||||
const [swapOutputCurrency, updateSwapOutputCurrency] = useSwapCurrency(Field.OUTPUT)
|
||||
|
||||
const isRouteLoading = tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
|
||||
const isDependentField = !useIsSwapFieldIndependent(Field.OUTPUT)
|
||||
const isLoading = isRouteLoading && isDependentField
|
||||
|
||||
const overrideColor = useAtomValue(colorAtom)
|
||||
const dynamicColor = useCurrencyColor(swapOutputCurrency)
|
||||
const color = overrideColor || dynamicColor
|
||||
@@ -53,50 +59,45 @@ export default function Output({ disabled, children }: OutputProps) {
|
||||
// different state true/null/false allow smoother color transition
|
||||
const hasColor = swapOutputCurrency ? Boolean(color) || null : false
|
||||
|
||||
const inputUSDC = useUSDCValue(inputCurrencyAmount)
|
||||
const outputUSDC = useUSDCValue(outputCurrencyAmount)
|
||||
const { outputUSDC, priceImpact } = useUSDCPriceImpact(inputCurrencyAmount, outputCurrencyAmount)
|
||||
const priceImpactWarning = useMemo(() => getPriceImpactWarning(priceImpact), [priceImpact])
|
||||
|
||||
const priceImpact = useMemo(() => {
|
||||
const computedChange = computeFiatValuePriceImpact(inputUSDC, outputUSDC)
|
||||
return computedChange ? parseFloat(computedChange.multiply(-1)?.toSignificant(3)) : undefined
|
||||
}, [inputUSDC, outputUSDC])
|
||||
|
||||
const usdc = useMemo(() => {
|
||||
if (outputUSDC) {
|
||||
return `$${outputUSDC.toFixed(2)} (${priceImpact && priceImpact > 0 ? '+' : ''}${priceImpact}%)`
|
||||
}
|
||||
return ''
|
||||
}, [priceImpact, outputUSDC])
|
||||
|
||||
const onMax = useCallback(() => {
|
||||
if (balance) {
|
||||
updateSwapOutputAmount(balance.toExact())
|
||||
}
|
||||
}, [balance, updateSwapOutputAmount])
|
||||
const amount = useFormattedFieldAmount({
|
||||
disabled,
|
||||
currencyAmount: outputCurrencyAmount,
|
||||
fieldAmount: swapOutputAmount,
|
||||
})
|
||||
|
||||
return (
|
||||
<DynamicThemeProvider color={color}>
|
||||
<OutputColumn hasColor={hasColor} gap={0.5}>
|
||||
<Row>
|
||||
<ThemedText.Subhead2 color="secondary">
|
||||
<ThemedText.Subhead1 color="secondary">
|
||||
<Trans>For</Trans>
|
||||
</ThemedText.Subhead2>
|
||||
</ThemedText.Subhead1>
|
||||
</Row>
|
||||
<TokenInput
|
||||
currency={swapOutputCurrency}
|
||||
amount={(swapOutputAmount !== undefined ? swapOutputAmount : outputCurrencyAmount?.toSignificant(6)) ?? ''}
|
||||
amount={amount}
|
||||
disabled={disabled}
|
||||
onMax={onMax}
|
||||
onChangeInput={updateSwapOutputAmount}
|
||||
onChangeCurrency={updateSwapOutputCurrency}
|
||||
loading={isLoading}
|
||||
>
|
||||
<ThemedText.Body2 color="secondary">
|
||||
<ThemedText.Body2 color="secondary" userSelect>
|
||||
<Row>
|
||||
{usdc}
|
||||
<USDC gap={0.5} isLoading={isRouteLoading}>
|
||||
{outputUSDC ? `$${outputUSDC.toFixed(2)}` : '-'}{' '}
|
||||
{priceImpact && (
|
||||
<ThemedText.Body2 color={priceImpactWarning}>
|
||||
({toHumanReadablePriceImpact(priceImpact)})
|
||||
</ThemedText.Body2>
|
||||
)}
|
||||
</USDC>
|
||||
{balance && (
|
||||
<span>
|
||||
Balance: <span style={{ userSelect: 'text' }}>{formatCurrencyAmount(balance, 4)}</span>
|
||||
</span>
|
||||
<Balance focused={focused}>
|
||||
Balance: <span>{formatCurrencyAmount(balance, 4, i18n.locale)}</span>
|
||||
</Balance>
|
||||
)}
|
||||
</Row>
|
||||
</ThemedText.Body2>
|
||||
|
||||
@@ -9,8 +9,7 @@ import Row from '../Row'
|
||||
const ReverseRow = styled(Row)`
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 7.45em;
|
||||
transform: translateX(-50%);
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: ${Layer.OVERLAY};
|
||||
`
|
||||
|
||||
|
||||
139
src/lib/components/Swap/RoutingDiagram/index.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import { Plural, Trans } from '@lingui/macro'
|
||||
import { Currency, TradeType } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { ReactComponent as DotLine } from 'assets/svg/dot_line.svg'
|
||||
import Column from 'lib/components/Column'
|
||||
import Row from 'lib/components/Row'
|
||||
import Rule from 'lib/components/Rule'
|
||||
import TokenImg from 'lib/components/TokenImg'
|
||||
import { AutoRouter } from 'lib/icons'
|
||||
import styled, { Layer, ThemedText } from 'lib/theme'
|
||||
import { useMemo } from 'react'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
|
||||
import { getTokenPath, RoutingDiagramEntry } from './utils'
|
||||
|
||||
const StyledAutoRouterLabel = styled(ThemedText.ButtonSmall)`
|
||||
@supports (-webkit-background-clip: text) and (-webkit-text-fill-color: transparent) {
|
||||
background-image: linear-gradient(90deg, #2172e5 0%, #54e521 163.16%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
`
|
||||
|
||||
function Header({ routes }: { routes: RoutingDiagramEntry[] }) {
|
||||
return (
|
||||
<Row justify="space-between" gap={1}>
|
||||
<ThemedText.Subhead2>
|
||||
<Row gap={0.25}>
|
||||
<AutoRouter />
|
||||
<StyledAutoRouterLabel color="primary" lineHeight={'16px'}>
|
||||
<Trans>Auto Router</Trans>
|
||||
</StyledAutoRouterLabel>
|
||||
</Row>
|
||||
</ThemedText.Subhead2>
|
||||
<ThemedText.Body2>
|
||||
<Plural value={routes.length} _1="Best route via 1 hop" other="Best route via # hops" />
|
||||
</ThemedText.Body2>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
const Dots = styled(DotLine)`
|
||||
color: ${({ theme }) => theme.outline};
|
||||
position: absolute;
|
||||
z-index: ${Layer.UNDERLAYER};
|
||||
`
|
||||
|
||||
const RouteRow = styled(Row)`
|
||||
flex-wrap: nowrap;
|
||||
`
|
||||
|
||||
const RouteNode = styled(Row)`
|
||||
background-color: ${({ theme }) => theme.interactive};
|
||||
border-radius: ${({ theme }) => `${(theme.borderRadius ?? 1) * 0.5}em`};
|
||||
margin-left: 1.625em;
|
||||
padding: 0.25em 0.375em;
|
||||
width: max-content;
|
||||
`
|
||||
|
||||
const RouteBadge = styled.div`
|
||||
background-color: ${({ theme }) => theme.module};
|
||||
border-radius: ${({ theme }) => `${(theme.borderRadius ?? 1) * 0.25}em`};
|
||||
padding: 0.125em;
|
||||
`
|
||||
|
||||
function RouteDetail({ route }: { route: RoutingDiagramEntry }) {
|
||||
const protocol = route.protocol.toUpperCase()
|
||||
return (
|
||||
<RouteNode>
|
||||
<Row gap={0.375}>
|
||||
<ThemedText.Caption>{route.percent.toSignificant(2)}%</ThemedText.Caption>
|
||||
<RouteBadge>
|
||||
<ThemedText.Badge color="secondary">{protocol}</ThemedText.Badge>
|
||||
</RouteBadge>
|
||||
</Row>
|
||||
</RouteNode>
|
||||
)
|
||||
}
|
||||
|
||||
const RoutePool = styled(RouteNode)`
|
||||
margin: 0 0.75em;
|
||||
`
|
||||
|
||||
function Pool({
|
||||
originCurrency,
|
||||
targetCurrency,
|
||||
feeAmount,
|
||||
}: {
|
||||
originCurrency: Currency
|
||||
targetCurrency: Currency
|
||||
feeAmount: FeeAmount
|
||||
}) {
|
||||
return (
|
||||
<RoutePool>
|
||||
<ThemedText.Caption>
|
||||
<Row gap={0.25}>
|
||||
<TokenImg token={originCurrency} />
|
||||
<TokenImg token={targetCurrency} style={{ marginLeft: '-0.65em' }} />
|
||||
{feeAmount / 10_000}%
|
||||
</Row>
|
||||
</ThemedText.Caption>
|
||||
</RoutePool>
|
||||
)
|
||||
}
|
||||
|
||||
function Route({ route }: { route: RoutingDiagramEntry }) {
|
||||
const [originCurrency] = route.path[0]
|
||||
const [, targetCurrency] = route.path[route.path.length - 1]
|
||||
|
||||
return (
|
||||
<Row align="center" style={{ gridTemplateColumns: '1em 1fr 1em' }}>
|
||||
<TokenImg token={originCurrency} />
|
||||
<RouteRow flex style={{ position: 'relative' }}>
|
||||
<Dots />
|
||||
<RouteDetail route={route} />
|
||||
<RouteRow justify="space-evenly" flex>
|
||||
{route.path.map(([originCurrency, targetCurrency, feeAmount], index) => (
|
||||
<Pool key={index} originCurrency={originCurrency} targetCurrency={targetCurrency} feeAmount={feeAmount} />
|
||||
))}
|
||||
</RouteRow>
|
||||
</RouteRow>
|
||||
<TokenImg token={targetCurrency} />
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
export default function RoutingDiagram({ trade }: { trade: InterfaceTrade<Currency, Currency, TradeType> }) {
|
||||
const routes: RoutingDiagramEntry[] = useMemo(() => getTokenPath(trade), [trade])
|
||||
|
||||
return (
|
||||
<Column gap={0.75}>
|
||||
<Header routes={routes} />
|
||||
<Rule />
|
||||
{routes.map((route, index) => (
|
||||
<Route key={index} route={route} />
|
||||
))}
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
43
src/lib/components/Swap/RoutingDiagram/utils.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Protocol } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
|
||||
export interface RoutingDiagramEntry {
|
||||
percent: Percent
|
||||
path: [Currency, Currency, FeeAmount][]
|
||||
protocol: Protocol
|
||||
}
|
||||
|
||||
const V2_DEFAULT_FEE_TIER = 3000
|
||||
|
||||
/**
|
||||
* Loops through all routes on a trade and returns an array of diagram entries.
|
||||
*/
|
||||
export function getTokenPath(trade: InterfaceTrade<Currency, Currency, TradeType>): RoutingDiagramEntry[] {
|
||||
return trade.swaps.map(({ route: { path: tokenPath, pools, protocol }, inputAmount, outputAmount }) => {
|
||||
const portion =
|
||||
trade.tradeType === TradeType.EXACT_INPUT
|
||||
? inputAmount.divide(trade.inputAmount)
|
||||
: outputAmount.divide(trade.outputAmount)
|
||||
const percent = new Percent(portion.numerator, portion.denominator)
|
||||
const path: RoutingDiagramEntry['path'] = []
|
||||
for (let i = 0; i < pools.length; i++) {
|
||||
const nextPool = pools[i]
|
||||
const tokenIn = tokenPath[i]
|
||||
const tokenOut = tokenPath[i + 1]
|
||||
const entry: RoutingDiagramEntry['path'][0] = [
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
nextPool instanceof Pair ? V2_DEFAULT_FEE_TIER : nextPool.fee,
|
||||
]
|
||||
path.push(entry)
|
||||
}
|
||||
return {
|
||||
percent,
|
||||
path,
|
||||
protocol,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useAtom } from 'jotai'
|
||||
import { Check, LargeIcon } from 'lib/icons'
|
||||
import { maxSlippageAtom } from 'lib/state/settings'
|
||||
import Popover from 'lib/components/Popover'
|
||||
import { useTooltip } from 'lib/components/Tooltip'
|
||||
import { getSlippageWarning, toPercent } from 'lib/hooks/useAllowedSlippage'
|
||||
import { AlertTriangle, Check, Icon, LargeIcon, XOctagon } from 'lib/icons'
|
||||
import { autoSlippageAtom, maxSlippageAtom } from 'lib/state/settings'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
import { PropsWithChildren, useCallback, useRef, useState } from 'react'
|
||||
import { forwardRef, memo, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
import { BaseButton, TextButton } from '../../Button'
|
||||
import Column from '../../Column'
|
||||
@@ -15,6 +17,10 @@ import { Label, optionCss } from './components'
|
||||
const tooltip = (
|
||||
<Trans>Your transaction will revert if the price changes unfavorably by more than this percentage.</Trans>
|
||||
)
|
||||
const highSlippage = <Trans>High slippage increases the risk of price movement</Trans>
|
||||
const invalidSlippage = <Trans>Please enter a valid slippage %</Trans>
|
||||
|
||||
const placeholder = '0.10'
|
||||
|
||||
const Button = styled(TextButton)<{ selected: boolean }>`
|
||||
${({ selected }) => optionCss(selected)}
|
||||
@@ -23,7 +29,6 @@ const Button = styled(TextButton)<{ selected: boolean }>`
|
||||
const Custom = styled(BaseButton)<{ selected: boolean }>`
|
||||
${({ selected }) => optionCss(selected)}
|
||||
${inputCss}
|
||||
border-color: ${({ selected, theme }) => (selected ? theme.active : 'transparent')} !important;
|
||||
padding: calc(0.75em - 3px) 0.625em;
|
||||
`
|
||||
|
||||
@@ -31,55 +36,113 @@ interface OptionProps {
|
||||
wrapper: typeof Button | typeof Custom
|
||||
selected: boolean
|
||||
onSelect: () => void
|
||||
icon?: ReactNode
|
||||
tabIndex?: number
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
function Option({ wrapper: Wrapper, children, selected, onSelect }: PropsWithChildren<OptionProps>) {
|
||||
const Option = forwardRef<HTMLButtonElement, OptionProps>(function Option(
|
||||
{ wrapper: Wrapper, children, selected, onSelect, icon, tabIndex }: OptionProps,
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<Wrapper selected={selected} onClick={onSelect}>
|
||||
<Wrapper selected={selected} onClick={onSelect} ref={ref} tabIndex={tabIndex}>
|
||||
<Row gap={0.5}>
|
||||
{children}
|
||||
<span style={{ width: '1.2em' }}>{selected && <LargeIcon icon={Check} />}</span>
|
||||
{icon ? icon : <LargeIcon icon={selected ? Check : undefined} size={1.25} />}
|
||||
</Row>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const Warning = memo(function Warning({ state, showTooltip }: { state?: 'warning' | 'error'; showTooltip: boolean }) {
|
||||
let icon: Icon | undefined
|
||||
let content: ReactNode
|
||||
let show = showTooltip
|
||||
switch (state) {
|
||||
case 'error':
|
||||
icon = XOctagon
|
||||
content = invalidSlippage
|
||||
show = true
|
||||
break
|
||||
case 'warning':
|
||||
icon = AlertTriangle
|
||||
content = highSlippage
|
||||
break
|
||||
}
|
||||
return (
|
||||
<Popover
|
||||
key={state}
|
||||
content={<ThemedText.Caption>{content}</ThemedText.Caption>}
|
||||
show={show}
|
||||
placement="top"
|
||||
offset={16}
|
||||
contained
|
||||
>
|
||||
<LargeIcon icon={icon} color={state} size={1.25} />
|
||||
</Popover>
|
||||
)
|
||||
})
|
||||
|
||||
export default function MaxSlippageSelect() {
|
||||
const [autoSlippage, setAutoSlippage] = useAtom(autoSlippageAtom)
|
||||
const [maxSlippage, setMaxSlippage] = useAtom(maxSlippageAtom)
|
||||
const maxSlippageInput = useMemo(() => maxSlippage?.toString() || '', [maxSlippage])
|
||||
|
||||
const option = useRef<HTMLButtonElement>(null)
|
||||
const showTooltip = useTooltip(option.current)
|
||||
|
||||
const [custom, setCustom] = useState('')
|
||||
const input = useRef<HTMLInputElement>(null)
|
||||
const focus = useCallback(() => input.current?.focus(), [input])
|
||||
|
||||
const onInputChange = useCallback(
|
||||
(custom: string) => {
|
||||
setCustom(custom)
|
||||
const numerator = Math.floor(+custom * 100)
|
||||
if (numerator) {
|
||||
setMaxSlippage(new Percent(numerator, 10_000))
|
||||
} else {
|
||||
setMaxSlippage('auto')
|
||||
}
|
||||
},
|
||||
[setMaxSlippage]
|
||||
)
|
||||
const [warning, setWarning] = useState<'warning' | 'error' | undefined>(getSlippageWarning(toPercent(maxSlippage)))
|
||||
useEffect(() => {
|
||||
setWarning(getSlippageWarning(toPercent(maxSlippage)))
|
||||
}, [maxSlippage])
|
||||
|
||||
const onInputSelect = useCallback(() => {
|
||||
focus()
|
||||
onInputChange(custom)
|
||||
}, [custom, focus, onInputChange])
|
||||
const percent = toPercent(maxSlippage)
|
||||
const warning = getSlippageWarning(percent)
|
||||
setAutoSlippage(!percent || warning === 'error')
|
||||
}, [focus, maxSlippage, setAutoSlippage])
|
||||
|
||||
const processValue = useCallback(
|
||||
(value: number | undefined) => {
|
||||
const percent = toPercent(value)
|
||||
const warning = getSlippageWarning(percent)
|
||||
setMaxSlippage(value)
|
||||
setAutoSlippage(!percent || warning === 'error')
|
||||
},
|
||||
[setAutoSlippage, setMaxSlippage]
|
||||
)
|
||||
|
||||
return (
|
||||
<Column gap={0.75}>
|
||||
<Label name={<Trans>Max slippage</Trans>} tooltip={tooltip} />
|
||||
<Row gap={0.5} grow="last">
|
||||
<Option wrapper={Button} selected={maxSlippage === 'auto'} onSelect={() => setMaxSlippage('auto')}>
|
||||
<Option wrapper={Button} selected={autoSlippage} onSelect={() => setAutoSlippage(true)}>
|
||||
<ThemedText.ButtonMedium>
|
||||
<Trans>Auto</Trans>
|
||||
</ThemedText.ButtonMedium>
|
||||
</Option>
|
||||
<Option wrapper={Custom} onSelect={onInputSelect} selected={maxSlippage !== 'auto'}>
|
||||
<Row>
|
||||
<DecimalInput value={custom} onChange={onInputChange} placeholder={t`Custom`} ref={input} />%
|
||||
<Option
|
||||
wrapper={Custom}
|
||||
selected={!autoSlippage}
|
||||
onSelect={onInputSelect}
|
||||
icon={warning && <Warning state={warning} showTooltip={showTooltip} />}
|
||||
ref={option}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Row color={warning === 'error' ? 'error' : undefined}>
|
||||
<DecimalInput
|
||||
size={Math.max(maxSlippageInput.length, 4)}
|
||||
value={maxSlippageInput}
|
||||
onChange={(input) => processValue(+input)}
|
||||
placeholder={placeholder}
|
||||
ref={input}
|
||||
/>
|
||||
%
|
||||
</Row>
|
||||
</Option>
|
||||
</Row>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useAtom } from 'jotai'
|
||||
import { TRANSACTION_TTL_DEFAULT, transactionTtlAtom } from 'lib/state/settings'
|
||||
import { useDefaultTransactionTtl, useTransactionTtl } from 'lib/hooks/useTransactionDeadline'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
import { useRef } from 'react'
|
||||
|
||||
@@ -16,17 +15,20 @@ const Input = styled(Row)`
|
||||
`
|
||||
|
||||
export default function TransactionTtlInput() {
|
||||
const [transactionTtl, setTransactionTtl] = useAtom(transactionTtlAtom)
|
||||
const [ttl, setTtl] = useTransactionTtl()
|
||||
const defaultTtl = useDefaultTransactionTtl()
|
||||
const placeholder = defaultTtl.toString()
|
||||
const input = useRef<HTMLInputElement>(null)
|
||||
return (
|
||||
<Column gap={0.75}>
|
||||
<Label name={<Trans>Transaction deadline</Trans>} tooltip={tooltip} />
|
||||
<ThemedText.Body1>
|
||||
<Input onClick={() => input.current?.focus()}>
|
||||
<Input justify="start" onClick={() => input.current?.focus()}>
|
||||
<IntegerInput
|
||||
placeholder={TRANSACTION_TTL_DEFAULT.toString()}
|
||||
value={transactionTtl?.toString() ?? ''}
|
||||
onChange={(value) => setTransactionTtl(value ? parseFloat(value) : 0)}
|
||||
placeholder={placeholder}
|
||||
value={ttl?.toString() ?? ''}
|
||||
onChange={(value) => setTtl(value ? parseFloat(value) : 0)}
|
||||
size={Math.max(ttl?.toString().length || 0, placeholder.length)}
|
||||
ref={input}
|
||||
/>
|
||||
<Trans>minutes</Trans>
|
||||
|
||||
@@ -14,9 +14,17 @@ export const optionCss = (selected: boolean) => css`
|
||||
grid-gap: 0.25em;
|
||||
padding: calc(0.75em - 1px) 0.625em;
|
||||
|
||||
:enabled {
|
||||
border: 1px solid ${({ theme }) => (selected ? theme.active : theme.outline)};
|
||||
}
|
||||
|
||||
:enabled:hover {
|
||||
border-color: ${({ theme }) => theme.onHover(selected ? theme.active : theme.outline)};
|
||||
}
|
||||
|
||||
:enabled:focus-within {
|
||||
border-color: ${({ theme }) => theme.active};
|
||||
}
|
||||
`
|
||||
|
||||
export function value(Value: AnyStyledComponent) {
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import ErrorDialog, { StatusHeader } from 'lib/components/Error/ErrorDialog'
|
||||
import EtherscanLink from 'lib/components/EtherscanLink'
|
||||
import SwapSummary from 'lib/components/Swap/Summary'
|
||||
import useInterval from 'lib/hooks/useInterval'
|
||||
import { CheckCircle, Clock, Spinner } from 'lib/icons'
|
||||
import { SwapTransactionInfo, Transaction } from 'lib/state/transactions'
|
||||
import { SwapTransactionInfo, Transaction, TransactionType, WrapTransactionInfo } from 'lib/state/transactions'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
import ms from 'ms.macro'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { ExplorerDataType } from 'utils/getExplorerLink'
|
||||
|
||||
import ActionButton from '../../ActionButton'
|
||||
import Column from '../../Column'
|
||||
import Row from '../../Row'
|
||||
import Summary from '../Summary'
|
||||
|
||||
const errorMessage = (
|
||||
<Trans>
|
||||
@@ -23,18 +26,13 @@ const TransactionRow = styled(Row)`
|
||||
flex-direction: row-reverse;
|
||||
`
|
||||
|
||||
function ElapsedTime({ tx }: { tx: Transaction<SwapTransactionInfo> }) {
|
||||
type PendingTransaction = Transaction<SwapTransactionInfo | WrapTransactionInfo>
|
||||
|
||||
function ElapsedTime({ tx }: { tx: PendingTransaction }) {
|
||||
const [elapsedMs, setElapsedMs] = useState(0)
|
||||
useInterval(
|
||||
() => {
|
||||
if (tx.info.response.timestamp) {
|
||||
setElapsedMs(tx.info.response.timestamp - tx.addedTime)
|
||||
} else {
|
||||
setElapsedMs(Date.now() - tx.addedTime)
|
||||
}
|
||||
},
|
||||
elapsedMs === tx.info.response.timestamp ? null : 1000
|
||||
)
|
||||
|
||||
useInterval(() => setElapsedMs(Date.now() - tx.addedTime), tx.receipt ? null : ms`1s`)
|
||||
|
||||
const toElapsedTime = useCallback((ms: number) => {
|
||||
let sec = Math.floor(ms / 1000)
|
||||
const min = Math.floor(sec / 60)
|
||||
@@ -57,13 +55,8 @@ function ElapsedTime({ tx }: { tx: Transaction<SwapTransactionInfo> }) {
|
||||
)
|
||||
}
|
||||
|
||||
const EtherscanA = styled.a`
|
||||
color: ${({ theme }) => theme.accent};
|
||||
text-decoration: none;
|
||||
`
|
||||
|
||||
interface TransactionStatusProps {
|
||||
tx: Transaction<SwapTransactionInfo>
|
||||
tx: PendingTransaction
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
@@ -72,19 +65,30 @@ function TransactionStatus({ tx, onClose }: TransactionStatusProps) {
|
||||
return tx.receipt?.status ? CheckCircle : Spinner
|
||||
}, [tx.receipt?.status])
|
||||
const heading = useMemo(() => {
|
||||
return tx.receipt?.status ? <Trans>Transaction submitted</Trans> : <Trans>Transaction pending</Trans>
|
||||
}, [tx.receipt?.status])
|
||||
if (tx.info.type === TransactionType.SWAP) {
|
||||
return tx.receipt?.status ? <Trans>Swap confirmed</Trans> : <Trans>Swap pending</Trans>
|
||||
} else if (tx.info.type === TransactionType.WRAP) {
|
||||
if (tx.info.unwrapped) {
|
||||
return tx.receipt?.status ? <Trans>Unwrap confirmed</Trans> : <Trans>Unwrap pending</Trans>
|
||||
}
|
||||
return tx.receipt?.status ? <Trans>Wrap confirmed</Trans> : <Trans>Wrap pending</Trans>
|
||||
}
|
||||
return tx.receipt?.status ? <Trans>Transaction confirmed</Trans> : <Trans>Transaction pending</Trans>
|
||||
}, [tx.info, tx.receipt?.status])
|
||||
|
||||
return (
|
||||
<Column flex padded gap={0.75} align="stretch" style={{ height: '100%' }}>
|
||||
<StatusHeader icon={Icon} iconColor={tx.receipt?.status ? 'success' : undefined}>
|
||||
<ThemedText.Subhead1>{heading}</ThemedText.Subhead1>
|
||||
<Summary input={tx.info.inputCurrencyAmount} output={tx.info.outputCurrencyAmount} />
|
||||
{tx.info.type === TransactionType.SWAP ? (
|
||||
<SwapSummary input={tx.info.inputCurrencyAmount} output={tx.info.outputCurrencyAmount} />
|
||||
) : null}
|
||||
</StatusHeader>
|
||||
<TransactionRow flex>
|
||||
<ThemedText.ButtonSmall>
|
||||
<EtherscanA href="//etherscan.io" target="_blank">
|
||||
<EtherscanLink type={ExplorerDataType.TRANSACTION} data={tx.info.response.hash}>
|
||||
<Trans>View on Etherscan</Trans>
|
||||
</EtherscanA>
|
||||
</EtherscanLink>
|
||||
</ThemedText.ButtonSmall>
|
||||
<ElapsedTime tx={tx} />
|
||||
</TransactionRow>
|
||||
@@ -101,7 +105,7 @@ export default function TransactionStatusDialog({ tx, onClose }: TransactionStat
|
||||
header={errorMessage}
|
||||
error={new Error('TODO(zzmp)')}
|
||||
action={<Trans>Dismiss</Trans>}
|
||||
onAction={onClose}
|
||||
onClick={onClose}
|
||||
/>
|
||||
) : (
|
||||
<TransactionStatus tx={tx} onClose={onClose} />
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
import { t } from '@lingui/macro'
|
||||
import { useLingui } from '@lingui/react'
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { useAtom } from 'jotai'
|
||||
import { integratorFeeAtom } from 'lib/state/settings'
|
||||
import { ThemedText } from 'lib/theme'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { getSlippageWarning } from 'lib/hooks/useAllowedSlippage'
|
||||
import { feeOptionsAtom } from 'lib/state/swap'
|
||||
import styled, { Color, ThemedText } from 'lib/theme'
|
||||
import { useMemo } from 'react'
|
||||
import { currencyId } from 'utils/currencyId'
|
||||
import { computeRealizedLPFeePercent } from 'utils/prices'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
import { computeRealizedLPFeeAmount, computeRealizedPriceImpact, getPriceImpactWarning } from 'utils/prices'
|
||||
|
||||
import Row from '../../Row'
|
||||
|
||||
const Value = styled.span<{ color?: Color }>`
|
||||
color: ${({ color, theme }) => color && theme[color]};
|
||||
white-space: nowrap;
|
||||
`
|
||||
|
||||
interface DetailProps {
|
||||
label: string
|
||||
value: string
|
||||
color?: Color
|
||||
}
|
||||
|
||||
function Detail({ label, value }: DetailProps) {
|
||||
function Detail({ label, value, color }: DetailProps) {
|
||||
return (
|
||||
<ThemedText.Caption>
|
||||
<ThemedText.Caption userSelect>
|
||||
<Row gap={2}>
|
||||
<span>{label}</span>
|
||||
<span style={{ whiteSpace: 'nowrap' }}>{value}</span>
|
||||
<Value color={color}>{value}</Value>
|
||||
</Row>
|
||||
</ThemedText.Caption>
|
||||
)
|
||||
@@ -35,38 +44,61 @@ export default function Details({ trade, allowedSlippage }: DetailsProps) {
|
||||
const { inputAmount, outputAmount } = trade
|
||||
const inputCurrency = inputAmount.currency
|
||||
const outputCurrency = outputAmount.currency
|
||||
|
||||
const integrator = window.location.hostname
|
||||
const [integratorFee] = useAtom(integratorFeeAtom)
|
||||
const feeOptions = useAtomValue(feeOptionsAtom)
|
||||
const priceImpact = useMemo(() => computeRealizedPriceImpact(trade), [trade])
|
||||
const lpFeeAmount = useMemo(() => computeRealizedLPFeeAmount(trade), [trade])
|
||||
const { i18n } = useLingui()
|
||||
|
||||
const priceImpact = useMemo(() => {
|
||||
const realizedLpFeePercent = computeRealizedLPFeePercent(trade)
|
||||
return trade.priceImpact.subtract(realizedLpFeePercent)
|
||||
}, [trade])
|
||||
|
||||
const details = useMemo((): [string, string][] => {
|
||||
const details = useMemo(() => {
|
||||
const rows: Array<[string, string] | [string, string, Color | undefined]> = []
|
||||
// @TODO(ianlapham): Check that provider fee is even a valid list item
|
||||
return [
|
||||
// [t`Liquidity provider fee`, `${swap.lpFee} ${inputSymbol}`],
|
||||
[t`${integrator} fee`, integratorFee && `${integratorFee} ${currencyId(inputCurrency)}`],
|
||||
[t`Price impact`, `${priceImpact.toFixed(2)}%`],
|
||||
trade.tradeType === TradeType.EXACT_INPUT
|
||||
? [t`Maximum sent`, `${trade.maximumAmountIn(allowedSlippage).toSignificant(6)} ${inputCurrency.symbol}`]
|
||||
: [],
|
||||
trade.tradeType === TradeType.EXACT_OUTPUT
|
||||
? [t`Minimum received`, `${trade.minimumAmountOut(allowedSlippage).toSignificant(6)} ${outputCurrency.symbol}`]
|
||||
: [],
|
||||
[t`Slippage tolerance`, `${allowedSlippage.toFixed(2)}%`],
|
||||
].filter(isDetail)
|
||||
|
||||
function isDetail(detail: unknown[]): detail is [string, string] {
|
||||
return Boolean(detail[1])
|
||||
if (feeOptions) {
|
||||
const fee = outputAmount.multiply(feeOptions.fee)
|
||||
if (fee.greaterThan(0)) {
|
||||
const parsedFee = formatCurrencyAmount(fee, 6, i18n.locale)
|
||||
rows.push([t`${integrator} fee`, `${parsedFee} ${outputCurrency.symbol || currencyId(outputCurrency)}`])
|
||||
}
|
||||
}
|
||||
}, [allowedSlippage, inputCurrency, integrator, integratorFee, outputCurrency.symbol, priceImpact, trade])
|
||||
|
||||
rows.push([t`Price impact`, `${priceImpact.toFixed(2)}%`, getPriceImpactWarning(priceImpact)])
|
||||
|
||||
if (lpFeeAmount) {
|
||||
const parsedLpFee = formatCurrencyAmount(lpFeeAmount, 6, i18n.locale)
|
||||
rows.push([t`Liquidity provider fee`, `${parsedLpFee} ${inputCurrency.symbol || currencyId(inputCurrency)}`])
|
||||
}
|
||||
|
||||
if (trade.tradeType === TradeType.EXACT_OUTPUT) {
|
||||
const localizedMaxSent = formatCurrencyAmount(trade.maximumAmountIn(allowedSlippage), 6, i18n.locale)
|
||||
rows.push([t`Maximum sent`, `${localizedMaxSent} ${inputCurrency.symbol}`])
|
||||
}
|
||||
|
||||
if (trade.tradeType === TradeType.EXACT_INPUT) {
|
||||
const localizedMaxSent = formatCurrencyAmount(trade.minimumAmountOut(allowedSlippage), 6, i18n.locale)
|
||||
rows.push([t`Minimum received`, `${localizedMaxSent} ${outputCurrency.symbol}`])
|
||||
}
|
||||
|
||||
rows.push([t`Slippage tolerance`, `${allowedSlippage.toFixed(2)}%`, getSlippageWarning(allowedSlippage)])
|
||||
|
||||
return rows
|
||||
}, [
|
||||
feeOptions,
|
||||
priceImpact,
|
||||
lpFeeAmount,
|
||||
trade,
|
||||
allowedSlippage,
|
||||
outputAmount,
|
||||
i18n.locale,
|
||||
integrator,
|
||||
outputCurrency,
|
||||
inputCurrency,
|
||||
])
|
||||
|
||||
return (
|
||||
<>
|
||||
{details.map(([label, detail]) => (
|
||||
<Detail key={label} label={label} value={detail} />
|
||||
{details.map(([label, detail, color]) => (
|
||||
<Detail key={label} label={label} value={detail} color={color} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,49 +1,42 @@
|
||||
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
|
||||
import { useUSDCValue } from 'hooks/useUSDCPrice'
|
||||
import { useLingui } from '@lingui/react'
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import useUSDCPriceImpact, { toHumanReadablePriceImpact } from 'lib/hooks/useUSDCPriceImpact'
|
||||
import { ArrowRight } from 'lib/icons'
|
||||
import styled from 'lib/theme'
|
||||
import { ThemedText } from 'lib/theme'
|
||||
import { useMemo } from 'react'
|
||||
import { computeFiatValuePriceImpact } from 'utils/computeFiatValuePriceImpact'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
import { getPriceImpactWarning } from 'utils/prices'
|
||||
|
||||
import Column from '../../Column'
|
||||
import Row from '../../Row'
|
||||
import TokenImg from '../../TokenImg'
|
||||
|
||||
const Percent = styled.span<{ gain: boolean }>`
|
||||
color: ${({ gain, theme }) => (gain ? theme.success : theme.error)};
|
||||
`
|
||||
|
||||
interface TokenValueProps {
|
||||
input: CurrencyAmount<Currency>
|
||||
usdc?: boolean
|
||||
change?: number
|
||||
usdc?: CurrencyAmount<Token>
|
||||
priceImpact?: Percent
|
||||
}
|
||||
|
||||
function TokenValue({ input, usdc, change }: TokenValueProps) {
|
||||
const percent = useMemo(() => {
|
||||
if (change) {
|
||||
const percent = (change * 100).toPrecision(3)
|
||||
return change > 0 ? `(+${percent}%)` : `(${percent}%)`
|
||||
}
|
||||
return undefined
|
||||
}, [change])
|
||||
|
||||
const usdcAmount = useUSDCValue(input)
|
||||
|
||||
function TokenValue({ input, usdc, priceImpact }: TokenValueProps) {
|
||||
const { i18n } = useLingui()
|
||||
const priceImpactWarning = useMemo(() => getPriceImpactWarning(priceImpact), [priceImpact])
|
||||
return (
|
||||
<Column justify="flex-start">
|
||||
<Row gap={0.375} justify="flex-start">
|
||||
<TokenImg token={input.currency} />
|
||||
<ThemedText.Body2>
|
||||
{input.toSignificant(6)} {input.currency.symbol}
|
||||
<ThemedText.Body2 userSelect>
|
||||
{formatCurrencyAmount(input, 6, i18n.locale)} {input.currency.symbol}
|
||||
</ThemedText.Body2>
|
||||
</Row>
|
||||
{usdc && usdcAmount && (
|
||||
{usdc && (
|
||||
<Row justify="flex-start">
|
||||
<ThemedText.Caption color="secondary">
|
||||
${usdcAmount.toFixed(2)}
|
||||
{change && <Percent gain={change > 0}> {percent}</Percent>}
|
||||
<ThemedText.Caption color="secondary" userSelect>
|
||||
${formatCurrencyAmount(usdc, 2, i18n.locale)}
|
||||
{priceImpact && (
|
||||
<ThemedText.Caption color={priceImpactWarning}>
|
||||
({toHumanReadablePriceImpact(priceImpact)})
|
||||
</ThemedText.Caption>
|
||||
)}
|
||||
</ThemedText.Caption>
|
||||
</Row>
|
||||
)}
|
||||
@@ -54,23 +47,17 @@ function TokenValue({ input, usdc, change }: TokenValueProps) {
|
||||
interface SummaryProps {
|
||||
input: CurrencyAmount<Currency>
|
||||
output: CurrencyAmount<Currency>
|
||||
usdc?: boolean
|
||||
showUSDC?: true
|
||||
}
|
||||
|
||||
export default function Summary({ input, output, usdc }: SummaryProps) {
|
||||
const inputUSDCValue = useUSDCValue(input)
|
||||
const outputUSDCValue = useUSDCValue(output)
|
||||
|
||||
const priceImpact = useMemo(() => {
|
||||
const computedChange = computeFiatValuePriceImpact(inputUSDCValue, outputUSDCValue)
|
||||
return computedChange ? parseFloat(computedChange.multiply(-1)?.toSignificant(3)) : undefined
|
||||
}, [inputUSDCValue, outputUSDCValue])
|
||||
export default function Summary({ input, output, showUSDC }: SummaryProps) {
|
||||
const { inputUSDC, outputUSDC, priceImpact } = useUSDCPriceImpact(input, output)
|
||||
|
||||
return (
|
||||
<Row gap={usdc ? 1 : 0.25}>
|
||||
<TokenValue input={input} usdc={usdc} />
|
||||
<Row gap={showUSDC ? 1 : 0.25}>
|
||||
<TokenValue input={input} usdc={showUSDC && inputUSDC} />
|
||||
<ArrowRight />
|
||||
<TokenValue input={output} usdc={usdc} change={priceImpact} />
|
||||
<TokenValue input={output} usdc={showUSDC && outputUSDC} priceImpact={priceImpact} />
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useLingui } from '@lingui/react'
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { IconButton } from 'lib/components/Button'
|
||||
import { useSwapTradeType } from 'lib/hooks/swap'
|
||||
import { getSlippageWarning } from 'lib/hooks/useAllowedSlippage'
|
||||
import useScrollbar from 'lib/hooks/useScrollbar'
|
||||
import { Expando, Info } from 'lib/icons'
|
||||
import { Field, independentFieldAtom } from 'lib/state/swap'
|
||||
import { AlertTriangle, BarChart, Expando, Info } from 'lib/icons'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
import formatLocaleNumber from 'lib/utils/formatLocaleNumber'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { formatCurrencyAmount, formatPrice } from 'utils/formatCurrencyAmount'
|
||||
import { computeRealizedPriceImpact, getPriceImpactWarning } from 'utils/prices'
|
||||
import { tradeMeaningfullyDiffers } from 'utils/tradeMeaningFullyDiffer'
|
||||
|
||||
import ActionButton from '../../ActionButton'
|
||||
import ActionButton, { Action } from '../../ActionButton'
|
||||
import Column from '../../Column'
|
||||
import { Header } from '../../Dialog'
|
||||
import Row from '../../Row'
|
||||
@@ -37,13 +41,14 @@ const Body = styled(Column)<{ open: boolean }>`
|
||||
transition: flex-grow 0.25s;
|
||||
|
||||
${DetailsColumn} {
|
||||
flex-basis: ${({ open }) => (open ? 7 : 0)}em;
|
||||
flex-basis: ${({ open }) => (open ? 7.5 : 0)}em;
|
||||
overflow-y: hidden;
|
||||
position: relative;
|
||||
transition: flex-basis 0.25s;
|
||||
|
||||
${Column} {
|
||||
height: 100%;
|
||||
height: 7.5em;
|
||||
grid-template-rows: repeat(auto-fill, 1em);
|
||||
padding: ${({ open }) => (open ? '0.5em 0' : 0)};
|
||||
transition: padding 0.25s;
|
||||
|
||||
@@ -61,6 +66,7 @@ const Body = styled(Column)<{ open: boolean }>`
|
||||
|
||||
${Estimate} {
|
||||
max-height: ${({ open }) => (open ? 0 : 56 / 12)}em; // 2 * line-height + padding
|
||||
min-height: 0;
|
||||
overflow-y: hidden;
|
||||
padding: ${({ open }) => (open ? 0 : '1em 0')};
|
||||
transition: ${({ open }) =>
|
||||
@@ -71,8 +77,6 @@ const Body = styled(Column)<{ open: boolean }>`
|
||||
}
|
||||
`
|
||||
|
||||
const priceUpdate = { message: <Trans>Price updated</Trans>, action: <Trans>Accept</Trans> }
|
||||
|
||||
interface SummaryDialogProps {
|
||||
trade: Trade<Currency, Currency, TradeType>
|
||||
allowedSlippage: Percent
|
||||
@@ -80,23 +84,46 @@ interface SummaryDialogProps {
|
||||
}
|
||||
|
||||
export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDialogProps) {
|
||||
const { inputAmount, outputAmount } = trade
|
||||
const { inputAmount, outputAmount, executionPrice } = trade
|
||||
const inputCurrency = inputAmount.currency
|
||||
const outputCurrency = outputAmount.currency
|
||||
const price = trade.executionPrice
|
||||
const priceImpact = useMemo(() => computeRealizedPriceImpact(trade), [trade])
|
||||
const tradeType = useSwapTradeType()
|
||||
const { i18n } = useLingui()
|
||||
|
||||
const independentField = useAtomValue(independentFieldAtom)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [details, setDetails] = useState<HTMLDivElement | null>(null)
|
||||
const scrollbar = useScrollbar(details)
|
||||
|
||||
const warning = useMemo(() => {
|
||||
return getPriceImpactWarning(priceImpact) || getSlippageWarning(allowedSlippage)
|
||||
}, [allowedSlippage, priceImpact])
|
||||
|
||||
const [ackPriceImpact, setAckPriceImpact] = useState(false)
|
||||
|
||||
const [confirmedTrade, setConfirmedTrade] = useState(trade)
|
||||
const doesTradeDiffer = useMemo(
|
||||
() => Boolean(trade && confirmedTrade && tradeMeaningfullyDiffers(trade, confirmedTrade)),
|
||||
[confirmedTrade, trade]
|
||||
)
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const [details, setDetails] = useState<HTMLDivElement | null>(null)
|
||||
|
||||
const scrollbar = useScrollbar(details)
|
||||
const action = useMemo((): Action | undefined => {
|
||||
if (doesTradeDiffer) {
|
||||
return {
|
||||
message: <Trans>Price updated</Trans>,
|
||||
icon: BarChart,
|
||||
onClick: () => setConfirmedTrade(trade),
|
||||
children: <Trans>Accept</Trans>,
|
||||
}
|
||||
} else if (getPriceImpactWarning(priceImpact) === 'error' && !ackPriceImpact) {
|
||||
return {
|
||||
message: <Trans>High price impact</Trans>,
|
||||
onClick: () => setAckPriceImpact(true),
|
||||
children: <Trans>Acknowledge</Trans>,
|
||||
}
|
||||
}
|
||||
return
|
||||
}, [ackPriceImpact, doesTradeDiffer, priceImpact, trade])
|
||||
|
||||
if (!(inputAmount && outputAmount && inputCurrency && outputCurrency)) {
|
||||
return null
|
||||
@@ -107,15 +134,18 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
|
||||
<Header title={<Trans>Swap summary</Trans>} ruled />
|
||||
<Body flex align="stretch" gap={0.75} padded open={open}>
|
||||
<SummaryColumn gap={0.75} flex justify="center">
|
||||
<Summary input={inputAmount} output={outputAmount} usdc={true} />
|
||||
<ThemedText.Caption>
|
||||
1 {inputCurrency.symbol} = {price?.toSignificant(6)} {outputCurrency.symbol}
|
||||
</ThemedText.Caption>
|
||||
<Summary input={inputAmount} output={outputAmount} showUSDC />
|
||||
<Row>
|
||||
<ThemedText.Caption userSelect>
|
||||
{formatLocaleNumber({ number: 1, sigFigs: 1, locale: i18n.locale })} {inputCurrency.symbol} ={' '}
|
||||
{formatPrice(executionPrice, 6, i18n.locale)} {outputCurrency.symbol}
|
||||
</ThemedText.Caption>
|
||||
</Row>
|
||||
</SummaryColumn>
|
||||
<Rule />
|
||||
<Row>
|
||||
<Row gap={0.5}>
|
||||
<Info color="secondary" />
|
||||
{warning ? <AlertTriangle color={warning} /> : <Info color="secondary" />}
|
||||
<ThemedText.Subhead2 color="secondary">
|
||||
<Trans>Swap details</Trans>
|
||||
</ThemedText.Subhead2>
|
||||
@@ -130,25 +160,22 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
|
||||
</Column>
|
||||
</DetailsColumn>
|
||||
<Estimate color="secondary">
|
||||
<Trans>Output is estimated.</Trans>
|
||||
{independentField === Field.INPUT && (
|
||||
<Trans>Output is estimated.</Trans>{' '}
|
||||
{tradeType === TradeType.EXACT_INPUT && (
|
||||
<Trans>
|
||||
You will send at most {trade.maximumAmountIn(allowedSlippage).toSignificant(6)} {inputCurrency.symbol}{' '}
|
||||
You will receive at least{' '}
|
||||
{formatCurrencyAmount(trade.minimumAmountOut(allowedSlippage), 6, i18n.locale)} {outputCurrency.symbol}{' '}
|
||||
or the transaction will revert.
|
||||
</Trans>
|
||||
)}
|
||||
{independentField === Field.OUTPUT && (
|
||||
{tradeType === TradeType.EXACT_OUTPUT && (
|
||||
<Trans>
|
||||
You will receive at least {trade.minimumAmountOut(allowedSlippage).toSignificant(6)}{' '}
|
||||
{outputCurrency.symbol} or the transaction will revert.
|
||||
You will send at most {formatCurrencyAmount(trade.maximumAmountIn(allowedSlippage), 6, i18n.locale)}{' '}
|
||||
{inputCurrency.symbol} or the transaction will revert.
|
||||
</Trans>
|
||||
)}
|
||||
</Estimate>
|
||||
<ActionButton
|
||||
onClick={onConfirm}
|
||||
onUpdate={() => setConfirmedTrade(trade)}
|
||||
update={doesTradeDiffer ? priceUpdate : undefined}
|
||||
>
|
||||
<ActionButton onClick={onConfirm} action={action}>
|
||||
<Trans>Confirm swap</Trans>
|
||||
</ActionButton>
|
||||
</ExpandoColumn>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { tokens } from '@uniswap/default-token-list'
|
||||
import { DAI, USDC } from 'constants/tokens'
|
||||
import { DAI, USDC_MAINNET } from 'constants/tokens'
|
||||
import { useUpdateAtom } from 'jotai/utils'
|
||||
import { useEffect } from 'react'
|
||||
import { useSelect, useValue } from 'react-cosmos/fixture'
|
||||
@@ -25,38 +25,48 @@ function Fixture() {
|
||||
}
|
||||
}, [color, setColor])
|
||||
|
||||
const optionsToAddressMap: Record<string, string> = {
|
||||
none: '',
|
||||
const [convenienceFee] = useValue('convenienceFee', { defaultValue: 100 })
|
||||
const FEE_RECIPIENT_OPTIONS = [
|
||||
'',
|
||||
'0x1D9Cd50Dde9C19073B81303b3d930444d11552f7',
|
||||
'0x0dA5533d5a9aA08c1792Ef2B6a7444E149cCB0AD',
|
||||
'0xE6abE059E5e929fd17bef158902E73f0FEaCD68c',
|
||||
]
|
||||
const [convenienceFeeRecipient] = useSelect('convenienceFeeRecipient', {
|
||||
options: FEE_RECIPIENT_OPTIONS,
|
||||
defaultValue: FEE_RECIPIENT_OPTIONS[1],
|
||||
})
|
||||
|
||||
const optionsToAddressMap: Record<string, string | undefined> = {
|
||||
None: undefined,
|
||||
Native: 'NATIVE',
|
||||
DAI: DAI.address,
|
||||
USDC: USDC.address,
|
||||
USDC: USDC_MAINNET.address,
|
||||
}
|
||||
const addressOptions = Object.keys(optionsToAddressMap)
|
||||
const [defaultInput] = useSelect('defaultInputAddress', {
|
||||
options: addressOptions,
|
||||
defaultValue: addressOptions[2],
|
||||
})
|
||||
const inputOptions = ['', '0', '100', '-1']
|
||||
const [defaultInputAmount] = useSelect('defaultInputAmount', {
|
||||
options: inputOptions,
|
||||
defaultValue: inputOptions[2],
|
||||
})
|
||||
const [defaultOutput] = useSelect('defaultOutputAddress', {
|
||||
|
||||
const [defaultInputToken] = useSelect('defaultInputToken', {
|
||||
options: addressOptions,
|
||||
defaultValue: addressOptions[1],
|
||||
})
|
||||
const [defaultOutputAmount] = useSelect('defaultOutputAmount', {
|
||||
options: inputOptions,
|
||||
defaultValue: inputOptions[0],
|
||||
const [defaultInputAmount] = useValue('defaultInputAmount', { defaultValue: 1 })
|
||||
|
||||
const [defaultOutputToken] = useSelect('defaultOutputToken', {
|
||||
options: addressOptions,
|
||||
defaultValue: addressOptions[2],
|
||||
})
|
||||
const [defaultOutputAmount] = useValue('defaultOutputAmount', { defaultValue: 0 })
|
||||
|
||||
return (
|
||||
<Swap
|
||||
tokenList={tokens}
|
||||
defaultInputAddress={optionsToAddressMap[defaultInput]}
|
||||
convenienceFee={convenienceFee}
|
||||
convenienceFeeRecipient={convenienceFeeRecipient}
|
||||
defaultInputTokenAddress={optionsToAddressMap[defaultInputToken]}
|
||||
defaultInputAmount={defaultInputAmount}
|
||||
defaultOutputAddress={optionsToAddressMap[defaultOutput]}
|
||||
defaultOutputTokenAddress={optionsToAddressMap[defaultOutputToken]}
|
||||
defaultOutputAmount={defaultOutputAmount}
|
||||
tokenList={tokens}
|
||||
onConnectWallet={() => console.log('onConnectWallet')} // this handler is included as a test of functionality, but only logs
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,41 +1,36 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { CHAIN_INFO } from 'constants/chainInfo'
|
||||
import { useERC20PermitFromTrade } from 'hooks/useERC20Permit'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { useUpdateAtom } from 'jotai/utils'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { useSwapInfo } from 'lib/hooks/swap'
|
||||
import useSwapApproval, {
|
||||
ApprovalState,
|
||||
import { WrapErrorText } from 'lib/components/Swap/WrapErrorText'
|
||||
import { useSwapCurrencyAmount, useSwapInfo, useSwapTradeType } from 'lib/hooks/swap'
|
||||
import {
|
||||
ApproveOrPermitState,
|
||||
useApproveOrPermit,
|
||||
useSwapApprovalOptimizedTrade,
|
||||
useSwapRouterAddress,
|
||||
} from 'lib/hooks/swap/useSwapApproval'
|
||||
import { useSwapCallback } from 'lib/hooks/swap/useSwapCallback'
|
||||
import { useAddTransaction } from 'lib/hooks/transactions'
|
||||
import { usePendingApproval } from 'lib/hooks/transactions'
|
||||
import useWrapCallback, { WrapError, WrapType } from 'lib/hooks/swap/useWrapCallback'
|
||||
import { useAddTransaction, usePendingApproval } from 'lib/hooks/transactions'
|
||||
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
|
||||
import useTransactionDeadline from 'lib/hooks/useTransactionDeadline'
|
||||
import { Link, Spinner } from 'lib/icons'
|
||||
import { displayTxHashAtom, Field, independentFieldAtom } from 'lib/state/swap'
|
||||
import { Spinner } from 'lib/icons'
|
||||
import { displayTxHashAtom, Field } from 'lib/state/swap'
|
||||
import { TransactionType } from 'lib/state/transactions'
|
||||
import styled from 'lib/theme'
|
||||
import { useTheme } from 'lib/theme'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import invariant from 'tiny-invariant'
|
||||
import { ExplorerDataType } from 'utils/getExplorerLink'
|
||||
|
||||
import ActionButton from '../ActionButton'
|
||||
import ActionButton, { ActionButtonProps } from '../ActionButton'
|
||||
import Dialog from '../Dialog'
|
||||
import Row from '../Row'
|
||||
import EtherscanLink from '../EtherscanLink'
|
||||
import { SummaryDialog } from './Summary'
|
||||
|
||||
interface SwapButtonProps {
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const EtherscanA = styled.a`
|
||||
color: currentColor;
|
||||
text-decoration: none;
|
||||
`
|
||||
|
||||
function useIsPendingApproval(token?: Token, spender?: string): boolean {
|
||||
return Boolean(usePendingApproval(token, spender))
|
||||
}
|
||||
@@ -43,87 +38,125 @@ function useIsPendingApproval(token?: Token, spender?: string): boolean {
|
||||
export default function SwapButton({ disabled }: SwapButtonProps) {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
|
||||
const { tokenColorExtraction } = useTheme()
|
||||
|
||||
const {
|
||||
trade,
|
||||
allowedSlippage,
|
||||
currencies: { [Field.INPUT]: inputCurrency },
|
||||
currencyBalances: { [Field.INPUT]: inputCurrencyBalance },
|
||||
currencyAmounts: { [Field.INPUT]: inputCurrencyAmount, [Field.OUTPUT]: outputCurrencyAmount },
|
||||
feeOptions,
|
||||
trade,
|
||||
tradeCurrencyAmounts: { [Field.INPUT]: inputTradeCurrencyAmount, [Field.OUTPUT]: outputTradeCurrencyAmount },
|
||||
} = useSwapInfo()
|
||||
|
||||
const independentField = useAtomValue(independentFieldAtom)
|
||||
const tradeType = useSwapTradeType()
|
||||
|
||||
const [activeTrade, setActiveTrade] = useState<typeof trade.trade | undefined>()
|
||||
useEffect(() => {
|
||||
setActiveTrade((activeTrade) => activeTrade && trade.trade)
|
||||
}, [trade])
|
||||
|
||||
// clear active trade on chain change
|
||||
useEffect(() => {
|
||||
setActiveTrade(undefined)
|
||||
}, [chainId])
|
||||
|
||||
// TODO(zzmp): Return an optimized trade directly from useSwapInfo.
|
||||
const optimizedTrade =
|
||||
// Use trade.trade if there is no swap optimized trade. This occurs if approvals are still pending.
|
||||
useSwapApprovalOptimizedTrade(trade.trade, allowedSlippage, useIsPendingApproval) || trade.trade
|
||||
const [approval, getApproval] = useSwapApproval(optimizedTrade, allowedSlippage, useIsPendingApproval)
|
||||
|
||||
const approvalCurrencyAmount = useSwapCurrencyAmount(Field.INPUT)
|
||||
|
||||
const { approvalState, signatureData, handleApproveOrPermit } = useApproveOrPermit(
|
||||
optimizedTrade,
|
||||
allowedSlippage,
|
||||
useIsPendingApproval,
|
||||
approvalCurrencyAmount
|
||||
)
|
||||
|
||||
const approvalHash = usePendingApproval(
|
||||
inputCurrency?.isToken ? inputCurrency : undefined,
|
||||
useSwapRouterAddress(optimizedTrade)
|
||||
)
|
||||
|
||||
const addTransaction = useAddTransaction()
|
||||
const addApprovalTransaction = useCallback(() => {
|
||||
getApproval().then((transaction) => {
|
||||
const onApprove = useCallback(() => {
|
||||
handleApproveOrPermit().then((transaction) => {
|
||||
if (transaction) {
|
||||
addTransaction({ type: TransactionType.APPROVAL, ...transaction })
|
||||
}
|
||||
})
|
||||
}, [addTransaction, getApproval])
|
||||
}, [addTransaction, handleApproveOrPermit])
|
||||
|
||||
const actionProps = useMemo(() => {
|
||||
if (disabled) return { disabled: true }
|
||||
const { type: wrapType, callback: wrapCallback, error: wrapError, loading: wrapLoading } = useWrapCallback()
|
||||
|
||||
if (chainId && inputCurrencyAmount && inputCurrencyBalance?.greaterThan(inputCurrencyAmount)) {
|
||||
if (approval === ApprovalState.PENDING) {
|
||||
return {
|
||||
disabled: true,
|
||||
update: {
|
||||
message: (
|
||||
<EtherscanA href={approvalHash && `${CHAIN_INFO[chainId].explorer}tx/${approvalHash}`} target="_blank">
|
||||
<Row gap={0.25}>
|
||||
<Trans>
|
||||
Approval pending <Link />
|
||||
</Trans>
|
||||
</Row>
|
||||
</EtherscanA>
|
||||
),
|
||||
action: <Trans>Approve</Trans>,
|
||||
icon: Spinner,
|
||||
},
|
||||
}
|
||||
} else if (approval === ApprovalState.NOT_APPROVED) {
|
||||
return {
|
||||
update: {
|
||||
message: <Trans>Approve {inputCurrencyAmount.currency.symbol} first</Trans>,
|
||||
action: <Trans>Approve</Trans>,
|
||||
},
|
||||
}
|
||||
}
|
||||
return {}
|
||||
const disableSwap = useMemo(
|
||||
() =>
|
||||
disabled ||
|
||||
!chainId ||
|
||||
wrapLoading ||
|
||||
(wrapType !== WrapType.NOT_APPLICABLE && wrapError) ||
|
||||
approvalState === ApproveOrPermitState.PENDING_SIGNATURE ||
|
||||
!(inputTradeCurrencyAmount && inputCurrencyBalance) ||
|
||||
inputCurrencyBalance.lessThan(inputTradeCurrencyAmount),
|
||||
[disabled, chainId, wrapLoading, wrapType, wrapError, approvalState, inputTradeCurrencyAmount, inputCurrencyBalance]
|
||||
)
|
||||
|
||||
const actionProps = useMemo((): Partial<ActionButtonProps> | undefined => {
|
||||
if (disableSwap) {
|
||||
return { disabled: true }
|
||||
}
|
||||
if (
|
||||
wrapType === WrapType.NOT_APPLICABLE &&
|
||||
(approvalState === ApproveOrPermitState.REQUIRES_APPROVAL ||
|
||||
approvalState === ApproveOrPermitState.REQUIRES_SIGNATURE)
|
||||
) {
|
||||
const currency = inputCurrency || approvalCurrencyAmount?.currency
|
||||
invariant(currency)
|
||||
return {
|
||||
action: {
|
||||
message:
|
||||
approvalState === ApproveOrPermitState.REQUIRES_SIGNATURE ? (
|
||||
<Trans>Allow {currency.symbol} first</Trans>
|
||||
) : (
|
||||
<Trans>Approve {currency.symbol} first</Trans>
|
||||
),
|
||||
onClick: onApprove,
|
||||
children:
|
||||
approvalState === ApproveOrPermitState.REQUIRES_SIGNATURE ? <Trans>Allow</Trans> : <Trans>Approve</Trans>,
|
||||
},
|
||||
}
|
||||
}
|
||||
if (approvalState === ApproveOrPermitState.PENDING_APPROVAL) {
|
||||
return {
|
||||
disabled: true,
|
||||
action: {
|
||||
message: (
|
||||
<EtherscanLink type={ExplorerDataType.TRANSACTION} data={approvalHash}>
|
||||
<Trans>Approval pending</Trans>
|
||||
</EtherscanLink>
|
||||
),
|
||||
icon: Spinner,
|
||||
onClick: () => void 0, // @TODO: should not require an onclick
|
||||
children: <Trans>Approve</Trans>,
|
||||
},
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}, [approvalCurrencyAmount?.currency, approvalHash, approvalState, disableSwap, inputCurrency, onApprove, wrapType])
|
||||
|
||||
return { disabled: true }
|
||||
}, [approval, approvalHash, chainId, disabled, inputCurrencyAmount, inputCurrencyBalance])
|
||||
|
||||
// @TODO(ianlapham): connect deadline from state instead of passing undefined.
|
||||
const { signatureData } = useERC20PermitFromTrade(optimizedTrade, allowedSlippage, undefined)
|
||||
const deadline = useTransactionDeadline()
|
||||
|
||||
// the callback to execute the swap
|
||||
const { callback: swapCallback } = useSwapCallback(
|
||||
optimizedTrade,
|
||||
const { callback: swapCallback } = useSwapCallback({
|
||||
trade: optimizedTrade,
|
||||
allowedSlippage,
|
||||
account ?? null,
|
||||
recipientAddressOrName: account ?? null,
|
||||
signatureData,
|
||||
deadline
|
||||
)
|
||||
deadline,
|
||||
feeOptions,
|
||||
})
|
||||
|
||||
//@TODO(ianlapham): add a loading state, process errors
|
||||
const setDisplayTxHash = useUpdateAtom(displayTxHashAtom)
|
||||
@@ -132,33 +165,70 @@ export default function SwapButton({ disabled }: SwapButtonProps) {
|
||||
swapCallback?.()
|
||||
.then((response) => {
|
||||
setDisplayTxHash(response.hash)
|
||||
invariant(inputCurrencyAmount && outputCurrencyAmount)
|
||||
invariant(inputTradeCurrencyAmount && outputTradeCurrencyAmount)
|
||||
addTransaction({
|
||||
response,
|
||||
type: TransactionType.SWAP,
|
||||
tradeType: independentField === Field.INPUT ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
|
||||
inputCurrencyAmount,
|
||||
outputCurrencyAmount,
|
||||
tradeType,
|
||||
inputCurrencyAmount: inputTradeCurrencyAmount,
|
||||
outputCurrencyAmount: outputTradeCurrencyAmount,
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
//@TODO(ianlapham): add error handling
|
||||
console.log(error)
|
||||
})
|
||||
}, [addTransaction, independentField, inputCurrencyAmount, outputCurrencyAmount, setDisplayTxHash, swapCallback])
|
||||
.finally(() => {
|
||||
setActiveTrade(undefined)
|
||||
})
|
||||
}, [addTransaction, inputTradeCurrencyAmount, outputTradeCurrencyAmount, setDisplayTxHash, swapCallback, tradeType])
|
||||
|
||||
const ButtonText = useCallback(() => {
|
||||
if (wrapError !== WrapError.NO_ERROR) {
|
||||
return <WrapErrorText wrapError={wrapError} />
|
||||
}
|
||||
switch (wrapType) {
|
||||
case WrapType.UNWRAP:
|
||||
return <Trans>Unwrap</Trans>
|
||||
case WrapType.WRAP:
|
||||
return <Trans>Wrap</Trans>
|
||||
case WrapType.NOT_APPLICABLE:
|
||||
default:
|
||||
return <Trans>Review swap</Trans>
|
||||
}
|
||||
}, [wrapError, wrapType])
|
||||
|
||||
const handleDialogClose = useCallback(() => {
|
||||
setActiveTrade(undefined)
|
||||
}, [])
|
||||
|
||||
const handleActionButtonClick = useCallback(async () => {
|
||||
if (wrapType === WrapType.NOT_APPLICABLE) {
|
||||
setActiveTrade(trade.trade)
|
||||
} else {
|
||||
const transaction = await wrapCallback()
|
||||
addTransaction({
|
||||
response: transaction,
|
||||
type: TransactionType.WRAP,
|
||||
unwrapped: wrapType === WrapType.UNWRAP,
|
||||
currencyAmountRaw: transaction.value?.toString() ?? '0',
|
||||
chainId,
|
||||
})
|
||||
setDisplayTxHash(transaction.hash)
|
||||
}
|
||||
}, [addTransaction, chainId, setDisplayTxHash, trade.trade, wrapCallback, wrapType])
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActionButton
|
||||
color="interactive"
|
||||
onClick={() => setActiveTrade(trade.trade)}
|
||||
onUpdate={addApprovalTransaction}
|
||||
color={tokenColorExtraction ? 'interactive' : 'accent'}
|
||||
onClick={handleActionButtonClick}
|
||||
{...actionProps}
|
||||
>
|
||||
<Trans>Review swap</Trans>
|
||||
<ButtonText />
|
||||
</ActionButton>
|
||||
{activeTrade && (
|
||||
<Dialog color="dialog" onClose={() => setActiveTrade(undefined)}>
|
||||
<Dialog color="dialog" onClose={handleDialogClose}>
|
||||
<SummaryDialog trade={activeTrade} allowedSlippage={allowedSlippage} onConfirm={onConfirm} />
|
||||
</Dialog>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { DefaultAddress, SwapProps } from 'lib/components/Swap'
|
||||
import { IntegrationError } from 'lib/errors'
|
||||
import { FeeOptions } from 'lib/hooks/swap/useSyncConvenienceFee'
|
||||
import { DefaultAddress, TokenDefaults } from 'lib/hooks/swap/useSyncTokenDefaults'
|
||||
import { PropsWithChildren, useEffect } from 'react'
|
||||
|
||||
import { isAddress } from '../../../utils'
|
||||
@@ -15,7 +16,7 @@ function isAddressOrAddressMap(addressOrMap: DefaultAddress): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
type ValidatorProps = PropsWithChildren<SwapProps>
|
||||
type ValidatorProps = PropsWithChildren<TokenDefaults & FeeOptions>
|
||||
|
||||
export default function SwapPropValidator(props: ValidatorProps) {
|
||||
const { convenienceFee, convenienceFeeRecipient } = props
|
||||
@@ -47,7 +48,7 @@ export default function SwapPropValidator(props: ValidatorProps) {
|
||||
}
|
||||
}, [convenienceFee, convenienceFeeRecipient])
|
||||
|
||||
const { defaultInputAddress, defaultInputAmount, defaultOutputAddress, defaultOutputAmount } = props
|
||||
const { defaultInputTokenAddress, defaultInputAmount, defaultOutputTokenAddress, defaultOutputAmount } = props
|
||||
useEffect(() => {
|
||||
if (defaultOutputAmount && defaultInputAmount) {
|
||||
throw new IntegrationError('defaultInputAmount and defaultOutputAmount may not both be defined.')
|
||||
@@ -60,17 +61,25 @@ export default function SwapPropValidator(props: ValidatorProps) {
|
||||
`defaultOutputAmount must be a positive number. (You set it to ${defaultOutputAmount})`
|
||||
)
|
||||
}
|
||||
if (defaultInputAddress && !isAddressOrAddressMap(defaultInputAddress) && defaultInputAddress !== 'NATIVE') {
|
||||
if (
|
||||
defaultInputTokenAddress &&
|
||||
!isAddressOrAddressMap(defaultInputTokenAddress) &&
|
||||
defaultInputTokenAddress !== 'NATIVE'
|
||||
) {
|
||||
throw new IntegrationError(
|
||||
`defaultInputAddress(es) must be a valid address or "NATIVE". (You set it to ${defaultInputAddress}`
|
||||
`defaultInputTokenAddress(es) must be a valid address or "NATIVE". (You set it to ${defaultInputTokenAddress}`
|
||||
)
|
||||
}
|
||||
if (defaultOutputAddress && !isAddressOrAddressMap(defaultOutputAddress) && defaultOutputAddress !== 'NATIVE') {
|
||||
if (
|
||||
defaultOutputTokenAddress &&
|
||||
!isAddressOrAddressMap(defaultOutputTokenAddress) &&
|
||||
defaultOutputTokenAddress !== 'NATIVE'
|
||||
) {
|
||||
throw new IntegrationError(
|
||||
`defaultOutputAddress(es) must be a valid address or "NATIVE". (You set it to ${defaultOutputAddress}`
|
||||
`defaultOutputTokenAddress(es) must be a valid address or "NATIVE". (You set it to ${defaultOutputTokenAddress}`
|
||||
)
|
||||
}
|
||||
}, [defaultInputAddress, defaultInputAmount, defaultOutputAddress, defaultOutputAmount])
|
||||
}, [defaultInputTokenAddress, defaultInputAmount, defaultOutputTokenAddress, defaultOutputAmount])
|
||||
|
||||
return <>{props.children}</>
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'setimmediate'
|
||||
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { loadingTransitionCss } from 'lib/css/loading'
|
||||
import styled, { keyframes, ThemedText } from 'lib/theme'
|
||||
import { FocusEvent, ReactNode, useCallback, useRef, useState } from 'react'
|
||||
import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
import Button from '../Button'
|
||||
import Column from '../Column'
|
||||
@@ -15,6 +18,7 @@ const TokenInputRow = styled(Row)`
|
||||
|
||||
const ValueInput = styled(DecimalInput)`
|
||||
color: ${({ theme }) => theme.primary};
|
||||
height: 1em;
|
||||
|
||||
:hover:not(:focus-within) {
|
||||
color: ${({ theme }) => theme.onHover(theme.primary)};
|
||||
@@ -23,6 +27,8 @@ const ValueInput = styled(DecimalInput)`
|
||||
:hover:not(:focus-within)::placeholder {
|
||||
color: ${({ theme }) => theme.onHover(theme.secondary)};
|
||||
}
|
||||
|
||||
${loadingTransitionCss}
|
||||
`
|
||||
|
||||
const delayedFadeIn = keyframes`
|
||||
@@ -46,49 +52,75 @@ const MaxButton = styled(Button)`
|
||||
interface TokenInputProps {
|
||||
currency?: Currency
|
||||
amount: string
|
||||
max?: string
|
||||
disabled?: boolean
|
||||
onMax?: () => void
|
||||
onChangeInput: (input: string) => void
|
||||
onChangeCurrency: (currency: Currency) => void
|
||||
loading?: boolean
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default function TokenInput({
|
||||
currency,
|
||||
amount,
|
||||
max,
|
||||
disabled,
|
||||
onMax,
|
||||
onChangeInput,
|
||||
onChangeCurrency,
|
||||
loading,
|
||||
children,
|
||||
}: TokenInputProps) {
|
||||
const max = useRef<HTMLButtonElement>(null)
|
||||
const [showMax, setShowMax] = useState(false)
|
||||
const onFocus = useCallback(() => setShowMax(Boolean(onMax)), [onMax])
|
||||
const onBlur = useCallback((e: FocusEvent) => {
|
||||
if (e.relatedTarget !== max.current) {
|
||||
const input = useRef<HTMLInputElement>(null)
|
||||
const onSelect = useCallback(
|
||||
(currency: Currency) => {
|
||||
onChangeCurrency(currency)
|
||||
setImmediate(() => input.current?.focus())
|
||||
},
|
||||
[onChangeCurrency]
|
||||
)
|
||||
|
||||
const maxButton = useRef<HTMLButtonElement>(null)
|
||||
const hasMax = useMemo(() => Boolean(max && max !== amount), [max, amount])
|
||||
const [showMax, setShowMax] = useState<boolean>(hasMax)
|
||||
useEffect(() => setShowMax((hasMax && input.current?.contains(document.activeElement)) ?? false), [hasMax])
|
||||
const onBlur = useCallback((e) => {
|
||||
// Filters out clicks on input or maxButton, because onBlur fires before onClickMax.
|
||||
if (!input.current?.contains(e.relatedTarget) && !maxButton.current?.contains(e.relatedTarget)) {
|
||||
setShowMax(false)
|
||||
}
|
||||
}, [])
|
||||
const onClickMax = useCallback(() => {
|
||||
onChangeInput(max || '')
|
||||
setShowMax(false)
|
||||
setImmediate(() => {
|
||||
input.current?.focus()
|
||||
// Brings the start of the input into view. NB: This only works for clicks, not eg keyboard interactions.
|
||||
input.current?.setSelectionRange(0, null)
|
||||
})
|
||||
}, [max, onChangeInput])
|
||||
|
||||
return (
|
||||
<Column gap={0.25}>
|
||||
<TokenInputRow gap={0.5} onBlur={onBlur}>
|
||||
<ThemedText.H2>
|
||||
<ValueInput
|
||||
value={amount}
|
||||
onFocus={onFocus}
|
||||
onFocus={() => setShowMax(hasMax)}
|
||||
onChange={onChangeInput}
|
||||
disabled={disabled || !currency}
|
||||
isLoading={Boolean(loading)}
|
||||
ref={input}
|
||||
></ValueInput>
|
||||
</ThemedText.H2>
|
||||
{showMax && (
|
||||
<MaxButton onClick={onMax} ref={max}>
|
||||
<ThemedText.ButtonMedium>
|
||||
<MaxButton onClick={onClickMax} ref={maxButton}>
|
||||
{/* Without a tab index, Safari would not populate the FocusEvent.relatedTarget needed by onBlur. */}
|
||||
<ThemedText.ButtonMedium tabIndex={-1}>
|
||||
<Trans>Max</Trans>
|
||||
</ThemedText.ButtonMedium>
|
||||
</MaxButton>
|
||||
)}
|
||||
<TokenSelect value={currency} collapsed={showMax} disabled={disabled} onSelect={onChangeCurrency} />
|
||||
<TokenSelect value={currency} collapsed={showMax} disabled={disabled} onSelect={onSelect} />
|
||||
</TokenInputRow>
|
||||
{children}
|
||||
</Column>
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { ALL_SUPPORTED_CHAIN_IDS } from 'constants/chains'
|
||||
import useUSDCPrice from 'hooks/useUSDCPrice'
|
||||
import { useSwapInfo } from 'lib/hooks/swap'
|
||||
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
|
||||
import { AlertTriangle, Info, largeIconCss, Spinner } from 'lib/icons'
|
||||
import { Field } from 'lib/state/swap'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { InterfaceTrade, TradeState } from 'state/routing/types'
|
||||
|
||||
import { TextButton } from '../Button'
|
||||
import Row from '../Row'
|
||||
import Rule from '../Rule'
|
||||
|
||||
const ToolbarRow = styled(Row)`
|
||||
padding: 0.5em 0;
|
||||
${largeIconCss}
|
||||
`
|
||||
|
||||
function RoutingTooltip() {
|
||||
return <Info color="secondary" />
|
||||
/* TODO(zzmp): Implement post-beta launch.
|
||||
return (
|
||||
<Tooltip icon={Info} placement="bottom">
|
||||
<ThemeProvider>
|
||||
<ThemedText.Subhead2>TODO: Routing Tooltip</ThemedText.Subhead2>
|
||||
</ThemeProvider>
|
||||
</Tooltip>
|
||||
)
|
||||
*/
|
||||
}
|
||||
|
||||
interface LoadedStateProps {
|
||||
inputAmount: CurrencyAmount<Currency>
|
||||
outputAmount: CurrencyAmount<Currency>
|
||||
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
|
||||
}
|
||||
|
||||
function LoadedState({ inputAmount, outputAmount, trade }: LoadedStateProps) {
|
||||
const [flip, setFlip] = useState(true)
|
||||
const executionPrice = trade?.executionPrice
|
||||
const fiatValueInput = useUSDCPrice(inputAmount.currency)
|
||||
const fiatValueOutput = useUSDCPrice(outputAmount.currency)
|
||||
|
||||
const ratio = useMemo(() => {
|
||||
const [a, b] = flip ? [outputAmount, inputAmount] : [inputAmount, outputAmount]
|
||||
|
||||
const ratio = `1 ${a.currency.symbol} = ${executionPrice?.toSignificant(6)} ${b.currency.symbol}`
|
||||
const usdc = !flip
|
||||
? fiatValueInput
|
||||
? ` ($${fiatValueInput.toSignificant(2)})`
|
||||
: null
|
||||
: fiatValueOutput
|
||||
? ` ($${fiatValueOutput.toSignificant(2)})`
|
||||
: null
|
||||
|
||||
return (
|
||||
<Row gap={0.25} style={{ userSelect: 'text' }}>
|
||||
{ratio}
|
||||
{usdc && <ThemedText.Caption color="secondary">{usdc}</ThemedText.Caption>}
|
||||
</Row>
|
||||
)
|
||||
}, [executionPrice, fiatValueInput, fiatValueOutput, flip, inputAmount, outputAmount])
|
||||
|
||||
return (
|
||||
<TextButton color="primary" onClick={() => setFlip(!flip)}>
|
||||
{ratio}
|
||||
</TextButton>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Toolbar({ disabled }: { disabled?: boolean }) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const {
|
||||
trade,
|
||||
currencies: { [Field.INPUT]: inputCurrency, [Field.OUTPUT]: outputCurency },
|
||||
currencyBalances: { [Field.INPUT]: balance },
|
||||
currencyAmounts: { [Field.INPUT]: inputAmount, [Field.OUTPUT]: outputAmount },
|
||||
} = useSwapInfo()
|
||||
|
||||
const [routeFound, routeIsLoading, routeIsSyncing] = useMemo(
|
||||
() => [Boolean(trade?.trade?.swaps), TradeState.LOADING === trade?.state, TradeState.SYNCING === trade?.state],
|
||||
[trade]
|
||||
)
|
||||
|
||||
const caption = useMemo(() => {
|
||||
if (disabled) {
|
||||
return (
|
||||
<>
|
||||
<AlertTriangle color="secondary" />
|
||||
<Trans>Connect wallet to swap</Trans>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (chainId && !ALL_SUPPORTED_CHAIN_IDS.includes(chainId)) {
|
||||
return (
|
||||
<>
|
||||
<AlertTriangle color="secondary" />
|
||||
<Trans>Unsupported network–switch to another to trade.</Trans>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (inputCurrency && outputCurency) {
|
||||
if (!trade?.trade || routeIsLoading || routeIsSyncing) {
|
||||
return (
|
||||
<>
|
||||
<Spinner color="secondary" />
|
||||
<Trans>Fetching best price…</Trans>
|
||||
</>
|
||||
)
|
||||
}
|
||||
if (inputAmount && balance && inputAmount.greaterThan(balance)) {
|
||||
return (
|
||||
<>
|
||||
<AlertTriangle color="secondary" />
|
||||
<Trans>Insufficient {inputCurrency?.symbol}</Trans>
|
||||
</>
|
||||
)
|
||||
}
|
||||
if (inputCurrency && outputCurency && !routeFound && !routeIsLoading && !routeIsSyncing) {
|
||||
return (
|
||||
<>
|
||||
<AlertTriangle color="secondary" />
|
||||
<Trans>Insufficient liquidity for this trade.</Trans>
|
||||
</>
|
||||
)
|
||||
}
|
||||
if (inputCurrency && inputAmount && outputCurency && outputAmount) {
|
||||
return (
|
||||
<>
|
||||
<RoutingTooltip />
|
||||
<LoadedState inputAmount={inputAmount} outputAmount={outputAmount} trade={trade?.trade} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Info color="secondary" />
|
||||
<Trans>Enter an amount</Trans>
|
||||
</>
|
||||
)
|
||||
}, [
|
||||
balance,
|
||||
chainId,
|
||||
disabled,
|
||||
inputAmount,
|
||||
inputCurrency,
|
||||
outputAmount,
|
||||
outputCurency,
|
||||
routeFound,
|
||||
routeIsLoading,
|
||||
routeIsSyncing,
|
||||
trade?.trade,
|
||||
])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Rule />
|
||||
<ThemedText.Caption>
|
||||
<ToolbarRow justify="flex-start" gap={0.5} iconSize={4 / 3}>
|
||||
{caption}
|
||||
</ToolbarRow>
|
||||
</ThemedText.Caption>
|
||||
</>
|
||||
)
|
||||
}
|
||||
132
src/lib/components/Swap/Toolbar/Caption.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, TradeType } from '@uniswap/sdk-core'
|
||||
import Column from 'lib/components/Column'
|
||||
import Rule from 'lib/components/Rule'
|
||||
import Tooltip from 'lib/components/Tooltip'
|
||||
import { loadingCss } from 'lib/css/loading'
|
||||
import { WrapType } from 'lib/hooks/swap/useWrapCallback'
|
||||
import useUSDCPriceImpact, { toHumanReadablePriceImpact } from 'lib/hooks/useUSDCPriceImpact'
|
||||
import { AlertTriangle, Icon, Info, InlineSpinner } from 'lib/icons'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
import { ReactNode, useCallback, useMemo, useState } from 'react'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import { getPriceImpactWarning } from 'utils/prices'
|
||||
|
||||
import { TextButton } from '../../Button'
|
||||
import Row from '../../Row'
|
||||
import RoutingDiagram from '../RoutingDiagram'
|
||||
|
||||
const Loading = styled.span`
|
||||
color: ${({ theme }) => theme.secondary};
|
||||
${loadingCss};
|
||||
`
|
||||
|
||||
interface CaptionProps {
|
||||
icon?: Icon
|
||||
caption: ReactNode
|
||||
}
|
||||
|
||||
function Caption({ icon: Icon = AlertTriangle, caption }: CaptionProps) {
|
||||
return (
|
||||
<>
|
||||
<Icon color="secondary" />
|
||||
{caption}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function ConnectWallet() {
|
||||
return <Caption caption={<Trans>Connect wallet to swap</Trans>} />
|
||||
}
|
||||
|
||||
export function UnsupportedNetwork() {
|
||||
return <Caption caption={<Trans>Unsupported network - switch to another to trade.</Trans>} />
|
||||
}
|
||||
|
||||
export function InsufficientBalance({ currency }: { currency: Currency }) {
|
||||
return <Caption caption={<Trans>Insufficient {currency?.symbol} balance</Trans>} />
|
||||
}
|
||||
|
||||
export function InsufficientLiquidity() {
|
||||
return <Caption caption={<Trans>Insufficient liquidity in the pool for your trade</Trans>} />
|
||||
}
|
||||
|
||||
export function Empty() {
|
||||
return <Caption icon={Info} caption={<Trans>Enter an amount</Trans>} />
|
||||
}
|
||||
|
||||
export function LoadingTrade() {
|
||||
return (
|
||||
<Caption
|
||||
icon={InlineSpinner}
|
||||
caption={
|
||||
<Loading>
|
||||
<Trans>Fetching best price…</Trans>
|
||||
</Loading>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function WrapCurrency({ loading, wrapType }: { loading: boolean; wrapType: WrapType.UNWRAP | WrapType.WRAP }) {
|
||||
const WrapText = useCallback(() => {
|
||||
if (wrapType === WrapType.WRAP) {
|
||||
return loading ? <Trans>Wrapping native currency.</Trans> : <Trans>Wrap native currency.</Trans>
|
||||
}
|
||||
return loading ? <Trans>Unwrapping native currency.</Trans> : <Trans>Unwrap native currency.</Trans>
|
||||
}, [loading, wrapType])
|
||||
|
||||
return <Caption icon={Info} caption={<WrapText />} />
|
||||
}
|
||||
|
||||
export function Trade({ trade }: { trade: InterfaceTrade<Currency, Currency, TradeType> }) {
|
||||
const [flip, setFlip] = useState(true)
|
||||
const { inputAmount: input, outputAmount: output, executionPrice } = trade
|
||||
const { inputUSDC, outputUSDC, priceImpact } = useUSDCPriceImpact(input, output)
|
||||
const isPriceImpactHigh = priceImpact && getPriceImpactWarning(priceImpact)
|
||||
|
||||
const ratio = useMemo(() => {
|
||||
const [a, b] = flip ? [output, input] : [input, output]
|
||||
const priceString = (!flip ? executionPrice : executionPrice?.invert())?.toSignificant(6)
|
||||
|
||||
const ratio = `1 ${a.currency.symbol} = ${priceString} ${b.currency.symbol}`
|
||||
const usdc = !flip
|
||||
? inputUSDC
|
||||
? ` ($${inputUSDC.toSignificant(6)})`
|
||||
: null
|
||||
: outputUSDC
|
||||
? ` ($${outputUSDC.toSignificant(6)})`
|
||||
: null
|
||||
|
||||
return (
|
||||
<ThemedText.Caption userSelect>
|
||||
<Row gap={0.25}>
|
||||
{ratio}
|
||||
{usdc && <ThemedText.Caption color="secondary">{usdc}</ThemedText.Caption>}
|
||||
</Row>
|
||||
</ThemedText.Caption>
|
||||
)
|
||||
}, [executionPrice, inputUSDC, outputUSDC, flip, input, output])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip placement="bottom" icon={isPriceImpactHigh ? AlertTriangle : Info}>
|
||||
<Column gap={0.75}>
|
||||
{isPriceImpactHigh && (
|
||||
<>
|
||||
<ThemedText.Caption>
|
||||
The output amount is estimated at {toHumanReadablePriceImpact(priceImpact)} less than the input amount
|
||||
due to high price impact
|
||||
</ThemedText.Caption>
|
||||
<Rule />
|
||||
</>
|
||||
)}
|
||||
<RoutingDiagram trade={trade} />
|
||||
</Column>
|
||||
</Tooltip>
|
||||
<TextButton color="primary" onClick={() => setFlip(!flip)}>
|
||||
{ratio}
|
||||
</TextButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||