Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
3
.env
@@ -1 +1,2 @@
|
||||
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||
REACT_APP_LOCALES="locales"
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -17,11 +17,10 @@
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
# builds
|
||||
/build
|
||||
|
||||
# widgets
|
||||
/dist
|
||||
/dts
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
"webpack": {
|
||||
"configPath": "react-scripts/config/webpack.config",
|
||||
"overridePath": "cosmos.override.js"
|
||||
}
|
||||
},
|
||||
"port": 5001
|
||||
}
|
||||
@@ -17,6 +17,7 @@ module.exports = (webpackConfig) => ({
|
||||
'process.env': {
|
||||
...plugin.definitions['process.env'],
|
||||
REACT_APP_IS_WIDGET: true,
|
||||
REACT_APP_LOCALES: '"../locales"',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
74
package.json
74
package.json
@@ -1,16 +1,20 @@
|
||||
{
|
||||
"name": "@uniswap/interface",
|
||||
"name": "@uniswap/widgets",
|
||||
"version": "0.0.11-beta",
|
||||
"description": "Uniswap Interface",
|
||||
"homepage": ".",
|
||||
"main": "dist/interface.js",
|
||||
"module": "dist/interface.esm.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"main": "dist/widgets.js",
|
||||
"module": "dist/widgets.esm.js",
|
||||
"types": "dist/widgets.d.ts",
|
||||
"files": [
|
||||
"lib",
|
||||
"dist"
|
||||
],
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@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",
|
||||
"@ethersproject/experimental": "^5.4.0",
|
||||
"@gnosis.pm/safe-apps-web3-react": "^0.6.0",
|
||||
"@graphql-codegen/cli": "1.21.5",
|
||||
@@ -22,10 +26,14 @@
|
||||
"@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 +68,20 @@
|
||||
"@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-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.11",
|
||||
"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,8 +102,10 @@
|
||||
"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",
|
||||
@@ -103,14 +115,17 @@
|
||||
"react-use-gesture": "^6.0.14",
|
||||
"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-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",
|
||||
@@ -132,7 +147,8 @@
|
||||
"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",
|
||||
@@ -154,6 +170,7 @@
|
||||
},
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.0",
|
||||
"@ethersproject/abi": "^5.4.1",
|
||||
"@ethersproject/abstract-provider": "^5.4.1",
|
||||
"@ethersproject/address": "^5.4.0",
|
||||
@@ -162,7 +179,7 @@
|
||||
"@ethersproject/constants": "^5.4.0",
|
||||
"@ethersproject/contracts": "^5.4.1",
|
||||
"@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",
|
||||
@@ -173,13 +190,15 @@
|
||||
"@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": "npm:@web3-react/core@^6.0.9",
|
||||
"ajv": "^6.12.3",
|
||||
"cids": "^1.0.0",
|
||||
"immer": "^9.0.6",
|
||||
@@ -192,8 +211,6 @@
|
||||
"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",
|
||||
@@ -204,12 +221,17 @@
|
||||
"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": "8.0.16-alpha.0",
|
||||
"@web3-react/eip1193": "8.0.16-alpha.0",
|
||||
"@web3-react/empty": "8.0.17-alpha.0",
|
||||
"@web3-react/metamask": "8.0.16-alpha.0",
|
||||
"@web3-react/types": "8.0.16-alpha.0",
|
||||
"@web3-react/url": "8.0.17-alpha.0",
|
||||
"wicg-inert": "^3.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": "^7.17.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
126
rollup.config.ts
126
rollup.config.ts
@@ -3,61 +3,125 @@
|
||||
* 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'
|
||||
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"',
|
||||
}
|
||||
|
||||
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
|
||||
externals({ exclude: ['constants'], deps: true, peerDeps: true }), // marks builtins, dependencies, and peerDependencies external
|
||||
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; doing so type-checking allows the json to be type-checked
|
||||
]
|
||||
|
||||
const check = {
|
||||
input: 'src/lib/index.tsx',
|
||||
output: { file: 'dist/widgets.tsc' },
|
||||
external: isAsset,
|
||||
plugins: [...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/widgets.d.ts' },
|
||||
external: isAsset,
|
||||
plugins: [
|
||||
dts({ compilerOptions: { baseUrl: 'dist/dts' } }),
|
||||
process.env.ROLLUP_WATCH ? undefined : del({ hook: 'buildEnd', targets: ['dist/widgets.tsc', 'dist/dts'] }),
|
||||
],
|
||||
}
|
||||
|
||||
const transpile = {
|
||||
input: 'src/lib/index.tsx',
|
||||
output: [
|
||||
{
|
||||
file: 'dist/widgets.js',
|
||||
format: 'cjs',
|
||||
inlineDynamicImports: true,
|
||||
sourcemap: true,
|
||||
sourcemap: false,
|
||||
},
|
||||
{
|
||||
file: 'dist/widgets.esm.js',
|
||||
format: 'esm',
|
||||
inlineDynamicImports: true,
|
||||
sourcemap: true,
|
||||
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
|
||||
...plugins,
|
||||
|
||||
// Source code transformation
|
||||
url({ include: ASSET_EXTENSIONS.map((extname) => '**/*' + extname) }), // 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({ insert: true }), // imports inlined sass styles
|
||||
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
|
||||
],
|
||||
}),
|
||||
|
||||
copy({
|
||||
copyOnce: true,
|
||||
targets: [{ src: 'src/locales/*.js', dest: 'dist/locales' }],
|
||||
}),
|
||||
],
|
||||
onwarn: squelchTypeWarnings, // this pipeline is only for transpilation
|
||||
}
|
||||
|
||||
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 = [library, typings]
|
||||
const config = [check, type, transpile]
|
||||
export default config
|
||||
|
||||
function squelchTranspilationWarnings(warning: RollupWarning, warn: (warning: RollupWarning) => void) {
|
||||
if (warning.pluginCode === 'TS5055') return
|
||||
warn(warning)
|
||||
}
|
||||
|
||||
function squelchTypeWarnings(warning: RollupWarning, warn: (warning: RollupWarning) => void) {
|
||||
if (warning.code === 'UNUSED_EXTERNAL_IMPORT') return
|
||||
warn(warning)
|
||||
}
|
||||
|
||||
@@ -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 }) {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -4,11 +4,10 @@ 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 { useURLWarningVisible } from '../../state/user/hooks'
|
||||
import { AutoColumn } from '../Column'
|
||||
import ClaimPopup from './ClaimPopup'
|
||||
import PopupItem from './PopupItem'
|
||||
import SurveyPopup from './SurveyPopup'
|
||||
|
||||
const MobilePopupWrapper = styled.div<{ height: string | number }>`
|
||||
position: relative;
|
||||
@@ -60,9 +59,6 @@ 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
|
||||
@@ -73,14 +69,12 @@ export default function Popups() {
|
||||
<>
|
||||
<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} />
|
||||
))}
|
||||
</FixedPopupColumn>
|
||||
<MobilePopupWrapper height={activePopups?.length > 0 || showSurveyPopup ? 'fit-content' : 0}>
|
||||
<MobilePopupWrapper height={activePopups?.length > 0 ? 'fit-content' : 0}>
|
||||
<MobilePopupInner>
|
||||
<SurveyPopup />
|
||||
{activePopups // reverse so new items up front
|
||||
.slice(0)
|
||||
.reverse()
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
@@ -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,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 },
|
||||
},
|
||||
}
|
||||
|
||||
23
src/constants/infura.ts
Normal file
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
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,4 @@ ReactDOM.render(
|
||||
if (process.env.REACT_APP_SERVICE_WORKER !== 'false') {
|
||||
serviceWorkerRegistration.register()
|
||||
}
|
||||
export { INFURA_NETWORK_URLS } from 'constants/chainInfo'
|
||||
export { INFURA_NETWORK_URLS } from 'constants/infura'
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
5
src/lib/assets/svg/wallet.svg
Normal file
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,44 @@ 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 Action {
|
||||
message: ReactNode
|
||||
icon?: Icon
|
||||
onClick: () => void
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export interface ActionButtonProps {
|
||||
color?: Color
|
||||
disabled?: boolean
|
||||
update?: { message: ReactNode; action: ReactNode; icon?: Icon }
|
||||
action?: Action
|
||||
onClick: () => void
|
||||
onUpdate?: () => void
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default function ActionButton({
|
||||
color = 'accent',
|
||||
disabled,
|
||||
update,
|
||||
onClick,
|
||||
onUpdate,
|
||||
children,
|
||||
}: ActionButtonProps) {
|
||||
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;
|
||||
@@ -55,10 +55,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>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -73,13 +73,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 +105,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>
|
||||
)
|
||||
|
||||
@@ -87,10 +87,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)
|
||||
@@ -123,7 +123,7 @@ export default function ErrorDialog({ header, error, action, onAction }: ErrorDi
|
||||
</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}</>
|
||||
}
|
||||
|
||||
32
src/lib/components/EtherscanLink.tsx
Normal file
32
src/lib/components/EtherscanLink.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
|
||||
import styled, { Color } from 'lib/theme'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
import ExternalLink from './ExternalLink'
|
||||
|
||||
const Link = 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 (
|
||||
<Link href={url} color={color} target="_blank">
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -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,6 +11,7 @@ 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;
|
||||
@@ -32,7 +33,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 +40,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 +81,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 +95,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 +123,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,30 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useLingui } from '@lingui/react'
|
||||
import { useUSDCValue } from 'hooks/useUSDCPrice'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { loadingOpacityCss } from 'lib/css/loading'
|
||||
import { useSwapAmount, useSwapCurrency, useSwapInfo } from 'lib/hooks/swap'
|
||||
import { usePrefetchCurrencyColor } from 'lib/hooks/useCurrencyColor'
|
||||
import { Field } from 'lib/state/swap'
|
||||
import { Field, independentFieldAtom } 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 LoadingRow = styled(Row)<{ $loading: boolean }>`
|
||||
${loadingOpacityCss};
|
||||
`
|
||||
|
||||
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,12 +35,15 @@ const InputColumn = styled(Column)<{ approved?: boolean }>`
|
||||
}
|
||||
`
|
||||
|
||||
interface InputProps {
|
||||
disabled?: boolean
|
||||
export interface InputProps {
|
||||
disabled: boolean
|
||||
focused: boolean
|
||||
}
|
||||
|
||||
export default function Input({ disabled }: InputProps) {
|
||||
export default function Input({ disabled, focused }: InputProps) {
|
||||
const { i18n } = useLingui()
|
||||
const {
|
||||
trade: { state: tradeState },
|
||||
currencyBalances: { [Field.INPUT]: balance },
|
||||
currencyAmounts: { [Field.INPUT]: inputCurrencyAmount },
|
||||
} = useSwapInfo()
|
||||
@@ -39,22 +55,25 @@ export default function Input({ disabled }: InputProps) {
|
||||
// extract eagerly in case of reversal
|
||||
usePrefetchCurrencyColor(swapInputCurrency)
|
||||
|
||||
const isRouteLoading = tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
|
||||
const isDependentField = useAtomValue(independentFieldAtom) !== 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())
|
||||
// account for gas needed if using max on native token
|
||||
const maxAmount = useMemo(() => maxAmountSpend(balance), [balance])
|
||||
|
||||
const onMax = useMemo(() => {
|
||||
if (maxAmount?.greaterThan(0)) {
|
||||
return () => updateSwapInputAmount(maxAmount.toExact())
|
||||
}
|
||||
}, [balance, updateSwapInputAmount])
|
||||
return
|
||||
}, [maxAmount, updateSwapInputAmount])
|
||||
|
||||
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)) ?? ''}
|
||||
@@ -62,14 +81,15 @@ export default function Input({ disabled }: InputProps) {
|
||||
onMax={onMax}
|
||||
onChangeInput={updateSwapInputAmount}
|
||||
onChangeCurrency={updateSwapInputCurrency}
|
||||
loading={isLoading}
|
||||
>
|
||||
<ThemedText.Body2 color="secondary">
|
||||
<Row>
|
||||
{inputUSDC ? `$${inputUSDC.toFixed(2)}` : '-'}
|
||||
<LoadingRow $loading={isLoading}>{inputUSDC ? `$${inputUSDC.toFixed(2)}` : '-'}</LoadingRow>
|
||||
{balance && (
|
||||
<ThemedText.Body2 color={inputCurrencyAmount?.greaterThan(balance) ? 'error' : undefined}>
|
||||
Balance: <span style={{ userSelect: 'text' }}>{formatCurrencyAmount(balance, 4)}</span>
|
||||
</ThemedText.Body2>
|
||||
<Balance color={inputCurrencyAmount?.greaterThan(balance) ? 'error' : undefined} focused={focused}>
|
||||
Balance: <span style={{ userSelect: 'text' }}>{formatCurrencyAmount(balance, 4, i18n.locale)}</span>
|
||||
</Balance>
|
||||
)}
|
||||
</Row>
|
||||
</ThemedText.Body2>
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useLingui } from '@lingui/react'
|
||||
import { useUSDCValue } from 'hooks/useUSDCPrice'
|
||||
import { atom } from 'jotai'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import BrandedFooter from 'lib/components/BrandedFooter'
|
||||
import { useSwapAmount, useSwapCurrency, useSwapInfo } from 'lib/hooks/swap'
|
||||
import useCurrencyColor from 'lib/hooks/useCurrencyColor'
|
||||
import { Field } from 'lib/state/swap'
|
||||
import { Field, independentFieldAtom } from 'lib/state/swap'
|
||||
import styled, { DynamicThemeProvider, ThemedText } from 'lib/theme'
|
||||
import { ReactNode, useCallback, useMemo } from 'react'
|
||||
import { PropsWithChildren, useMemo } from 'react'
|
||||
import { TradeState } from 'state/routing/types'
|
||||
import { computeFiatValuePriceImpact } from 'utils/computeFiatValuePriceImpact'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
import { getPriceImpactWarning } from 'utils/prices'
|
||||
|
||||
import Column from '../Column'
|
||||
import Row from '../Row'
|
||||
import { Balance, InputProps, LoadingRow } from './Input'
|
||||
import TokenInput from './TokenInput'
|
||||
|
||||
export const colorAtom = atom<string | undefined>(undefined)
|
||||
@@ -21,24 +25,23 @@ 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 {
|
||||
trade: { state: tradeState },
|
||||
currencyBalances: { [Field.OUTPUT]: balance },
|
||||
currencyAmounts: { [Field.INPUT]: inputCurrencyAmount, [Field.OUTPUT]: outputCurrencyAmount },
|
||||
} = useSwapInfo()
|
||||
@@ -46,6 +49,10 @@ export default function Output({ disabled, children }: OutputProps) {
|
||||
const [swapOutputAmount, updateSwapOutputAmount] = useSwapAmount(Field.OUTPUT)
|
||||
const [swapOutputCurrency, updateSwapOutputCurrency] = useSwapCurrency(Field.OUTPUT)
|
||||
|
||||
const isRouteLoading = tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
|
||||
const isDependentField = useAtomValue(independentFieldAtom) !== Field.OUTPUT
|
||||
const isLoading = isRouteLoading && isDependentField
|
||||
|
||||
const overrideColor = useAtomValue(colorAtom)
|
||||
const dynamicColor = useCurrencyColor(swapOutputCurrency)
|
||||
const color = overrideColor || dynamicColor
|
||||
@@ -57,46 +64,45 @@ export default function Output({ disabled, children }: OutputProps) {
|
||||
const outputUSDC = useUSDCValue(outputCurrencyAmount)
|
||||
|
||||
const priceImpact = useMemo(() => {
|
||||
const computedChange = computeFiatValuePriceImpact(inputUSDC, outputUSDC)
|
||||
return computedChange ? parseFloat(computedChange.multiply(-1)?.toSignificant(3)) : undefined
|
||||
const fiatValuePriceImpact = computeFiatValuePriceImpact(inputUSDC, outputUSDC)
|
||||
if (!fiatValuePriceImpact) return null
|
||||
|
||||
const color = getPriceImpactWarning(fiatValuePriceImpact)
|
||||
const sign = fiatValuePriceImpact.lessThan(0) ? '+' : ''
|
||||
const displayedPriceImpact = parseFloat(fiatValuePriceImpact.multiply(-1)?.toSignificant(3))
|
||||
return (
|
||||
<ThemedText.Body2 color={color}>
|
||||
({sign}
|
||||
{displayedPriceImpact}%)
|
||||
</ThemedText.Body2>
|
||||
)
|
||||
}, [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])
|
||||
|
||||
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)) ?? ''}
|
||||
disabled={disabled}
|
||||
onMax={onMax}
|
||||
onChangeInput={updateSwapOutputAmount}
|
||||
onChangeCurrency={updateSwapOutputCurrency}
|
||||
loading={isLoading}
|
||||
>
|
||||
<ThemedText.Body2 color="secondary">
|
||||
<Row>
|
||||
{usdc}
|
||||
<LoadingRow gap={0.5} $loading={isLoading}>
|
||||
{outputUSDC?.toFixed(2)} {priceImpact}
|
||||
</LoadingRow>
|
||||
{balance && (
|
||||
<span>
|
||||
Balance: <span style={{ userSelect: 'text' }}>{formatCurrencyAmount(balance, 4)}</span>
|
||||
</span>
|
||||
<Balance focused={focused}>
|
||||
Balance: <span style={{ userSelect: 'text' }}>{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};
|
||||
`
|
||||
|
||||
|
||||
@@ -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, 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,107 @@ 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 [warning, setWarning] = useState<'warning' | 'error' | undefined>(getSlippageWarning(toPercent(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')
|
||||
}
|
||||
const processValue = useCallback(
|
||||
(value: number | undefined) => {
|
||||
const percent = toPercent(value)
|
||||
const warning = getSlippageWarning(percent)
|
||||
setWarning(warning)
|
||||
setMaxSlippage(value)
|
||||
setAutoSlippage(!percent || warning === 'error')
|
||||
},
|
||||
[setMaxSlippage]
|
||||
[setAutoSlippage, setMaxSlippage]
|
||||
)
|
||||
const onInputSelect = useCallback(() => {
|
||||
focus()
|
||||
onInputChange(custom)
|
||||
}, [custom, focus, onInputChange])
|
||||
processValue(maxSlippage)
|
||||
}, [focus, maxSlippage, processValue])
|
||||
|
||||
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, 3)}
|
||||
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,10 +1,13 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import ErrorDialog, { StatusHeader } from 'lib/components/Error/ErrorDialog'
|
||||
import EtherscanLink from 'lib/components/EtherscanLink'
|
||||
import useInterval from 'lib/hooks/useInterval'
|
||||
import { CheckCircle, Clock, Spinner } from 'lib/icons'
|
||||
import { SwapTransactionInfo, Transaction } 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'
|
||||
@@ -25,16 +28,9 @@ const TransactionRow = styled(Row)`
|
||||
|
||||
function ElapsedTime({ tx }: { tx: Transaction<SwapTransactionInfo> }) {
|
||||
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,11 +53,6 @@ function ElapsedTime({ tx }: { tx: Transaction<SwapTransactionInfo> }) {
|
||||
)
|
||||
}
|
||||
|
||||
const EtherscanA = styled.a`
|
||||
color: ${({ theme }) => theme.accent};
|
||||
text-decoration: none;
|
||||
`
|
||||
|
||||
interface TransactionStatusProps {
|
||||
tx: Transaction<SwapTransactionInfo>
|
||||
onClose: () => void
|
||||
@@ -74,6 +65,7 @@ function TransactionStatus({ tx, onClose }: TransactionStatusProps) {
|
||||
const heading = useMemo(() => {
|
||||
return tx.receipt?.status ? <Trans>Transaction submitted</Trans> : <Trans>Transaction pending</Trans>
|
||||
}, [tx.receipt?.status])
|
||||
|
||||
return (
|
||||
<Column flex padded gap={0.75} align="stretch" style={{ height: '100%' }}>
|
||||
<StatusHeader icon={Icon} iconColor={tx.receipt?.status ? 'success' : undefined}>
|
||||
@@ -82,9 +74,9 @@ function TransactionStatus({ tx, onClose }: TransactionStatusProps) {
|
||||
</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 +93,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>
|
||||
<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,3 +1,4 @@
|
||||
import { useLingui } from '@lingui/react'
|
||||
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
|
||||
import { useUSDCValue } from 'hooks/useUSDCPrice'
|
||||
import { ArrowRight } from 'lib/icons'
|
||||
@@ -5,6 +6,7 @@ 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 Column from '../../Column'
|
||||
import Row from '../../Row'
|
||||
@@ -21,9 +23,10 @@ interface TokenValueProps {
|
||||
}
|
||||
|
||||
function TokenValue({ input, usdc, change }: TokenValueProps) {
|
||||
const { i18n } = useLingui()
|
||||
const percent = useMemo(() => {
|
||||
if (change) {
|
||||
const percent = (change * 100).toPrecision(3)
|
||||
const percent = change.toPrecision(3)
|
||||
return change > 0 ? `(+${percent}%)` : `(${percent}%)`
|
||||
}
|
||||
return undefined
|
||||
@@ -36,13 +39,13 @@ function TokenValue({ input, usdc, change }: TokenValueProps) {
|
||||
<Row gap={0.375} justify="flex-start">
|
||||
<TokenImg token={input.currency} />
|
||||
<ThemedText.Body2>
|
||||
{input.toSignificant(6)} {input.currency.symbol}
|
||||
{formatCurrencyAmount(input, 6, i18n.locale)} {input.currency.symbol}
|
||||
</ThemedText.Body2>
|
||||
</Row>
|
||||
{usdc && usdcAmount && (
|
||||
<Row justify="flex-start">
|
||||
<ThemedText.Caption color="secondary">
|
||||
${usdcAmount.toFixed(2)}
|
||||
${formatCurrencyAmount(usdcAmount, 2, i18n.locale)}
|
||||
{change && <Percent gain={change > 0}> {percent}</Percent>}
|
||||
</ThemedText.Caption>
|
||||
</Row>
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
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 { getSlippageWarning } from 'lib/hooks/useAllowedSlippage'
|
||||
import useScrollbar from 'lib/hooks/useScrollbar'
|
||||
import { Expando, Info } from 'lib/icons'
|
||||
import { AlertTriangle, BarChart, Expando, Info } from 'lib/icons'
|
||||
import { Field, independentFieldAtom } from 'lib/state/swap'
|
||||
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 +42,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 +67,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 +78,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 +85,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 independentField = useAtomValue(independentFieldAtom)
|
||||
const { i18n } = useLingui()
|
||||
|
||||
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
|
||||
@@ -109,13 +137,14 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
|
||||
<SummaryColumn gap={0.75} flex justify="center">
|
||||
<Summary input={inputAmount} output={outputAmount} usdc={true} />
|
||||
<ThemedText.Caption>
|
||||
1 {inputCurrency.symbol} = {price?.toSignificant(6)} {outputCurrency.symbol}
|
||||
{formatLocaleNumber({ number: 1, sigFigs: 1, locale: i18n.locale })} {inputCurrency.symbol} ={' '}
|
||||
{formatPrice(executionPrice, 6, i18n.locale)} {outputCurrency.symbol}
|
||||
</ThemedText.Caption>
|
||||
</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>
|
||||
@@ -133,22 +162,19 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
|
||||
<Trans>Output is estimated.</Trans>
|
||||
{independentField === Field.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 && (
|
||||
<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>
|
||||
|
||||
@@ -25,6 +25,18 @@ function Fixture() {
|
||||
}
|
||||
}, [color, setColor])
|
||||
|
||||
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> = {
|
||||
none: '',
|
||||
Native: 'NATIVE',
|
||||
@@ -52,11 +64,13 @@ function Fixture() {
|
||||
|
||||
return (
|
||||
<Swap
|
||||
tokenList={tokens}
|
||||
convenienceFee={convenienceFee}
|
||||
convenienceFeeRecipient={convenienceFeeRecipient}
|
||||
defaultInputAddress={optionsToAddressMap[defaultInput]}
|
||||
defaultInputAmount={defaultInputAmount}
|
||||
defaultOutputAddress={optionsToAddressMap[defaultOutput]}
|
||||
defaultOutputAmount={defaultOutputAmount}
|
||||
tokenList={tokens}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { CHAIN_INFO } from 'constants/chainInfo'
|
||||
import { useERC20PermitFromTrade } from 'hooks/useERC20Permit'
|
||||
import { useUpdateAtom } from 'jotai/utils'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
@@ -18,12 +17,14 @@ import useTransactionDeadline from 'lib/hooks/useTransactionDeadline'
|
||||
import { Link, Spinner } from 'lib/icons'
|
||||
import { displayTxHashAtom, Field, independentFieldAtom } 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 EtherscanLink from '../EtherscanLink'
|
||||
import Row from '../Row'
|
||||
import { SummaryDialog } from './Summary'
|
||||
|
||||
@@ -31,11 +32,6 @@ 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,12 +39,15 @@ 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,
|
||||
} = useSwapInfo()
|
||||
|
||||
const independentField = useAtomValue(independentFieldAtom)
|
||||
@@ -77,32 +76,36 @@ export default function SwapButton({ disabled }: SwapButtonProps) {
|
||||
})
|
||||
}, [addTransaction, getApproval])
|
||||
|
||||
const actionProps = useMemo(() => {
|
||||
const actionProps = useMemo((): Partial<ActionButtonProps> | undefined => {
|
||||
if (disabled) return { disabled: true }
|
||||
|
||||
if (chainId && inputCurrencyAmount && inputCurrencyBalance?.greaterThan(inputCurrencyAmount)) {
|
||||
if (approval === ApprovalState.PENDING) {
|
||||
if (chainId && inputCurrencyAmount) {
|
||||
if (!inputCurrencyBalance || inputCurrencyBalance.lessThan(inputCurrencyAmount)) {
|
||||
return { disabled: true }
|
||||
} else if (approval === ApprovalState.PENDING) {
|
||||
return {
|
||||
disabled: true,
|
||||
update: {
|
||||
action: {
|
||||
message: (
|
||||
<EtherscanA href={approvalHash && `${CHAIN_INFO[chainId].explorer}tx/${approvalHash}`} target="_blank">
|
||||
<EtherscanLink type={ExplorerDataType.TRANSACTION} data={approvalHash}>
|
||||
<Row gap={0.25}>
|
||||
<Trans>
|
||||
Approval pending <Link />
|
||||
</Trans>
|
||||
</Row>
|
||||
</EtherscanA>
|
||||
</EtherscanLink>
|
||||
),
|
||||
action: <Trans>Approve</Trans>,
|
||||
icon: Spinner,
|
||||
onClick: addApprovalTransaction,
|
||||
children: <Trans>Approve</Trans>,
|
||||
},
|
||||
}
|
||||
} else if (approval === ApprovalState.NOT_APPROVED) {
|
||||
return {
|
||||
update: {
|
||||
action: {
|
||||
message: <Trans>Approve {inputCurrencyAmount.currency.symbol} first</Trans>,
|
||||
action: <Trans>Approve</Trans>,
|
||||
onClick: addApprovalTransaction,
|
||||
children: <Trans>Approve</Trans>,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -110,20 +113,20 @@ export default function SwapButton({ disabled }: SwapButtonProps) {
|
||||
}
|
||||
|
||||
return { disabled: true }
|
||||
}, [approval, approvalHash, chainId, disabled, inputCurrencyAmount, inputCurrencyBalance])
|
||||
}, [addApprovalTransaction, 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()
|
||||
const { signatureData } = useERC20PermitFromTrade(optimizedTrade, allowedSlippage, deadline)
|
||||
|
||||
// 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)
|
||||
@@ -145,14 +148,16 @@ export default function SwapButton({ disabled }: SwapButtonProps) {
|
||||
//@TODO(ianlapham): add error handling
|
||||
console.log(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setActiveTrade(undefined)
|
||||
})
|
||||
}, [addTransaction, independentField, inputCurrencyAmount, outputCurrencyAmount, setDisplayTxHash, swapCallback])
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActionButton
|
||||
color="interactive"
|
||||
color={tokenColorExtraction ? 'interactive' : 'accent'}
|
||||
onClick={() => setActiveTrade(trade.trade)}
|
||||
onUpdate={addApprovalTransaction}
|
||||
{...actionProps}
|
||||
>
|
||||
<Trans>Review swap</Trans>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { loadingOpacityCss } from 'lib/css/loading'
|
||||
import styled, { keyframes, ThemedText } from 'lib/theme'
|
||||
import { FocusEvent, ReactNode, useCallback, useRef, useState } from 'react'
|
||||
|
||||
@@ -13,8 +14,9 @@ const TokenInputRow = styled(Row)`
|
||||
grid-template-columns: 1fr;
|
||||
`
|
||||
|
||||
const ValueInput = styled(DecimalInput)`
|
||||
const ValueInput = styled(DecimalInput)<{ $loading: boolean }>`
|
||||
color: ${({ theme }) => theme.primary};
|
||||
height: 1em;
|
||||
|
||||
:hover:not(:focus-within) {
|
||||
color: ${({ theme }) => theme.onHover(theme.primary)};
|
||||
@@ -23,6 +25,8 @@ const ValueInput = styled(DecimalInput)`
|
||||
:hover:not(:focus-within)::placeholder {
|
||||
color: ${({ theme }) => theme.onHover(theme.secondary)};
|
||||
}
|
||||
|
||||
${loadingOpacityCss}
|
||||
`
|
||||
|
||||
const delayedFadeIn = keyframes`
|
||||
@@ -50,6 +54,7 @@ interface TokenInputProps {
|
||||
onMax?: () => void
|
||||
onChangeInput: (input: string) => void
|
||||
onChangeCurrency: (currency: Currency) => void
|
||||
loading?: boolean
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
@@ -60,16 +65,27 @@ export default function TokenInput({
|
||||
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) {
|
||||
if (e.relatedTarget !== max.current && e.relatedTarget !== input.current) {
|
||||
setShowMax(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const input = useRef<HTMLInputElement>(null)
|
||||
const onSelect = useCallback(
|
||||
(currency: Currency) => {
|
||||
onChangeCurrency(currency)
|
||||
setTimeout(() => input.current?.focus(), 0)
|
||||
},
|
||||
[onChangeCurrency]
|
||||
)
|
||||
|
||||
return (
|
||||
<Column gap={0.25}>
|
||||
<TokenInputRow gap={0.5} onBlur={onBlur}>
|
||||
@@ -79,6 +95,8 @@ export default function TokenInput({
|
||||
onFocus={onFocus}
|
||||
onChange={onChangeInput}
|
||||
disabled={disabled || !currency}
|
||||
$loading={Boolean(loading)}
|
||||
ref={input}
|
||||
></ValueInput>
|
||||
</ThemedText.H2>
|
||||
{showMax && (
|
||||
@@ -88,7 +106,7 @@ export default function TokenInput({
|
||||
</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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
81
src/lib/components/Swap/Toolbar/Caption.tsx
Normal file
81
src/lib/components/Swap/Toolbar/Caption.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, TradeType } from '@uniswap/sdk-core'
|
||||
import useUSDCPrice from 'hooks/useUSDCPrice'
|
||||
import { AlertTriangle, Icon, Info, Spinner } from 'lib/icons'
|
||||
import { ThemedText } from 'lib/theme'
|
||||
import { ReactNode, useMemo, useState } from 'react'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
|
||||
import { TextButton } from '../../Button'
|
||||
import Row from '../../Row'
|
||||
import RoutingTooltip from './RoutingTooltip'
|
||||
|
||||
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={Spinner} caption={<Trans>Fetching best price…</Trans>} />
|
||||
}
|
||||
|
||||
export function Trade({ trade }: { trade: InterfaceTrade<Currency, Currency, TradeType> }) {
|
||||
const [flip, setFlip] = useState(true)
|
||||
const { inputAmount, outputAmount, executionPrice } = trade
|
||||
const fiatValueInput = useUSDCPrice(inputAmount.currency)
|
||||
const fiatValueOutput = useUSDCPrice(outputAmount.currency)
|
||||
|
||||
const ratio = useMemo(() => {
|
||||
const [a, b] = flip ? [outputAmount, inputAmount] : [inputAmount, outputAmount]
|
||||
const priceString = (!flip ? executionPrice : executionPrice?.invert())?.toSignificant(6)
|
||||
|
||||
const ratio = `1 ${a.currency.symbol} = ${priceString} ${b.currency.symbol}`
|
||||
const usdc = !flip
|
||||
? fiatValueInput
|
||||
? ` ($${fiatValueInput.toSignificant(6)})`
|
||||
: null
|
||||
: fiatValueOutput
|
||||
? ` ($${fiatValueOutput.toSignificant(6)})`
|
||||
: 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 (
|
||||
<>
|
||||
<RoutingTooltip />
|
||||
<TextButton color="primary" onClick={() => setFlip(!flip)}>
|
||||
{ratio}
|
||||
</TextButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
14
src/lib/components/Swap/Toolbar/RoutingTooltip.tsx
Normal file
14
src/lib/components/Swap/Toolbar/RoutingTooltip.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Info } from 'lib/icons'
|
||||
|
||||
export default 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>
|
||||
)
|
||||
*/
|
||||
}
|
||||
66
src/lib/components/Swap/Toolbar/index.tsx
Normal file
66
src/lib/components/Swap/Toolbar/index.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { ALL_SUPPORTED_CHAIN_IDS } from 'constants/chains'
|
||||
import { useIsAmountPopulated, useSwapInfo } from 'lib/hooks/swap'
|
||||
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
|
||||
import { largeIconCss } from 'lib/icons'
|
||||
import { Field } from 'lib/state/swap'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
import { useMemo } from 'react'
|
||||
import { TradeState } from 'state/routing/types'
|
||||
|
||||
import Row from '../../Row'
|
||||
import Rule from '../../Rule'
|
||||
import * as Caption from './Caption'
|
||||
|
||||
const ToolbarRow = styled(Row)`
|
||||
padding: 0.5em 0;
|
||||
${largeIconCss}
|
||||
`
|
||||
|
||||
export default function Toolbar({ disabled }: { disabled?: boolean }) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const {
|
||||
trade: { trade, state },
|
||||
currencies: { [Field.INPUT]: inputCurrency, [Field.OUTPUT]: outputCurrency },
|
||||
currencyBalances: { [Field.INPUT]: balance },
|
||||
} = useSwapInfo()
|
||||
const isRouteLoading = state === TradeState.SYNCING || state === TradeState.LOADING
|
||||
const isAmountPopulated = useIsAmountPopulated()
|
||||
|
||||
const caption = useMemo(() => {
|
||||
if (disabled) {
|
||||
return <Caption.ConnectWallet />
|
||||
}
|
||||
|
||||
if (chainId && !ALL_SUPPORTED_CHAIN_IDS.includes(chainId)) {
|
||||
return <Caption.UnsupportedNetwork />
|
||||
}
|
||||
|
||||
if (inputCurrency && outputCurrency && isAmountPopulated) {
|
||||
if (isRouteLoading) {
|
||||
return <Caption.LoadingTrade />
|
||||
}
|
||||
if (!trade?.swaps) {
|
||||
return <Caption.InsufficientLiquidity />
|
||||
}
|
||||
if (balance && trade?.inputAmount.greaterThan(balance)) {
|
||||
return <Caption.InsufficientBalance currency={trade.inputAmount.currency} />
|
||||
}
|
||||
if (trade.inputAmount && trade.outputAmount) {
|
||||
return <Caption.Trade trade={trade} />
|
||||
}
|
||||
}
|
||||
|
||||
return <Caption.Empty />
|
||||
}, [balance, chainId, disabled, inputCurrency, isAmountPopulated, isRouteLoading, outputCurrency, trade])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Rule />
|
||||
<ThemedText.Caption>
|
||||
<ToolbarRow justify="flex-start" gap={0.5} iconSize={4 / 3}>
|
||||
{caption}
|
||||
</ToolbarRow>
|
||||
</ThemedText.Caption>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TokenInfo } from '@uniswap/token-lists'
|
||||
import { ALL_SUPPORTED_CHAIN_IDS } from 'constants/chains'
|
||||
import { useAtom } from 'jotai'
|
||||
import useSwapDefaults from 'lib/hooks/swap/useSwapDefaults'
|
||||
import { SwapInfoUpdater } from 'lib/hooks/swap/useSwapInfo'
|
||||
import useSyncConvenienceFee from 'lib/hooks/swap/useSyncConvenienceFee'
|
||||
import useSyncSwapDefaults from 'lib/hooks/swap/useSyncSwapDefaults'
|
||||
import { usePendingTransactions } from 'lib/hooks/transactions'
|
||||
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
|
||||
import useHasFocus from 'lib/hooks/useHasFocus'
|
||||
import useTokenList from 'lib/hooks/useTokenList'
|
||||
import { displayTxHashAtom } from 'lib/state/swap'
|
||||
import { SwapTransactionInfo, Transaction, TransactionType } from 'lib/state/transactions'
|
||||
import { useState } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
import Dialog from '../Dialog'
|
||||
import Header from '../Header'
|
||||
@@ -43,31 +46,40 @@ export interface SwapProps {
|
||||
defaultOutputAmount?: string
|
||||
convenienceFee?: number
|
||||
convenienceFeeRecipient?: string | { [chainId: number]: string }
|
||||
onConnectWallet?: () => void
|
||||
}
|
||||
|
||||
export default function Swap(props: SwapProps) {
|
||||
useTokenList(props.tokenList)
|
||||
useSwapDefaults(props)
|
||||
const list = useTokenList(props.tokenList)
|
||||
useSyncSwapDefaults(props)
|
||||
useSyncConvenienceFee(props)
|
||||
|
||||
const { active, account } = useActiveWeb3React()
|
||||
const [boundary, setBoundary] = useState<HTMLDivElement | null>(null)
|
||||
const { active, account, chainId } = useActiveWeb3React()
|
||||
const [wrapper, setWrapper] = useState<HTMLDivElement | null>(null)
|
||||
|
||||
const [displayTxHash, setDisplayTxHash] = useAtom(displayTxHashAtom)
|
||||
const pendingTxs = usePendingTransactions()
|
||||
const displayTx = getSwapTx(pendingTxs, displayTxHash)
|
||||
|
||||
const onSupportedChain = useMemo(
|
||||
() => chainId && ALL_SUPPORTED_CHAIN_IDS.includes(chainId) && list.some((token) => token.chainId === chainId),
|
||||
[chainId, list]
|
||||
)
|
||||
|
||||
const focused = useHasFocus(wrapper)
|
||||
|
||||
return (
|
||||
<SwapPropValidator {...props}>
|
||||
<SwapInfoUpdater />
|
||||
{onSupportedChain && <SwapInfoUpdater />}
|
||||
<Header title={<Trans>Swap</Trans>}>
|
||||
{active && <Wallet disabled={!account} />}
|
||||
{active && <Wallet disabled={!account} onClick={props.onConnectWallet} />}
|
||||
<Settings disabled={!active} />
|
||||
</Header>
|
||||
<div ref={setBoundary}>
|
||||
<BoundaryProvider value={boundary}>
|
||||
<Input disabled={!active} />
|
||||
<div ref={setWrapper}>
|
||||
<BoundaryProvider value={wrapper}>
|
||||
<Input disabled={!active} focused={focused} />
|
||||
<ReverseButton disabled={!active} />
|
||||
<Output disabled={!active}>
|
||||
<Output disabled={!active} focused={focused}>
|
||||
<Toolbar disabled={!active} />
|
||||
<SwapButton disabled={!account} />
|
||||
</Output>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import useTokenList, { DEFAULT_TOKEN_LIST } from 'lib/hooks/useTokenList'
|
||||
import DEFAULT_TOKEN_LIST from '@uniswap/default-token-list'
|
||||
import useTokenList from 'lib/hooks/useTokenList'
|
||||
|
||||
import { Modal } from './Dialog'
|
||||
import { TokenSelectDialog } from './TokenSelect'
|
||||
|
||||
export default function Fixture() {
|
||||
useTokenList(DEFAULT_TOKEN_LIST)
|
||||
useTokenList(DEFAULT_TOKEN_LIST.tokens)
|
||||
|
||||
return (
|
||||
<Modal color="module">
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { ChevronDown } from 'lib/icons'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import Button from '../Button'
|
||||
import Row from '../Row'
|
||||
@@ -34,9 +35,11 @@ interface TokenButtonProps {
|
||||
}
|
||||
|
||||
export default function TokenButton({ value, collapsed, disabled, onClick }: TokenButtonProps) {
|
||||
const buttonBackgroundColor = useMemo(() => (value ? 'interactive' : 'accent'), [value])
|
||||
const contentColor = useMemo(() => (value || disabled ? 'onInteractive' : 'onAccent'), [value, disabled])
|
||||
return (
|
||||
<StyledTokenButton onClick={onClick} empty={!value} color={value ? 'interactive' : 'accent'} disabled={disabled}>
|
||||
<ThemedText.ButtonLarge color="onInteractive">
|
||||
<StyledTokenButton onClick={onClick} empty={!value} color={buttonBackgroundColor} disabled={disabled}>
|
||||
<ThemedText.ButtonLarge color={contentColor}>
|
||||
<TokenButtonRow gap={0.4} collapsed={Boolean(value) && collapsed}>
|
||||
{value ? (
|
||||
<>
|
||||
@@ -46,7 +49,7 @@ export default function TokenButton({ value, collapsed, disabled, onClick }: Tok
|
||||
) : (
|
||||
<Trans>Select a token</Trans>
|
||||
)}
|
||||
<ChevronDown color="onInteractive" strokeWidth={3} />
|
||||
<ChevronDown color={contentColor} strokeWidth={3} />
|
||||
</TokenButtonRow>
|
||||
</ThemedText.ButtonLarge>
|
||||
</StyledTokenButton>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useLingui } from '@lingui/react'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
|
||||
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
|
||||
@@ -19,8 +20,8 @@ import {
|
||||
} from 'react'
|
||||
import AutoSizer from 'react-virtualized-auto-sizer'
|
||||
import { areEqual, FixedSizeList, FixedSizeListProps } from 'react-window'
|
||||
import invariant from 'tiny-invariant'
|
||||
import { currencyId } from 'utils/currencyId'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
|
||||
import { BaseButton } from '../Button'
|
||||
import Column from '../Column'
|
||||
@@ -69,6 +70,7 @@ interface BubbledEvent extends SyntheticEvent {
|
||||
}
|
||||
|
||||
function TokenOption({ index, value, style }: TokenOptionProps) {
|
||||
const { i18n } = useLingui()
|
||||
const ref = useRef<HTMLButtonElement>(null)
|
||||
// Annotate the event to be handled later instead of passing in handlers to avoid rerenders.
|
||||
// This prevents token logos from reloading and flashing on the screen.
|
||||
@@ -101,7 +103,7 @@ function TokenOption({ index, value, style }: TokenOptionProps) {
|
||||
<ThemedText.Caption color="secondary">{value.name}</ThemedText.Caption>
|
||||
</Column>
|
||||
</Row>
|
||||
{balance?.greaterThan(0) && balance?.toFixed(2)}
|
||||
{balance?.greaterThan(0) && formatCurrencyAmount(balance, 2, i18n.locale)}
|
||||
</Row>
|
||||
</ThemedText.Body1>
|
||||
</TokenButton>
|
||||
@@ -137,68 +139,73 @@ const TokenOptions = forwardRef<TokenOptionsHandle, TokenOptionsProps>(function
|
||||
ref
|
||||
) {
|
||||
const [focused, setFocused] = useState(false)
|
||||
const [hover, setHover] = useState(-1)
|
||||
useEffect(() => setHover(-1), [tokens])
|
||||
const [hover, setHover] = useState<{ index: number; currency?: Currency }>({ index: -1 })
|
||||
useEffect(() => {
|
||||
setHover((hover) => {
|
||||
const index = hover.currency ? tokens.indexOf(hover.currency) : -1
|
||||
return { index, currency: tokens[index] }
|
||||
})
|
||||
}, [tokens])
|
||||
|
||||
const list = useRef<FixedSizeList>(null)
|
||||
const [element, setElement] = useState<HTMLElement | null>(null)
|
||||
const scrollTo = useCallback(
|
||||
(index: number | undefined) => {
|
||||
if (index === undefined) return
|
||||
list.current?.scrollToItem(index)
|
||||
if (focused) {
|
||||
element?.querySelector<HTMLElement>(`[data-index='${index}']`)?.focus()
|
||||
}
|
||||
setHover({ index, currency: tokens[index] })
|
||||
},
|
||||
[element, focused, tokens]
|
||||
)
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
||||
if (e.key === 'ArrowDown' && hover < tokens.length - 1) {
|
||||
scrollTo(hover + 1)
|
||||
} else if (e.key === 'ArrowUp' && hover > 0) {
|
||||
scrollTo(hover - 1)
|
||||
} else if (e.key === 'ArrowUp' && hover === -1) {
|
||||
if (e.key === 'ArrowDown' && hover.index < tokens.length - 1) {
|
||||
scrollTo(hover.index + 1)
|
||||
} else if (e.key === 'ArrowUp' && hover.index > 0) {
|
||||
scrollTo(hover.index - 1)
|
||||
} else if (e.key === 'ArrowUp' && hover.index === -1) {
|
||||
scrollTo(tokens.length - 1)
|
||||
}
|
||||
e.preventDefault()
|
||||
}
|
||||
if (e.key === 'Enter' && hover) {
|
||||
onSelect(tokens[hover])
|
||||
}
|
||||
|
||||
function scrollTo(index: number) {
|
||||
list.current?.scrollToItem(index)
|
||||
setHover(index)
|
||||
if (e.key === 'Enter' && hover.index !== -1) {
|
||||
onSelect(tokens[hover.index])
|
||||
}
|
||||
},
|
||||
[hover, onSelect, tokens]
|
||||
[hover.index, onSelect, scrollTo, tokens]
|
||||
)
|
||||
const blur = useCallback(() => setHover(-1), [])
|
||||
const blur = useCallback(() => setHover({ index: -1 }), [])
|
||||
useImperativeHandle(ref, () => ({ onKeyDown, blur }), [blur, onKeyDown])
|
||||
|
||||
const onClick = useCallback(({ token }: BubbledEvent) => token && onSelect(token), [onSelect])
|
||||
const onFocus = useCallback(({ index }: BubbledEvent) => {
|
||||
if (index !== undefined) {
|
||||
setHover(index)
|
||||
const onFocus = useCallback(
|
||||
({ index }: BubbledEvent) => {
|
||||
setFocused(true)
|
||||
}
|
||||
}, [])
|
||||
const onBlur = useCallback(() => setFocused(false), [])
|
||||
const onMouseMove = useCallback(
|
||||
({ index, ref }: BubbledEvent) => {
|
||||
if (index !== undefined) {
|
||||
setHover(index)
|
||||
if (focused) {
|
||||
ref?.focus()
|
||||
}
|
||||
}
|
||||
scrollTo(index)
|
||||
},
|
||||
[focused]
|
||||
[scrollTo]
|
||||
)
|
||||
const onBlur = useCallback(() => setFocused(false), [])
|
||||
const onMouseMove = useCallback(({ index }: BubbledEvent) => scrollTo(index), [scrollTo])
|
||||
|
||||
const [element, setElement] = useState<HTMLElement | null>(null)
|
||||
const scrollbar = useScrollbar(element, { padded: true })
|
||||
|
||||
const onHover = useRef<HTMLDivElement>(null)
|
||||
// use native onscroll handler to capture Safari's bouncy overscroll effect
|
||||
useNativeEvent(element, 'scroll', (e) => {
|
||||
invariant(element)
|
||||
if (onHover.current) {
|
||||
// must be set synchronously to avoid jank (avoiding useState)
|
||||
onHover.current.style.marginTop = `${-element.scrollTop}px`
|
||||
}
|
||||
})
|
||||
useNativeEvent(
|
||||
element,
|
||||
'scroll',
|
||||
useCallback(() => {
|
||||
if (element && onHover.current) {
|
||||
// must be set synchronously to avoid jank (avoiding useState)
|
||||
onHover.current.style.marginTop = `${-element.scrollTop}px`
|
||||
}
|
||||
}, [element])
|
||||
)
|
||||
|
||||
return (
|
||||
<Column
|
||||
@@ -212,11 +219,11 @@ const TokenOptions = forwardRef<TokenOptionsHandle, TokenOptionsProps>(function
|
||||
style={{ overflow: 'hidden' }}
|
||||
>
|
||||
{/* OnHover is a workaround to Safari's incorrect (overflow: overlay) implementation */}
|
||||
<OnHover hover={hover} ref={onHover} />
|
||||
<OnHover hover={hover.index} ref={onHover} />
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
<TokenList
|
||||
hover={hover}
|
||||
hover={hover.index}
|
||||
height={height}
|
||||
width="100%"
|
||||
itemCount={tokens.length}
|
||||
|
||||
@@ -1,40 +1,45 @@
|
||||
import { Placement } from '@popperjs/core'
|
||||
import useHasFocus from 'lib/hooks/useHasFocus'
|
||||
import useHasHover from 'lib/hooks/useHasHover'
|
||||
import { HelpCircle, Icon } from 'lib/icons'
|
||||
import styled from 'lib/theme'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import { ComponentProps, ReactNode, useRef } from 'react'
|
||||
|
||||
import { IconButton } from './Button'
|
||||
import Popover from './Popover'
|
||||
|
||||
export function useTooltip(tooltip: Node | null | undefined): boolean {
|
||||
const hover = useHasHover(tooltip)
|
||||
const focus = useHasFocus(tooltip)
|
||||
return hover || focus
|
||||
}
|
||||
|
||||
const IconTooltip = styled(IconButton)`
|
||||
:hover {
|
||||
cursor: help;
|
||||
}
|
||||
cursor: help;
|
||||
`
|
||||
|
||||
interface TooltipInterface {
|
||||
interface TooltipProps {
|
||||
icon?: Icon
|
||||
iconProps?: ComponentProps<Icon>
|
||||
children: ReactNode
|
||||
placement: Placement
|
||||
placement?: Placement
|
||||
offset?: number
|
||||
contained?: true
|
||||
}
|
||||
|
||||
export default function Tooltip({
|
||||
icon: Icon = HelpCircle,
|
||||
iconProps,
|
||||
children,
|
||||
placement = 'auto',
|
||||
offset,
|
||||
contained,
|
||||
}: TooltipInterface) {
|
||||
const [show, setShow] = useState(false)
|
||||
}: TooltipProps) {
|
||||
const tooltip = useRef<HTMLDivElement>(null)
|
||||
const showTooltip = useTooltip(tooltip.current)
|
||||
return (
|
||||
<Popover content={children} show={show} placement={placement} contained={contained}>
|
||||
<IconTooltip
|
||||
onMouseEnter={() => setShow(true)}
|
||||
onMouseLeave={() => setShow(false)}
|
||||
onFocus={() => setShow(true)}
|
||||
onBlur={() => setShow(false)}
|
||||
icon={Icon}
|
||||
/>
|
||||
<Popover content={children} show={showTooltip} placement={placement} offset={offset} contained={contained}>
|
||||
<IconTooltip icon={Icon} iconProps={iconProps} ref={tooltip} />
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
import { CreditCard } from 'lib/icons'
|
||||
import { ThemedText } from 'lib/theme'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Wallet as WalletIcon } from 'lib/icons'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
|
||||
import Row from './Row'
|
||||
|
||||
export default function Wallet({ disabled }: { disabled?: boolean }) {
|
||||
interface WalletProps {
|
||||
disabled?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const ClickableRow = styled(Row)<{ onClick?: unknown }>`
|
||||
cursor: ${({ onClick }) => onClick && 'pointer'};
|
||||
`
|
||||
|
||||
export default function Wallet({ disabled, onClick }: WalletProps) {
|
||||
return disabled ? (
|
||||
<ThemedText.Caption color="secondary">
|
||||
<Row gap={0.25}>
|
||||
<CreditCard />
|
||||
Connect wallet to swap
|
||||
</Row>
|
||||
<ClickableRow gap={0.5} onClick={onClick}>
|
||||
<WalletIcon />
|
||||
<Trans>Connect your wallet</Trans>
|
||||
</ClickableRow>
|
||||
</ThemedText.Caption>
|
||||
) : null
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { initializeConnector, Web3ReactHooks } from '@web3-react/core'
|
||||
import { EIP1193 } from '@web3-react/eip1193'
|
||||
import { Actions, Connector, Provider as EthProvider } from '@web3-react/types'
|
||||
import { Url } from '@web3-react/url'
|
||||
import { SetStateAction } from 'jotai'
|
||||
import { RESET, useUpdateAtom } from 'jotai/utils'
|
||||
import { injectedAtom, urlAtom } from 'lib/state/web3'
|
||||
import { ReactNode, useEffect } from 'react'
|
||||
import { initializeConnector, Web3ReactHooks } from 'widgets-web3-react/core'
|
||||
import { EIP1193 } from 'widgets-web3-react/eip1193'
|
||||
import { Actions, Connector, Provider as EthProvider } from 'widgets-web3-react/types'
|
||||
import { Url } from 'widgets-web3-react/url'
|
||||
|
||||
interface Web3ProviderProps {
|
||||
jsonRpcEndpoint?: string
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Provider as EthProvider } from '@web3-react/types'
|
||||
import { DEFAULT_LOCALE, SupportedLocale } from 'constants/locales'
|
||||
import { Provider as AtomProvider } from 'jotai'
|
||||
import { TransactionsUpdater } from 'lib/hooks/transactions'
|
||||
@@ -6,26 +7,14 @@ import { UNMOUNTING } from 'lib/hooks/useUnmount'
|
||||
import { Provider as I18nProvider } from 'lib/i18n'
|
||||
import { MulticallUpdater, store as multicallStore } from 'lib/state/multicall'
|
||||
import styled, { keyframes, Theme, ThemeProvider } from 'lib/theme'
|
||||
import { PropsWithChildren, StrictMode, useRef } from 'react'
|
||||
import { PropsWithChildren, StrictMode, useState } from 'react'
|
||||
import { Provider as ReduxProvider } from 'react-redux'
|
||||
import { Provider as EthProvider } from 'widgets-web3-react/types'
|
||||
|
||||
import { Provider as DialogProvider } from './Dialog'
|
||||
import { Modal, Provider as DialogProvider } from './Dialog'
|
||||
import ErrorBoundary, { ErrorHandler } from './Error/ErrorBoundary'
|
||||
import WidgetPropValidator from './Error/WidgetsPropsValidator'
|
||||
import Web3Provider from './Web3Provider'
|
||||
|
||||
const slideDown = keyframes`
|
||||
to {
|
||||
top: calc(100% - 0.25em);
|
||||
}
|
||||
`
|
||||
const slideUp = keyframes`
|
||||
from {
|
||||
top: calc(100% - 0.25em);
|
||||
}
|
||||
`
|
||||
|
||||
const WidgetWrapper = styled.div<{ width?: number | string }>`
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@@ -39,17 +28,12 @@ const WidgetWrapper = styled.div<{ width?: number | string }>`
|
||||
font-size: 16px;
|
||||
font-smooth: always;
|
||||
font-variant: none;
|
||||
height: 376px;
|
||||
height: 348px;
|
||||
min-width: 300px;
|
||||
overflow-y: hidden;
|
||||
padding: 0.25em;
|
||||
position: relative;
|
||||
width: ${({ width }) => width && (isNaN(Number(width)) ? width : `${width}px`)};
|
||||
|
||||
@supports (overflow: clip) {
|
||||
overflow-y: clip;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: ${({ theme }) => theme.fontFamily};
|
||||
@@ -59,12 +43,37 @@ const WidgetWrapper = styled.div<{ width?: number | string }>`
|
||||
font-family: ${({ theme }) => theme.fontFamilyVariable};
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
.dialog {
|
||||
const slideDown = keyframes`
|
||||
to {
|
||||
transform: translateY(calc(100% - 0.25em));
|
||||
}
|
||||
`
|
||||
const slideUp = keyframes`
|
||||
from {
|
||||
transform: translateY(calc(100% - 0.25em));
|
||||
}
|
||||
`
|
||||
|
||||
const DialogWrapper = styled.div`
|
||||
height: calc(100% - 0.5em);
|
||||
left: 0;
|
||||
margin: 0.25em;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: calc(100% - 0.5em);
|
||||
|
||||
@supports (overflow: clip) {
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
${Modal} {
|
||||
animation: ${slideUp} 0.25s ease-in-out;
|
||||
}
|
||||
|
||||
.dialog.${UNMOUNTING} {
|
||||
${Modal}.${UNMOUNTING} {
|
||||
animation: ${slideDown} 0.25s ease-in-out;
|
||||
}
|
||||
`
|
||||
@@ -98,18 +107,19 @@ export default function Widget(props: PropsWithChildren<WidgetProps>) {
|
||||
provider,
|
||||
jsonRpcEndpoint,
|
||||
width = 360,
|
||||
dialog,
|
||||
dialog: userDialog,
|
||||
className,
|
||||
onError,
|
||||
} = props
|
||||
|
||||
const wrapper = useRef<HTMLDivElement>(null)
|
||||
const [dialog, setDialog] = useState<HTMLDivElement | null>(null)
|
||||
return (
|
||||
<StrictMode>
|
||||
<I18nProvider locale={locale}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<WidgetWrapper width={width} className={className} ref={wrapper}>
|
||||
<DialogProvider value={dialog || wrapper.current}>
|
||||
<WidgetWrapper width={width} className={className}>
|
||||
<DialogWrapper ref={setDialog} />
|
||||
<DialogProvider value={userDialog || dialog}>
|
||||
<ErrorBoundary onError={onError}>
|
||||
<WidgetPropValidator {...props}>
|
||||
<ReduxProvider store={multicallStore}>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { INFURA_NETWORK_URLS } from 'constants/chainInfo'
|
||||
import { initializeConnector } from '@web3-react/core'
|
||||
import { MetaMask } from '@web3-react/metamask'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { INFURA_NETWORK_URLS } from 'constants/infura'
|
||||
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from 'constants/locales'
|
||||
import Widget from 'lib/components/Widget'
|
||||
import { darkTheme, defaultTheme, lightTheme } from 'lib/theme'
|
||||
import { ReactNode, useEffect, useMemo } from 'react'
|
||||
import { useSelect, useValue } from 'react-cosmos/fixture'
|
||||
import { initializeConnector } from 'widgets-web3-react/core'
|
||||
import { MetaMask } from 'widgets-web3-react/metamask'
|
||||
|
||||
export const [metaMask] = initializeConnector<MetaMask>((actions) => new MetaMask(actions))
|
||||
|
||||
|
||||
8
src/lib/css/loading.ts
Normal file
8
src/lib/css/loading.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { css } from 'lib/theme'
|
||||
|
||||
// need to use $loading as `loading` is a reserved prop
|
||||
export const loadingOpacityCss = css<{ $loading: boolean }>`
|
||||
filter: ${({ $loading }) => ($loading ? 'grayscale(1)' : 'none')};
|
||||
opacity: ${({ $loading }) => ($loading ? '0.4' : '1')};
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
`
|
||||
98
src/lib/hooks/routing/clientSideSmartOrderRouter.ts
Normal file
98
src/lib/hooks/routing/clientSideSmartOrderRouter.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { BigintIsh, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { AlphaRouter, AlphaRouterConfig, AlphaRouterParams, ChainId } from '@uniswap/smart-order-router'
|
||||
import JSBI from 'jsbi'
|
||||
import { GetQuoteResult } from 'state/routing/types'
|
||||
import { transformSwapRouteToGetQuoteResult } from 'utils/transformSwapRouteToGetQuoteResult'
|
||||
|
||||
export const AUTO_ROUTER_SUPPORTED_CHAINS: ChainId[] = Object.values(ChainId) as number[]
|
||||
|
||||
async function getQuote(
|
||||
{
|
||||
type,
|
||||
chainId,
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
amount: amountRaw,
|
||||
}: {
|
||||
type: 'exactIn' | 'exactOut'
|
||||
chainId: ChainId
|
||||
tokenIn: { address: string; chainId: number; decimals: number; symbol?: string }
|
||||
tokenOut: { address: string; chainId: number; decimals: number; symbol?: string }
|
||||
amount: BigintIsh
|
||||
},
|
||||
routerParams: AlphaRouterParams,
|
||||
routerConfig: Partial<AlphaRouterConfig>
|
||||
): Promise<{ data: GetQuoteResult; error?: unknown }> {
|
||||
const router = new AlphaRouter(routerParams)
|
||||
|
||||
const currencyIn = new Token(tokenIn.chainId, tokenIn.address, tokenIn.decimals, tokenIn.symbol)
|
||||
const currencyOut = new Token(tokenOut.chainId, tokenOut.address, tokenOut.decimals, tokenOut.symbol)
|
||||
|
||||
const baseCurrency = type === 'exactIn' ? currencyIn : currencyOut
|
||||
const quoteCurrency = type === 'exactIn' ? currencyOut : currencyIn
|
||||
const amount = CurrencyAmount.fromRawAmount(baseCurrency, JSBI.BigInt(amountRaw))
|
||||
|
||||
const swapRoute = await router.route(
|
||||
amount,
|
||||
quoteCurrency,
|
||||
type === 'exactIn' ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
|
||||
/*swapConfig=*/ undefined,
|
||||
routerConfig
|
||||
)
|
||||
|
||||
if (!swapRoute) throw new Error('Failed to generate client side quote')
|
||||
|
||||
return { data: transformSwapRouteToGetQuoteResult(type, amount, swapRoute) }
|
||||
}
|
||||
|
||||
interface QuoteArguments {
|
||||
tokenInAddress: string
|
||||
tokenInChainId: ChainId
|
||||
tokenInDecimals: number
|
||||
tokenInSymbol?: string
|
||||
tokenOutAddress: string
|
||||
tokenOutChainId: ChainId
|
||||
tokenOutDecimals: number
|
||||
tokenOutSymbol?: string
|
||||
amount: string
|
||||
type: 'exactIn' | 'exactOut'
|
||||
}
|
||||
|
||||
export async function getClientSideQuote(
|
||||
{
|
||||
tokenInAddress,
|
||||
tokenInChainId,
|
||||
tokenInDecimals,
|
||||
tokenInSymbol,
|
||||
tokenOutAddress,
|
||||
tokenOutChainId,
|
||||
tokenOutDecimals,
|
||||
tokenOutSymbol,
|
||||
amount,
|
||||
type,
|
||||
}: QuoteArguments,
|
||||
routerParams: AlphaRouterParams,
|
||||
routerConfig: Partial<AlphaRouterConfig>
|
||||
) {
|
||||
return getQuote(
|
||||
{
|
||||
type,
|
||||
chainId: tokenInChainId,
|
||||
tokenIn: {
|
||||
address: tokenInAddress,
|
||||
chainId: tokenInChainId,
|
||||
decimals: tokenInDecimals,
|
||||
symbol: tokenInSymbol,
|
||||
},
|
||||
tokenOut: {
|
||||
address: tokenOutAddress,
|
||||
chainId: tokenOutChainId,
|
||||
decimals: tokenOutDecimals,
|
||||
symbol: tokenOutSymbol,
|
||||
},
|
||||
amount,
|
||||
},
|
||||
routerParams,
|
||||
routerConfig
|
||||
)
|
||||
}
|
||||
155
src/lib/hooks/routing/useClientSideSmartOrderRouterTrade.ts
Normal file
155
src/lib/hooks/routing/useClientSideSmartOrderRouterTrade.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { Protocol } from '@uniswap/router-sdk'
|
||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { ChainId } from '@uniswap/smart-order-router'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useStablecoinAmountFromFiatValue } from 'hooks/useUSDCPrice'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { GetQuoteResult, InterfaceTrade, TradeState } from 'state/routing/types'
|
||||
import { computeRoutes, transformRoutesToTrade } from 'state/routing/utils'
|
||||
|
||||
import useActiveWeb3React from '../useActiveWeb3React'
|
||||
import { getClientSideQuote } from './clientSideSmartOrderRouter'
|
||||
import { useRoutingAPIArguments } from './useRoutingAPIArguments'
|
||||
|
||||
/**
|
||||
* Reduces client-side latency by increasing the minimum percentage of the input token to use for each route in a split route while SOR is used client-side.
|
||||
* Defaults are defined in https://github.com/Uniswap/smart-order-router/blob/309e6f6603984d3b5aef0733b0cfaf129c29f602/src/routers/alpha-router/config.ts#L83.
|
||||
*/
|
||||
const DistributionPercents: { [key: number]: number } = {
|
||||
[ChainId.MAINNET]: 10,
|
||||
[ChainId.OPTIMISM]: 10,
|
||||
[ChainId.OPTIMISTIC_KOVAN]: 10,
|
||||
[ChainId.ARBITRUM_ONE]: 25,
|
||||
[ChainId.ARBITRUM_RINKEBY]: 25,
|
||||
}
|
||||
|
||||
const DEFAULT_DISTRIBUTION_PERCENT = 10
|
||||
|
||||
function getConfig(chainId: ChainId | undefined) {
|
||||
return {
|
||||
// Limit to only V2 and V3.
|
||||
protocols: [Protocol.V2, Protocol.V3],
|
||||
|
||||
distributionPercent: (chainId && DistributionPercents[chainId]) ?? DEFAULT_DISTRIBUTION_PERCENT,
|
||||
}
|
||||
}
|
||||
|
||||
export default function useClientSideSmartOrderRouterTrade<TTradeType extends TradeType>(
|
||||
tradeType: TTradeType,
|
||||
amountSpecified?: CurrencyAmount<Currency>,
|
||||
otherCurrency?: Currency
|
||||
): {
|
||||
state: TradeState
|
||||
trade: InterfaceTrade<Currency, Currency, TTradeType> | undefined
|
||||
} {
|
||||
// Debounce is used to prevent excessive requests to SOR, as it is data intensive.
|
||||
// This helps provide a "syncing" state the UI can reference for loading animations.
|
||||
const inputs = useMemo(() => [tradeType, amountSpecified, otherCurrency], [tradeType, amountSpecified, otherCurrency])
|
||||
const debouncedInputs = useDebounce(inputs, 200)
|
||||
const isDebouncing = inputs !== debouncedInputs
|
||||
|
||||
const chainId = amountSpecified?.currency.chainId
|
||||
const { library } = useActiveWeb3React()
|
||||
|
||||
const [currencyIn, currencyOut]: [Currency | undefined, Currency | undefined] = useMemo(
|
||||
() =>
|
||||
tradeType === TradeType.EXACT_INPUT
|
||||
? [amountSpecified?.currency, otherCurrency]
|
||||
: [otherCurrency, amountSpecified?.currency],
|
||||
[amountSpecified, otherCurrency, tradeType]
|
||||
)
|
||||
|
||||
const queryArgs = useRoutingAPIArguments({
|
||||
tokenIn: currencyIn,
|
||||
tokenOut: currencyOut,
|
||||
amount: amountSpecified,
|
||||
tradeType,
|
||||
useClientSideRouter: true,
|
||||
})
|
||||
const params = useMemo(() => chainId && library && { chainId, provider: library }, [chainId, library])
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [{ data: quoteResult, error }, setResult] = useState<{
|
||||
data?: GetQuoteResult
|
||||
error?: unknown
|
||||
}>({ error: undefined })
|
||||
const config = useMemo(() => getConfig(chainId), [chainId])
|
||||
|
||||
// When arguments update, make a new call to SOR for updated quote
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
if (isDebouncing) return
|
||||
|
||||
let stale = false
|
||||
fetchQuote()
|
||||
return () => {
|
||||
stale = true
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
async function fetchQuote() {
|
||||
if (queryArgs && params) {
|
||||
let result
|
||||
try {
|
||||
result = await getClientSideQuote(queryArgs, params, config)
|
||||
} catch {
|
||||
result = { error: true }
|
||||
}
|
||||
if (!stale) {
|
||||
setResult(result)
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [queryArgs, params, config, isDebouncing])
|
||||
|
||||
const route = useMemo(
|
||||
() => computeRoutes(currencyIn, currencyOut, tradeType, quoteResult),
|
||||
[currencyIn, currencyOut, quoteResult, tradeType]
|
||||
)
|
||||
const gasUseEstimateUSD = useStablecoinAmountFromFiatValue(quoteResult?.gasUseEstimateUSD) ?? null
|
||||
const trade = useMemo(() => {
|
||||
if (route) {
|
||||
try {
|
||||
return route && transformRoutesToTrade(route, tradeType, gasUseEstimateUSD)
|
||||
} catch (e: unknown) {
|
||||
console.debug('transformRoutesToTrade failed: ', e)
|
||||
}
|
||||
}
|
||||
return
|
||||
}, [gasUseEstimateUSD, route, tradeType])
|
||||
|
||||
return useMemo(() => {
|
||||
if (!currencyIn || !currencyOut) {
|
||||
return { state: TradeState.INVALID, trade: undefined }
|
||||
}
|
||||
|
||||
// Returns the last trade state while syncing/loading to avoid jank from clearing the last trade while loading.
|
||||
if (isDebouncing) {
|
||||
return { state: TradeState.SYNCING, trade }
|
||||
} else if (loading) {
|
||||
return { state: TradeState.LOADING, trade }
|
||||
}
|
||||
|
||||
let otherAmount = undefined
|
||||
if (quoteResult) {
|
||||
switch (tradeType) {
|
||||
case TradeType.EXACT_INPUT:
|
||||
otherAmount = CurrencyAmount.fromRawAmount(currencyOut, quoteResult.quote)
|
||||
break
|
||||
case TradeType.EXACT_OUTPUT:
|
||||
otherAmount = CurrencyAmount.fromRawAmount(currencyIn, quoteResult.quote)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (error || !otherAmount || !route || route.length === 0 || !queryArgs) {
|
||||
return { state: TradeState.NO_ROUTE_FOUND, trade: undefined }
|
||||
}
|
||||
|
||||
if (trade) {
|
||||
return { state: TradeState.VALID, trade }
|
||||
}
|
||||
return { state: TradeState.INVALID, trade: undefined }
|
||||
}, [currencyIn, currencyOut, isDebouncing, loading, quoteResult, error, route, queryArgs, trade, tradeType])
|
||||
}
|
||||
41
src/lib/hooks/routing/useRoutingAPIArguments.ts
Normal file
41
src/lib/hooks/routing/useRoutingAPIArguments.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
/**
|
||||
* Returns query arguments for the Routing API query or undefined if the
|
||||
* query should be skipped. Input arguments do not need to be memoized, as they will
|
||||
* be destructured.
|
||||
*/
|
||||
export function useRoutingAPIArguments({
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
amount,
|
||||
tradeType,
|
||||
useClientSideRouter,
|
||||
}: {
|
||||
tokenIn: Currency | undefined
|
||||
tokenOut: Currency | undefined
|
||||
amount: CurrencyAmount<Currency> | undefined
|
||||
tradeType: TradeType
|
||||
useClientSideRouter: boolean
|
||||
}) {
|
||||
return useMemo(
|
||||
() =>
|
||||
!tokenIn || !tokenOut || !amount || tokenIn.equals(tokenOut)
|
||||
? undefined
|
||||
: {
|
||||
amount: amount.quotient.toString(),
|
||||
tokenInAddress: tokenIn.wrapped.address,
|
||||
tokenInChainId: tokenIn.wrapped.chainId,
|
||||
tokenInDecimals: tokenIn.wrapped.decimals,
|
||||
tokenInSymbol: tokenIn.wrapped.symbol,
|
||||
tokenOutAddress: tokenOut.wrapped.address,
|
||||
tokenOutChainId: tokenOut.wrapped.chainId,
|
||||
tokenOutDecimals: tokenOut.wrapped.decimals,
|
||||
tokenOutSymbol: tokenOut.wrapped.symbol,
|
||||
useClientSideRouter,
|
||||
type: (tradeType === TradeType.EXACT_INPUT ? 'exactIn' : 'exactOut') as 'exactIn' | 'exactOut',
|
||||
},
|
||||
[amount, tokenIn, tokenOut, tradeType, useClientSideRouter]
|
||||
)
|
||||
}
|
||||
@@ -72,3 +72,8 @@ export function useSwapAmount(field: Field): [string | undefined, (amount: strin
|
||||
)
|
||||
return [value, updateAmount]
|
||||
}
|
||||
|
||||
// check if any amount has been entered by user
|
||||
export function useIsAmountPopulated() {
|
||||
return Boolean(useAtomValue(amountAtom))
|
||||
}
|
||||
|
||||
32
src/lib/hooks/swap/useBestTrade.ts
Normal file
32
src/lib/hooks/swap/useBestTrade.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { useClientSideV3Trade } from 'hooks/useClientSideV3Trade'
|
||||
import { InterfaceTrade, TradeState } from 'state/routing/types'
|
||||
|
||||
import useClientSideSmartOrderRouterTrade from '../routing/useClientSideSmartOrderRouterTrade'
|
||||
|
||||
/**
|
||||
* Returns the best v2+v3 trade for a desired swap.
|
||||
* @param tradeType whether the swap is an exact in/out
|
||||
* @param amountSpecified the exact amount to swap in/out
|
||||
* @param otherCurrency the desired output/payment currency
|
||||
*/
|
||||
export function useBestTrade(
|
||||
tradeType: TradeType,
|
||||
amountSpecified?: CurrencyAmount<Currency>,
|
||||
otherCurrency?: Currency
|
||||
): {
|
||||
state: TradeState
|
||||
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
|
||||
} {
|
||||
const clientSORTrade = useClientSideSmartOrderRouterTrade(tradeType, amountSpecified, otherCurrency)
|
||||
|
||||
// Use a simple client side logic as backup if SOR is not available.
|
||||
const useFallback = clientSORTrade.state === TradeState.NO_ROUTE_FOUND || clientSORTrade.state === TradeState.INVALID
|
||||
const fallbackTrade = useClientSideV3Trade(
|
||||
tradeType,
|
||||
useFallback ? amountSpecified : undefined,
|
||||
useFallback ? otherCurrency : undefined
|
||||
)
|
||||
|
||||
return useFallback ? fallbackTrade : clientSORTrade
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { FeeOptions } from '@uniswap/v3-sdk'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useENS from 'hooks/useENS'
|
||||
import { SignatureData } from 'hooks/useERC20Permit'
|
||||
@@ -17,18 +18,40 @@ export enum SwapCallbackState {
|
||||
VALID,
|
||||
}
|
||||
|
||||
interface UseSwapCallbackReturns {
|
||||
state: SwapCallbackState
|
||||
callback: null | (() => Promise<TransactionResponse>)
|
||||
error: ReactNode | null
|
||||
}
|
||||
interface UseSwapCallbackArgs {
|
||||
trade: AnyTrade | undefined // trade to execute, required
|
||||
allowedSlippage: Percent // in bips
|
||||
recipientAddressOrName: string | null | undefined // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
|
||||
signatureData: SignatureData | null | undefined
|
||||
deadline: BigNumber | undefined
|
||||
feeOptions?: FeeOptions
|
||||
}
|
||||
|
||||
// returns a function that will execute a swap, if the parameters are all valid
|
||||
// and the user has approved the slippage adjusted input amount for the trade
|
||||
export function useSwapCallback(
|
||||
trade: AnyTrade | undefined, // trade to execute, required
|
||||
allowedSlippage: Percent, // in bips
|
||||
recipientAddressOrName: string | null | undefined, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
|
||||
signatureData: SignatureData | null | undefined,
|
||||
deadline: BigNumber | undefined
|
||||
): { state: SwapCallbackState; callback: null | (() => Promise<TransactionResponse>); error: ReactNode | null } {
|
||||
export function useSwapCallback({
|
||||
trade,
|
||||
allowedSlippage,
|
||||
recipientAddressOrName,
|
||||
signatureData,
|
||||
deadline,
|
||||
feeOptions,
|
||||
}: UseSwapCallbackArgs): UseSwapCallbackReturns {
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
|
||||
const swapCalls = useSwapCallArguments(trade, allowedSlippage, recipientAddressOrName, signatureData, deadline)
|
||||
const swapCalls = useSwapCallArguments(
|
||||
trade,
|
||||
allowedSlippage,
|
||||
recipientAddressOrName,
|
||||
signatureData,
|
||||
deadline,
|
||||
feeOptions
|
||||
)
|
||||
const { callback } = useSendSwapTransaction(account, chainId, library, trade, swapCalls)
|
||||
|
||||
const { address: recipientAddress } = useENS(recipientAddressOrName)
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance'
|
||||
import { useClientSideV3Trade } from 'hooks/useClientSideV3Trade'
|
||||
import { FeeOptions } from '@uniswap/v3-sdk'
|
||||
import { atom } from 'jotai'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance'
|
||||
import { maxSlippageAtom } from 'lib/state/settings'
|
||||
import { Field, swapAtom } from 'lib/state/swap'
|
||||
import { feeOptionsAtom, Field, swapAtom } from 'lib/state/swap'
|
||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||
import { ReactNode, useEffect, useMemo } from 'react'
|
||||
import { InterfaceTrade, TradeState } from 'state/routing/types'
|
||||
|
||||
import { isAddress } from '../../../utils'
|
||||
import useActiveWeb3React from '../useActiveWeb3React'
|
||||
import useAllowedSlippage from '../useAllowedSlippage'
|
||||
import { useBestTrade } from './useBestTrade'
|
||||
|
||||
interface SwapInfo {
|
||||
currencies: { [field in Field]?: Currency }
|
||||
@@ -23,6 +23,7 @@ interface SwapInfo {
|
||||
state: TradeState
|
||||
}
|
||||
allowedSlippage: Percent
|
||||
feeOptions: FeeOptions | undefined
|
||||
}
|
||||
|
||||
const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = {
|
||||
@@ -42,6 +43,8 @@ function useComputeSwapInfo(): SwapInfo {
|
||||
[Field.OUTPUT]: outputCurrency,
|
||||
} = useAtomValue(swapAtom)
|
||||
|
||||
const feeOptions = useAtomValue(feeOptionsAtom)
|
||||
|
||||
const to = account
|
||||
|
||||
const relevantTokenBalances = useCurrencyBalances(
|
||||
@@ -54,11 +57,11 @@ function useComputeSwapInfo(): SwapInfo {
|
||||
() => tryParseCurrencyAmount(amount, (isExactIn ? inputCurrency : outputCurrency) ?? undefined),
|
||||
[inputCurrency, isExactIn, outputCurrency, amount]
|
||||
)
|
||||
const parsedAmountIn = isExactIn ? parsedAmount : undefined
|
||||
const parsedAmountOut = isExactIn ? undefined : parsedAmount
|
||||
|
||||
/**
|
||||
* @TODO (ianlapham): eventually need a strategy for routing API here
|
||||
*/
|
||||
const trade = useClientSideV3Trade(
|
||||
//@TODO(ianlapham): this would eventually be replaced with routing api logic.
|
||||
const trade = useBestTrade(
|
||||
isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
|
||||
parsedAmount,
|
||||
(isExactIn ? outputCurrency : inputCurrency) ?? undefined
|
||||
@@ -82,22 +85,13 @@ function useComputeSwapInfo(): SwapInfo {
|
||||
|
||||
const currencyAmounts = useMemo(
|
||||
() => ({
|
||||
[Field.INPUT]: trade.trade?.inputAmount,
|
||||
[Field.OUTPUT]: trade.trade?.outputAmount,
|
||||
[Field.INPUT]: parsedAmountIn || trade.trade?.inputAmount,
|
||||
[Field.OUTPUT]: parsedAmountOut || trade.trade?.outputAmount,
|
||||
}),
|
||||
[trade.trade?.inputAmount, trade.trade?.outputAmount]
|
||||
[parsedAmountIn, parsedAmountOut, trade.trade?.inputAmount, trade.trade?.outputAmount]
|
||||
)
|
||||
|
||||
/*
|
||||
* If user has enabled 'auto' slippage, use the default best slippage calculated
|
||||
* based on the trade. If user has entered custom slippage, use that instead.
|
||||
*/
|
||||
const autoSlippageTolerance = useAutoSlippageTolerance(trade.trade)
|
||||
const maxSlippage = useAtomValue(maxSlippageAtom)
|
||||
const allowedSlippage = useMemo(
|
||||
() => (maxSlippage === 'auto' ? autoSlippageTolerance : maxSlippage),
|
||||
[autoSlippageTolerance, maxSlippage]
|
||||
)
|
||||
const allowedSlippage = useAllowedSlippage(trade.trade)
|
||||
|
||||
const inputError = useMemo(() => {
|
||||
let inputError: ReactNode | undefined
|
||||
@@ -141,8 +135,9 @@ function useComputeSwapInfo(): SwapInfo {
|
||||
inputError,
|
||||
trade,
|
||||
allowedSlippage,
|
||||
feeOptions,
|
||||
}),
|
||||
[currencies, currencyBalances, currencyAmounts, inputError, trade, allowedSlippage]
|
||||
[currencies, currencyBalances, currencyAmounts, inputError, trade, allowedSlippage, feeOptions]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -152,6 +147,7 @@ const swapInfoAtom = atom<SwapInfo>({
|
||||
currencyAmounts: {},
|
||||
trade: { state: TradeState.INVALID },
|
||||
allowedSlippage: new Percent(0),
|
||||
feeOptions: undefined,
|
||||
})
|
||||
|
||||
export function SwapInfoUpdater() {
|
||||
|
||||
35
src/lib/hooks/swap/useSyncConvenienceFee.ts
Normal file
35
src/lib/hooks/swap/useSyncConvenienceFee.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useUpdateAtom } from 'jotai/utils'
|
||||
import { feeOptionsAtom } from 'lib/state/swap'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
interface FeeOptionsArgs {
|
||||
convenienceFee?: number
|
||||
convenienceFeeRecipient?: string | string | { [chainId: number]: string }
|
||||
}
|
||||
|
||||
export default function useSyncConvenienceFee({ convenienceFee, convenienceFeeRecipient }: FeeOptionsArgs) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const updateFeeOptions = useUpdateAtom(feeOptionsAtom)
|
||||
|
||||
useEffect(() => {
|
||||
if (convenienceFee && convenienceFeeRecipient) {
|
||||
if (typeof convenienceFeeRecipient === 'string') {
|
||||
updateFeeOptions({
|
||||
fee: new Percent(convenienceFee, 10_000),
|
||||
recipient: convenienceFeeRecipient,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (chainId && convenienceFeeRecipient[chainId]) {
|
||||
updateFeeOptions({
|
||||
fee: new Percent(convenienceFee, 10_000),
|
||||
recipient: convenienceFeeRecipient[chainId],
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
updateFeeOptions(undefined)
|
||||
}, [chainId, convenienceFee, convenienceFeeRecipient, updateFeeOptions])
|
||||
}
|
||||
@@ -31,7 +31,7 @@ interface UseSwapDefaultsArgs {
|
||||
defaultOutputAmount?: string
|
||||
}
|
||||
|
||||
export default function useSwapDefaults({
|
||||
export default function useSyncSwapDefaults({
|
||||
defaultInputAddress,
|
||||
defaultInputAmount,
|
||||
defaultOutputAddress,
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Web3ReactHooks } from '@web3-react/core'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { injectedAtom, urlAtom, Web3ReactState } from 'lib/state/web3'
|
||||
import { Web3ReactHooks } from 'widgets-web3-react/core'
|
||||
|
||||
export function useActiveWeb3ReactState(): Web3ReactState {
|
||||
const injected = useAtomValue(injectedAtom)
|
||||
|
||||
27
src/lib/hooks/useAllowedSlippage.ts
Normal file
27
src/lib/hooks/useAllowedSlippage.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { autoSlippageAtom, maxSlippageAtom } from 'lib/state/settings'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
|
||||
export function toPercent(maxSlippage: number | undefined): Percent | undefined {
|
||||
if (!maxSlippage) return undefined
|
||||
const numerator = Math.floor(maxSlippage * 100)
|
||||
return new Percent(numerator, 10_000)
|
||||
}
|
||||
|
||||
/** Returns the user-inputted max slippage. */
|
||||
export default function useAllowedSlippage(trade: InterfaceTrade<Currency, Currency, TradeType> | undefined): Percent {
|
||||
const autoSlippage = useAutoSlippageTolerance(trade)
|
||||
const maxSlippage = toPercent(useAtomValue(maxSlippageAtom))
|
||||
return useAtomValue(autoSlippageAtom) ? autoSlippage : maxSlippage ?? autoSlippage
|
||||
}
|
||||
|
||||
export const MAX_VALID_SLIPPAGE = new Percent(1, 2)
|
||||
export const MIN_HIGH_SLIPPAGE = new Percent(1, 100)
|
||||
|
||||
export function getSlippageWarning(slippage?: Percent): 'warning' | 'error' | undefined {
|
||||
if (slippage?.greaterThan(MAX_VALID_SLIPPAGE)) return 'error'
|
||||
if (slippage?.greaterThan(MIN_HIGH_SLIPPAGE)) return 'warning'
|
||||
return
|
||||
}
|
||||
16
src/lib/hooks/useHasFocus.ts
Normal file
16
src/lib/hooks/useHasFocus.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
export default function useHasFocus(node: Node | null | undefined): boolean {
|
||||
const [hasFocus, setHasFocus] = useState(node?.contains(document.activeElement) ?? false)
|
||||
const onFocus = useCallback(() => setHasFocus(true), [])
|
||||
const onBlur = useCallback((e) => setHasFocus(node?.contains(e.relatedTarget) ?? false), [node])
|
||||
useEffect(() => {
|
||||
node?.addEventListener('focusin', onFocus)
|
||||
node?.addEventListener('focusout', onBlur)
|
||||
return () => {
|
||||
node?.removeEventListener('focusin', onFocus)
|
||||
node?.removeEventListener('focusout', onBlur)
|
||||
}
|
||||
}, [node, onFocus, onBlur])
|
||||
return hasFocus
|
||||
}
|
||||
16
src/lib/hooks/useHasHover.ts
Normal file
16
src/lib/hooks/useHasHover.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
export default function useHasHover(node: Node | null | undefined): boolean {
|
||||
const [hasHover, setHasHover] = useState(false)
|
||||
const onMouseEnter = useCallback(() => setHasHover(true), [])
|
||||
const onMouseLeave = useCallback((e) => setHasHover(false), [])
|
||||
useEffect(() => {
|
||||
node?.addEventListener('mouseenter', onMouseEnter)
|
||||
node?.addEventListener('mouseleave', onMouseLeave)
|
||||
return () => {
|
||||
node?.removeEventListener('mouseenter', onMouseEnter)
|
||||
node?.removeEventListener('mouseleave', onMouseLeave)
|
||||
}
|
||||
}, [node, onMouseEnter, onMouseLeave])
|
||||
return hasHover
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function useNativeEvent(
|
||||
export default function useNativeEvent<K extends keyof HTMLElementEventMap>(
|
||||
element: HTMLElement | null,
|
||||
...eventListener: Parameters<HTMLElement['addEventListener']>
|
||||
type: K,
|
||||
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
|
||||
options?: boolean | AddEventListenerOptions | undefined
|
||||
) {
|
||||
useEffect(() => {
|
||||
element?.addEventListener(...eventListener)
|
||||
return () => element?.removeEventListener(...eventListener)
|
||||
}, [element, eventListener])
|
||||
element?.addEventListener(type, listener, options)
|
||||
return () => element?.removeEventListener(type, listener, options)
|
||||
}, [element, type, listener, options])
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lib/theme'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import useNativeEvent from './useNativeEvent'
|
||||
|
||||
@@ -52,7 +52,11 @@ export default function useScrollbar(element: HTMLElement | null, { padded = fal
|
||||
useEffect(() => {
|
||||
setOverflow(hasOverflow(element))
|
||||
}, [element])
|
||||
useNativeEvent(element, 'transitionend', () => setOverflow(hasOverflow(element)))
|
||||
useNativeEvent(
|
||||
element,
|
||||
'transitionend',
|
||||
useCallback(() => setOverflow(hasOverflow(element)), [element])
|
||||
)
|
||||
return useMemo(() => (overflow ? scrollbarCss(padded) : overflowCss), [overflow, padded])
|
||||
|
||||
function hasOverflow(element: HTMLElement | null) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { TokenInfo } from '@uniswap/token-lists'
|
||||
import { useMemo } from 'react'
|
||||
import { useTokenBalances } from 'state/wallet/hooks'
|
||||
|
||||
/** Sorts currency amounts (descending). */
|
||||
function balanceComparator(a?: CurrencyAmount<Currency>, b?: CurrencyAmount<Currency>) {
|
||||
@@ -15,8 +14,10 @@ function balanceComparator(a?: CurrencyAmount<Currency>, b?: CurrencyAmount<Curr
|
||||
return 0
|
||||
}
|
||||
|
||||
type TokenBalances = { [tokenAddress: string]: CurrencyAmount<Token> | undefined }
|
||||
|
||||
/** Sorts tokens by currency amount (descending), then symbol (ascending). */
|
||||
export function tokenComparator(balances: ReturnType<typeof useTokenBalances>, a: Token, b: Token) {
|
||||
export function tokenComparator(balances: TokenBalances, a: Token, b: Token) {
|
||||
// Sorts by balances
|
||||
const balanceComparison = balanceComparator(balances[a.address], balances[b.address])
|
||||
if (balanceComparison !== 0) return balanceComparison
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { L2_CHAIN_IDS } from 'constants/chains'
|
||||
import { L2_DEADLINE_FROM_NOW } from 'constants/misc'
|
||||
import { DEFAULT_DEADLINE_FROM_NOW, L2_DEADLINE_FROM_NOW } from 'constants/misc'
|
||||
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { useAtom } from 'jotai'
|
||||
import { transactionTtlAtom } from 'lib/state/settings'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import useActiveWeb3React from './useActiveWeb3React'
|
||||
|
||||
/** Returns the default transaction TTL for the chain, in minutes. */
|
||||
export function useDefaultTransactionTtl(): number {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
if (chainId && L2_CHAIN_IDS.includes(chainId)) return L2_DEADLINE_FROM_NOW / 60
|
||||
return DEFAULT_DEADLINE_FROM_NOW / 60
|
||||
}
|
||||
|
||||
/** Returns the user-inputted transaction TTL, in minutes. */
|
||||
export function useTransactionTtl(): [number | undefined, (ttl?: number) => void] {
|
||||
return useAtom(transactionTtlAtom)
|
||||
}
|
||||
|
||||
// combines the block timestamp with the user setting to give the deadline that should be used for any submitted transaction
|
||||
export default function useTransactionDeadline(): BigNumber | undefined {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const userDeadline = useAtomValue(transactionTtlAtom)
|
||||
const [ttl] = useTransactionTtl()
|
||||
const defaultTtl = useDefaultTransactionTtl()
|
||||
|
||||
const blockTimestamp = useCurrentBlockTimestamp()
|
||||
return useMemo(() => {
|
||||
if (blockTimestamp && chainId && L2_CHAIN_IDS.includes(chainId)) return blockTimestamp.add(L2_DEADLINE_FROM_NOW)
|
||||
//@TODO(ianlapham): update this to be stored as seconds
|
||||
if (blockTimestamp && userDeadline) return blockTimestamp.add(userDeadline * 60) // adjust for seconds
|
||||
return undefined
|
||||
}, [blockTimestamp, chainId, userDeadline])
|
||||
if (!blockTimestamp) return undefined
|
||||
return blockTimestamp.add((ttl || defaultTtl) /* in seconds */ * 60)
|
||||
}, [blockTimestamp, defaultTtl, ttl])
|
||||
}
|
||||
|
||||
@@ -80,7 +80,8 @@ export async function dynamicActivate(locale: SupportedLocale) {
|
||||
i18n.loadLocaleData(locale, { plurals: () => plurals[locale] })
|
||||
// There are no default messages in production; instead, bundle the default to save a network request:
|
||||
// see https://github.com/lingui/js-lingui/issues/388#issuecomment-497779030
|
||||
const catalog = locale === DEFAULT_LOCALE ? DEFAULT_CATALOG : await import(`locales/${locale}`)
|
||||
const catalog =
|
||||
locale === DEFAULT_LOCALE ? DEFAULT_CATALOG : await import(`${process.env.REACT_APP_LOCALES}/${locale}`)
|
||||
i18n.load(locale, catalog.messages)
|
||||
i18n.activate(locale)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import { ReactComponent as CheckIcon } from 'lib/assets/svg/check.svg'
|
||||
import { ReactComponent as ExpandoIcon } from 'lib/assets/svg/expando.svg'
|
||||
import { ReactComponent as LogoIcon } from 'lib/assets/svg/logo.svg'
|
||||
import { ReactComponent as SpinnerIcon } from 'lib/assets/svg/spinner.svg'
|
||||
import { ReactComponent as WalletIcon } from 'lib/assets/svg/wallet.svg'
|
||||
import styled, { Color, css, keyframes } from 'lib/theme'
|
||||
import { FunctionComponent, SVGProps } from 'react'
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import { Icon as FeatherIcon } from 'react-feather'
|
||||
import {
|
||||
AlertTriangle as AlertTriangleIcon,
|
||||
ArrowDown as ArrowDownIcon,
|
||||
ArrowRight as ArrowRightIcon,
|
||||
ArrowUp as ArrowUpIcon,
|
||||
BarChart2 as BarChart2Icon,
|
||||
CheckCircle as CheckCircleIcon,
|
||||
ChevronDown as ChevronDownIcon,
|
||||
Clock as ClockIcon,
|
||||
CreditCard as CreditCardIcon,
|
||||
ExternalLink as LinkIcon,
|
||||
HelpCircle as HelpCircleIcon,
|
||||
Info as InfoIcon,
|
||||
@@ -18,12 +23,9 @@ import {
|
||||
Slash as SlashIcon,
|
||||
Trash2 as Trash2Icon,
|
||||
X as XIcon,
|
||||
XOctagon as XOctagonIcon,
|
||||
} from 'react-feather'
|
||||
|
||||
import { ReactComponent as CheckIcon } from '../assets/svg/check.svg'
|
||||
import { ReactComponent as ExpandoIcon } from '../assets/svg/expando.svg'
|
||||
import { ReactComponent as LogoIcon } from '../assets/svg/logo.svg'
|
||||
import { ReactComponent as SpinnerIcon } from '../assets/svg/spinner.svg'
|
||||
/* eslint-enable no-restricted-imports */
|
||||
|
||||
type SVGIcon = FunctionComponent<SVGProps<SVGSVGElement>>
|
||||
|
||||
@@ -36,8 +38,6 @@ function icon(Icon: FeatherIcon | SVGIcon) {
|
||||
`
|
||||
}
|
||||
|
||||
export type Icon = ReturnType<typeof icon>
|
||||
|
||||
export const largeIconCss = css<{ iconSize: number }>`
|
||||
display: flex;
|
||||
|
||||
@@ -50,11 +50,14 @@ export const largeIconCss = css<{ iconSize: number }>`
|
||||
|
||||
const LargeWrapper = styled.div<{ iconSize: number }>`
|
||||
height: 1em;
|
||||
width: ${({ iconSize }) => iconSize}em;
|
||||
${largeIconCss}
|
||||
`
|
||||
|
||||
export type Icon = ReturnType<typeof icon> | typeof LargeIcon
|
||||
|
||||
interface LargeIconProps {
|
||||
icon: Icon
|
||||
icon?: Icon
|
||||
color?: Color
|
||||
size?: number
|
||||
className?: string
|
||||
@@ -63,7 +66,7 @@ interface LargeIconProps {
|
||||
export function LargeIcon({ icon: Icon, color, size = 1.2, className }: LargeIconProps) {
|
||||
return (
|
||||
<LargeWrapper color={color} iconSize={size} className={className}>
|
||||
<Icon color={color} />
|
||||
{Icon && <Icon color={color} />}
|
||||
</LargeWrapper>
|
||||
)
|
||||
}
|
||||
@@ -73,16 +76,18 @@ export const ArrowDown = icon(ArrowDownIcon)
|
||||
export const ArrowRight = icon(ArrowRightIcon)
|
||||
export const ArrowUp = icon(ArrowUpIcon)
|
||||
export const CheckCircle = icon(CheckCircleIcon)
|
||||
export const BarChart = icon(BarChart2Icon)
|
||||
export const ChevronDown = icon(ChevronDownIcon)
|
||||
export const Clock = icon(ClockIcon)
|
||||
export const CreditCard = icon(CreditCardIcon)
|
||||
export const HelpCircle = icon(HelpCircleIcon)
|
||||
export const Info = icon(InfoIcon)
|
||||
export const Link = icon(LinkIcon)
|
||||
export const Settings = icon(SettingsIcon)
|
||||
export const Slash = icon(SlashIcon)
|
||||
export const Trash2 = icon(Trash2Icon)
|
||||
export const Wallet = icon(WalletIcon)
|
||||
export const X = icon(XIcon)
|
||||
export const XOctagon = icon(XOctagonIcon)
|
||||
|
||||
export const Check = styled(icon(CheckIcon))`
|
||||
circle {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user