web3-react migration (#269)
* put suspense below redux * don't mount qr * properly format .json's * remove useless Web3Connect component * remove react-responsive header logic * finalize initial web3-react migration * add rudimentary network support * address ci/cd issues * fix syntax * add infura support rewrite create-exchange closes #173 * remove CI flag, lazy loaded disabled for now * roll back /pool * fix currency input errors fix valid state of buttons * fix nav * obscure env variables * fix mobile header bug
This commit is contained in:
parent
f855706f0d
commit
4e413915f8
3
.env.example
Normal file
3
.env.example
Normal file
@ -0,0 +1,3 @@
|
||||
REACT_APP_NETWORK_ID="1"
|
||||
REACT_APP_NETWORK_URL=""
|
||||
REACT_APP_NETWORK_NAME="Main Ethereum Network"
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,6 +11,7 @@
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
|
16
README.md
16
README.md
@ -16,23 +16,25 @@ This an an open source interface for Uniswap - a protocol for decentralized exch
|
||||
|
||||
## To Start Development
|
||||
|
||||
###### Installing dependencies
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
###### Running locally on Rinkeby
|
||||
### Configure Environment
|
||||
|
||||
Rename `.env.example` to `.env` and fill in the appropriate variables.
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
# or
|
||||
yarn start:rinkeby
|
||||
```
|
||||
|
||||
###### Running locally on other testnet
|
||||
|
||||
```bash
|
||||
REACT_APP_NETWORK_ID=2 REACT_APP_NETWORK='Ropsten Test Network' yarn start
|
||||
```
|
||||
More robust support for other testnets is in the works!
|
||||
|
||||
## Contributions
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
# support SPA setup
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
|
@ -20,7 +20,6 @@
|
||||
"react-ga": "^2.5.7",
|
||||
"react-i18next": "^10.7.0",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-responsive": "^5.0.0",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"react-scripts": "^2.1.8",
|
||||
"react-transition-group": "1.x",
|
||||
@ -29,13 +28,13 @@
|
||||
"redux-thunk": "^2.2.0",
|
||||
"ua-parser-js": "^0.7.18",
|
||||
"web3": "1.0.0-beta.52",
|
||||
"web3-react": "^4.0.0"
|
||||
"web3-react": "^5.0.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"start:rinkeby": "REACT_APP_NETWORK_ID=4 REACT_APP_NETWORK='Rinkeby Test Network' yarn start",
|
||||
"build": "react-scripts build",
|
||||
"build:rinkeby": "REACT_APP_NETWORK_ID=4 REACT_APP_NETWORK='Rinkeby Test Network' yarn build",
|
||||
"start:rinkeby": "REACT_APP_NETWORK_ID=4 REACT_APP_NETWORK_NAME='Rinkeby Test Network' yarn start",
|
||||
"build:rinkeby": "REACT_APP_NETWORK_ID=4 REACT_APP_NETWORK_NAME='Rinkeby Test Network' yarn build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject",
|
||||
"lint:base": "yarn eslint './src/**/*.{js,jsx}'",
|
||||
|
@ -70,6 +70,7 @@
|
||||
"invalidDecimals": "Invalid decimals",
|
||||
"tokenAddress": "Token Address",
|
||||
"label": "Label",
|
||||
"symbol": "Symbol",
|
||||
"decimals": "Decimals",
|
||||
"enterTokenCont": "Enter a token address to continue"
|
||||
}
|
||||
|
@ -3,35 +3,16 @@
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "string" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"inputs": [{ "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" }],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "bool" }],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
@ -40,12 +21,7 @@
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "uint256" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
@ -53,26 +29,12 @@
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
{ "name": "_from", "type": "address" },
|
||||
{ "name": "_to", "type": "address" },
|
||||
{ "name": "_value", "type": "uint256" }
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "bool" }],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
@ -81,31 +43,16 @@
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "uint8" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"inputs": [{ "name": "_owner", "type": "address" }],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "balance", "type": "uint256" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
@ -114,85 +61,36 @@
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "string" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"inputs": [{ "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" }],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "bool" }],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"inputs": [{ "name": "_owner", "type": "address" }, { "name": "_spender", "type": "address" }],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "uint256" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{ "payable": true, "stateMutability": "payable", "type": "fallback" },
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
{ "indexed": true, "name": "owner", "type": "address" },
|
||||
{ "indexed": true, "name": "spender", "type": "address" },
|
||||
{ "indexed": false, "name": "value", "type": "uint256" }
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
@ -200,21 +98,9 @@
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
{ "indexed": true, "name": "from", "type": "address" },
|
||||
{ "indexed": true, "name": "to", "type": "address" },
|
||||
{ "indexed": false, "name": "value", "type": "uint256" }
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
|
@ -3,35 +3,16 @@
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "string" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"inputs": [{ "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" }],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "bool" }],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
@ -40,12 +21,7 @@
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "uint256" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
@ -53,26 +29,12 @@
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
{ "name": "_from", "type": "address" },
|
||||
{ "name": "_to", "type": "address" },
|
||||
{ "name": "_value", "type": "uint256" }
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "bool" }],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
@ -81,31 +43,16 @@
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "uint8" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"inputs": [{ "name": "_owner", "type": "address" }],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "balance", "type": "uint256" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
@ -114,85 +61,36 @@
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "bytes32" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"inputs": [{ "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" }],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "bool" }],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"inputs": [{ "name": "_owner", "type": "address" }, { "name": "_spender", "type": "address" }],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [{ "name": "", "type": "uint256" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{ "payable": true, "stateMutability": "payable", "type": "fallback" },
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
{ "indexed": true, "name": "owner", "type": "address" },
|
||||
{ "indexed": true, "name": "spender", "type": "address" },
|
||||
{ "indexed": false, "name": "value", "type": "uint256" }
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
@ -200,21 +98,9 @@
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
{ "indexed": true, "name": "from", "type": "address" },
|
||||
{ "indexed": true, "name": "to", "type": "address" },
|
||||
{ "indexed": false, "name": "value", "type": "uint256" }
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
|
File diff suppressed because one or more lines are too long
@ -1 +1,74 @@
|
||||
[{"name": "NewExchange", "inputs": [{"type": "address", "name": "token", "indexed": true}, {"type": "address", "name": "exchange", "indexed": true}], "anonymous": false, "type": "event"}, {"name": "initializeFactory", "outputs": [], "inputs": [{"type": "address", "name": "template"}], "constant": false, "payable": false, "type": "function", "gas": 35725}, {"name": "createExchange", "outputs": [{"type": "address", "name": "out"}], "inputs": [{"type": "address", "name": "token"}], "constant": false, "payable": false, "type": "function", "gas": 187911}, {"name": "getExchange", "outputs": [{"type": "address", "name": "out"}], "inputs": [{"type": "address", "name": "token"}], "constant": true, "payable": false, "type": "function", "gas": 715}, {"name": "getToken", "outputs": [{"type": "address", "name": "out"}], "inputs": [{"type": "address", "name": "exchange"}], "constant": true, "payable": false, "type": "function", "gas": 745}, {"name": "getTokenWithId", "outputs": [{"type": "address", "name": "out"}], "inputs": [{"type": "uint256", "name": "token_id"}], "constant": true, "payable": false, "type": "function", "gas": 736}, {"name": "exchangeTemplate", "outputs": [{"type": "address", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 633}, {"name": "tokenCount", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 663}]
|
||||
[
|
||||
{
|
||||
"name": "NewExchange",
|
||||
"inputs": [
|
||||
{ "type": "address", "name": "token", "indexed": true },
|
||||
{ "type": "address", "name": "exchange", "indexed": true }
|
||||
],
|
||||
"anonymous": false,
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"name": "initializeFactory",
|
||||
"outputs": [],
|
||||
"inputs": [{ "type": "address", "name": "template" }],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 35725
|
||||
},
|
||||
{
|
||||
"name": "createExchange",
|
||||
"outputs": [{ "type": "address", "name": "out" }],
|
||||
"inputs": [{ "type": "address", "name": "token" }],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 187911
|
||||
},
|
||||
{
|
||||
"name": "getExchange",
|
||||
"outputs": [{ "type": "address", "name": "out" }],
|
||||
"inputs": [{ "type": "address", "name": "token" }],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 715
|
||||
},
|
||||
{
|
||||
"name": "getToken",
|
||||
"outputs": [{ "type": "address", "name": "out" }],
|
||||
"inputs": [{ "type": "address", "name": "exchange" }],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 745
|
||||
},
|
||||
{
|
||||
"name": "getTokenWithId",
|
||||
"outputs": [{ "type": "address", "name": "out" }],
|
||||
"inputs": [{ "type": "uint256", "name": "token_id" }],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 736
|
||||
},
|
||||
{
|
||||
"name": "exchangeTemplate",
|
||||
"outputs": [{ "type": "address", "name": "out" }],
|
||||
"inputs": [],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 633
|
||||
},
|
||||
{
|
||||
"name": "tokenCount",
|
||||
"outputs": [{ "type": "uint256", "name": "out" }],
|
||||
"inputs": [],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 663
|
||||
}
|
||||
]
|
||||
|
@ -33,7 +33,6 @@
|
||||
}
|
||||
|
||||
&__qr-container {
|
||||
display: none;
|
||||
padding: 10px;
|
||||
background: $concrete-gray;
|
||||
border: 1px solid $mercury-gray;
|
||||
|
@ -1,58 +1,45 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import c from 'classnames'
|
||||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
// import QrCode from '../QrCode' // commented out pending further review
|
||||
|
||||
import QrCode from '../QrCode'
|
||||
import './address-input-panel.scss'
|
||||
|
||||
class AddressInputPanel extends Component {
|
||||
static propTypes = {
|
||||
title: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
errorMessage: PropTypes.string
|
||||
}
|
||||
export default function AddressInputPanel({ title, onChange = () => {}, value = '', errorMessage }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
static defaultProps = {
|
||||
onChange() {},
|
||||
value: ''
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t, title, onChange, value, errorMessage } = this.props
|
||||
|
||||
return (
|
||||
<div className="currency-input-panel">
|
||||
<div
|
||||
className={c('currency-input-panel__container address-input-panel__recipient-row', {
|
||||
'currency-input-panel__container--error': errorMessage
|
||||
})}
|
||||
>
|
||||
<div className="address-input-panel__input-container">
|
||||
<div className="currency-input-panel__label-row">
|
||||
<div className="currency-input-panel__label-container">
|
||||
<span className="currency-input-panel__label">{title || t('recipientAddress')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="currency-input-panel__input-row">
|
||||
<input
|
||||
type="text"
|
||||
className={c('address-input-panel__input', {
|
||||
'address-input-panel__input--error': errorMessage
|
||||
})}
|
||||
placeholder="0x1234..."
|
||||
onChange={e => onChange(e.target.value)}
|
||||
value={value}
|
||||
/>
|
||||
return (
|
||||
<div className="currency-input-panel">
|
||||
<div
|
||||
className={classnames('currency-input-panel__container address-input-panel__recipient-row', {
|
||||
'currency-input-panel__container--error': errorMessage
|
||||
})}
|
||||
>
|
||||
<div className="address-input-panel__input-container">
|
||||
<div className="currency-input-panel__label-row">
|
||||
<div className="currency-input-panel__label-container">
|
||||
<span className="currency-input-panel__label">{title || t('recipientAddress')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="address-input-panel__qr-container">
|
||||
<QrCode onValueReceived={value => onChange(value)} />
|
||||
<div className="currency-input-panel__input-row">
|
||||
<input
|
||||
type="text"
|
||||
className={classnames('address-input-panel__input', {
|
||||
'address-input-panel__input--error': errorMessage
|
||||
})}
|
||||
placeholder="0x1234..."
|
||||
onChange={e => onChange(e.target.value)}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* commented out pending further review
|
||||
<div className="address-input-panel__qr-container">
|
||||
<QrCode onValueReceived={value => onChange(value)} />
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddressInputPanel
|
||||
|
@ -1,10 +1,15 @@
|
||||
import React, { Component } from 'react'
|
||||
import React, { useState, useRef, useEffect } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { CSSTransitionGroup } from 'react-transition-group'
|
||||
import classnames from 'classnames'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { withTranslation, useTranslation } from 'react-i18next'
|
||||
import { BigNumber as BN } from 'bignumber.js'
|
||||
import { useWeb3Context } from 'web3-react'
|
||||
import { ethers } from 'ethers'
|
||||
|
||||
import { useSignerOrProvider } from '../../hooks'
|
||||
import { getExchangeDetails, getTokenDetails, isAddress } from '../../utils'
|
||||
import Fuse from '../../helpers/fuse'
|
||||
import Modal from '../Modal'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
@ -12,13 +17,10 @@ import SearchIcon from '../../assets/images/magnifying-glass.svg'
|
||||
import { selectors, addPendingTx } from '../../ducks/web3connect'
|
||||
import { addApprovalTx } from '../../ducks/pending'
|
||||
import { addExchange } from '../../ducks/addresses'
|
||||
import { BigNumber as BN } from 'bignumber.js'
|
||||
import ERC20_ABI from '../../abi/erc20'
|
||||
|
||||
import './currency-panel.scss'
|
||||
|
||||
import ERC20_ABI from '../../abi/erc20'
|
||||
import FACTORY_ABI from '../../abi/factory'
|
||||
|
||||
const FUSE_OPTIONS = {
|
||||
includeMatches: false,
|
||||
threshold: 0.0,
|
||||
@ -32,50 +34,51 @@ const FUSE_OPTIONS = {
|
||||
|
||||
const TOKEN_ADDRESS_TO_LABEL = { ETH: 'ETH' }
|
||||
|
||||
class CurrencyInputPanel extends Component {
|
||||
static propTypes = {
|
||||
title: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
extraText: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
onCurrencySelected: PropTypes.func,
|
||||
onValueChange: PropTypes.func,
|
||||
tokenAddresses: PropTypes.shape({
|
||||
addresses: PropTypes.array.isRequired
|
||||
}).isRequired,
|
||||
exchangeAddresses: PropTypes.shape({
|
||||
fromToken: PropTypes.object.isRequired
|
||||
}).isRequired,
|
||||
factoryAddress: PropTypes.string,
|
||||
selectedTokens: PropTypes.array.isRequired,
|
||||
errorMessage: PropTypes.string,
|
||||
account: PropTypes.string,
|
||||
selectedTokenAddress: PropTypes.string,
|
||||
disableTokenSelect: PropTypes.bool,
|
||||
selectors: PropTypes.func.isRequired,
|
||||
addExchange: PropTypes.func.isRequired,
|
||||
filteredTokens: PropTypes.arrayOf(PropTypes.string),
|
||||
disableUnlock: PropTypes.bool,
|
||||
renderInput: PropTypes.func
|
||||
}
|
||||
function CurrencyInputPanel({
|
||||
tokenAddresses,
|
||||
filteredTokens = [],
|
||||
onValueChange = () => {},
|
||||
renderInput,
|
||||
onCurrencySelected = () => {},
|
||||
title,
|
||||
description,
|
||||
extraText,
|
||||
errorMessage,
|
||||
selectedTokens = [],
|
||||
disableUnlock,
|
||||
disableTokenSelect,
|
||||
selectors,
|
||||
account,
|
||||
factoryAddress,
|
||||
selectedTokenAddress = '',
|
||||
exchangeAddresses: { fromToken },
|
||||
addExchange,
|
||||
history,
|
||||
web3,
|
||||
transactions,
|
||||
pendingApprovals,
|
||||
value,
|
||||
addApprovalTx,
|
||||
addPendingTx
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const context = useWeb3Context()
|
||||
const signerOrProvider = useSignerOrProvider()
|
||||
|
||||
static defaultProps = {
|
||||
selectedTokens: [],
|
||||
filteredTokens: [],
|
||||
onCurrencySelected() {},
|
||||
onValueChange() {},
|
||||
selectedTokenAddress: ''
|
||||
}
|
||||
const inputRef = useRef()
|
||||
|
||||
state = {
|
||||
isShowingModal: false,
|
||||
searchQuery: '',
|
||||
loadingExchange: false
|
||||
}
|
||||
const [isShowingModal, setIsShowingModal] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [loadingExchange, setLoadingExchange] = useState(false)
|
||||
|
||||
createTokenList = () => {
|
||||
const { filteredTokens } = this.props
|
||||
let tokens = this.props.tokenAddresses.addresses
|
||||
useEffect(() => {
|
||||
if (inputRef.current && isShowingModal) {
|
||||
inputRef.current.focus()
|
||||
}
|
||||
}, [inputRef.current, isShowingModal])
|
||||
|
||||
function createTokenList() {
|
||||
let tokens = tokenAddresses.addresses
|
||||
let tokenList = [{ value: 'ETH', label: 'ETH', address: 'ETH' }]
|
||||
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
@ -90,30 +93,15 @@ class CurrencyInputPanel extends Component {
|
||||
return tokenList.filter(({ address }) => !filteredTokens.includes(address))
|
||||
}
|
||||
|
||||
onTokenSelect = address => {
|
||||
this.setState({
|
||||
searchQuery: '',
|
||||
isShowingModal: false
|
||||
})
|
||||
function onTokenSelect(address) {
|
||||
setSearchQuery('')
|
||||
setIsShowingModal(false)
|
||||
|
||||
this.props.onCurrencySelected(address)
|
||||
onCurrencySelected(address)
|
||||
}
|
||||
|
||||
renderTokenList() {
|
||||
const tokens = this.createTokenList()
|
||||
const { loadingExchange, searchQuery } = this.state
|
||||
const {
|
||||
t,
|
||||
selectedTokens,
|
||||
disableTokenSelect,
|
||||
web3,
|
||||
selectors,
|
||||
account,
|
||||
factoryAddress,
|
||||
exchangeAddresses: { fromToken },
|
||||
addExchange,
|
||||
history
|
||||
} = this.props
|
||||
function renderTokenList() {
|
||||
const tokens = createTokenList()
|
||||
|
||||
if (loadingExchange) {
|
||||
return (
|
||||
@ -124,21 +112,24 @@ class CurrencyInputPanel extends Component {
|
||||
)
|
||||
}
|
||||
|
||||
if (web3 && web3.utils && web3.utils.isAddress(searchQuery)) {
|
||||
if (isAddress(searchQuery)) {
|
||||
const tokenAddress = searchQuery
|
||||
const { label } = selectors().getBalance(account, tokenAddress)
|
||||
const factory = new web3.eth.Contract(FACTORY_ABI, factoryAddress)
|
||||
const exchangeAddress = fromToken[tokenAddress]
|
||||
|
||||
if (!exchangeAddress) {
|
||||
this.setState({ loadingExchange: true })
|
||||
factory.methods.getExchange(tokenAddress).call((err, data) => {
|
||||
if (!err && data !== '0x0000000000000000000000000000000000000000') {
|
||||
addExchange({ label, tokenAddress, exchangeAddress: data })
|
||||
setLoadingExchange(true)
|
||||
|
||||
getExchangeDetails(context.networkId, tokenAddress, signerOrProvider).then(async ({ exchangeAddress }) => {
|
||||
if (exchangeAddress !== ethers.constants.AddressZero) {
|
||||
const { symbol } = await getTokenDetails(tokenAddress, signerOrProvider)
|
||||
addExchange({
|
||||
tokenAddress,
|
||||
label: symbol,
|
||||
exchangeAddress
|
||||
})
|
||||
}
|
||||
this.setState({ loadingExchange: false })
|
||||
setLoadingExchange(false)
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,10 +143,10 @@ class CurrencyInputPanel extends Component {
|
||||
results = tokens
|
||||
} else {
|
||||
const fuse = new Fuse(tokens, FUSE_OPTIONS)
|
||||
results = fuse.search(this.state.searchQuery)
|
||||
results = fuse.search(searchQuery)
|
||||
}
|
||||
|
||||
if (!results.length && web3 && web3.utils && web3.utils.isAddress(searchQuery)) {
|
||||
if (!results.length && web3 && web3.utils && isAddress(searchQuery)) {
|
||||
const { label } = selectors().getBalance(account, searchQuery)
|
||||
return [
|
||||
<div key="token-modal-no-exchange" className="token-modal__token-row token-modal__token-row--no-exchange">
|
||||
@ -165,7 +156,7 @@ class CurrencyInputPanel extends Component {
|
||||
key="token-modal-create-exchange"
|
||||
className="token-modal__token-row token-modal__token-row--create-exchange"
|
||||
onClick={() => {
|
||||
this.setState({ isShowingModal: false })
|
||||
setIsShowingModal(false)
|
||||
history.push(`/create-exchange/${searchQuery}`)
|
||||
}}
|
||||
>
|
||||
@ -191,7 +182,7 @@ class CurrencyInputPanel extends Component {
|
||||
className={classnames('token-modal__token-row', {
|
||||
'token-modal__token-row--selected': isSelected
|
||||
})}
|
||||
onClick={() => this.onTokenSelect(address)}
|
||||
onClick={() => onTokenSelect(address)}
|
||||
>
|
||||
<TokenLogo className="token-modal__token-logo" address={address} />
|
||||
<div className="token-modal__token-label">{label}</div>
|
||||
@ -200,13 +191,18 @@ class CurrencyInputPanel extends Component {
|
||||
})
|
||||
}
|
||||
|
||||
renderModal() {
|
||||
if (!this.state.isShowingModal) {
|
||||
function renderModal() {
|
||||
if (!isShowingModal) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal onClose={() => this.setState({ isShowingModal: false, searchQuery: '' })}>
|
||||
<Modal
|
||||
onClose={() => {
|
||||
setIsShowingModal(false)
|
||||
setSearchQuery('')
|
||||
}}
|
||||
>
|
||||
<CSSTransitionGroup
|
||||
transitionName="token-modal"
|
||||
transitionAppear={true}
|
||||
@ -218,38 +214,24 @@ class CurrencyInputPanel extends Component {
|
||||
<div className="token-modal">
|
||||
<div className="token-modal__search-container">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
placeholder={this.props.t('searchOrPaste')}
|
||||
placeholder={t('searchOrPaste')}
|
||||
className="token-modal__search-input"
|
||||
onChange={e => {
|
||||
this.setState({ searchQuery: e.target.value })
|
||||
setSearchQuery(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<img src={SearchIcon} className="token-modal__search-icon" alt="search" />
|
||||
</div>
|
||||
<div className="token-modal__token-list">{this.renderTokenList()}</div>
|
||||
<div className="token-modal__token-list">{renderTokenList()}</div>
|
||||
</div>
|
||||
</CSSTransitionGroup>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
renderUnlockButton() {
|
||||
const {
|
||||
t,
|
||||
selectors,
|
||||
selectedTokenAddress,
|
||||
account,
|
||||
exchangeAddresses: { fromToken },
|
||||
web3,
|
||||
disableUnlock,
|
||||
transactions,
|
||||
pendingApprovals,
|
||||
value,
|
||||
addApprovalTx,
|
||||
addPendingTx
|
||||
} = this.props
|
||||
|
||||
function renderUnlockButton() {
|
||||
if (disableUnlock || !selectedTokenAddress || selectedTokenAddress === 'ETH') {
|
||||
return
|
||||
}
|
||||
@ -294,9 +276,7 @@ class CurrencyInputPanel extends Component {
|
||||
)
|
||||
}
|
||||
|
||||
renderInput() {
|
||||
const { t, errorMessage, value, onValueChange, selectedTokenAddress, disableTokenSelect, renderInput } = this.props
|
||||
|
||||
function _renderInput() {
|
||||
if (typeof renderInput === 'function') {
|
||||
return renderInput()
|
||||
}
|
||||
@ -322,7 +302,7 @@ class CurrencyInputPanel extends Component {
|
||||
}}
|
||||
value={value}
|
||||
/>
|
||||
{this.renderUnlockButton()}
|
||||
{renderUnlockButton()}
|
||||
<button
|
||||
className={classnames('currency-input-panel__currency-select', {
|
||||
'currency-input-panel__currency-select--selected': selectedTokenAddress,
|
||||
@ -330,7 +310,7 @@ class CurrencyInputPanel extends Component {
|
||||
})}
|
||||
onClick={() => {
|
||||
if (!disableTokenSelect) {
|
||||
this.setState({ isShowingModal: true })
|
||||
setIsShowingModal(true)
|
||||
}
|
||||
}}
|
||||
>
|
||||
@ -344,35 +324,31 @@ class CurrencyInputPanel extends Component {
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { title, description, extraText, errorMessage } = this.props
|
||||
|
||||
return (
|
||||
<div className="currency-input-panel">
|
||||
<div
|
||||
className={classnames('currency-input-panel__container', {
|
||||
'currency-input-panel__container--error': errorMessage
|
||||
})}
|
||||
>
|
||||
<div className="currency-input-panel__label-row">
|
||||
<div className="currency-input-panel__label-container">
|
||||
<span className="currency-input-panel__label">{title}</span>
|
||||
<span className="currency-input-panel__label-description">{description}</span>
|
||||
</div>
|
||||
<span
|
||||
className={classnames('currency-input-panel__extra-text', {
|
||||
'currency-input-panel__extra-text--error': errorMessage
|
||||
})}
|
||||
>
|
||||
{extraText}
|
||||
</span>
|
||||
return (
|
||||
<div className="currency-input-panel">
|
||||
<div
|
||||
className={classnames('currency-input-panel__container', {
|
||||
'currency-input-panel__container--error': errorMessage
|
||||
})}
|
||||
>
|
||||
<div className="currency-input-panel__label-row">
|
||||
<div className="currency-input-panel__label-container">
|
||||
<span className="currency-input-panel__label">{title}</span>
|
||||
<span className="currency-input-panel__label-description">{description}</span>
|
||||
</div>
|
||||
{this.renderInput()}
|
||||
<span
|
||||
className={classnames('currency-input-panel__extra-text', {
|
||||
'currency-input-panel__extra-text--error': errorMessage
|
||||
})}
|
||||
>
|
||||
{extraText}
|
||||
</span>
|
||||
</div>
|
||||
{this.renderModal()}
|
||||
{_renderInput()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{renderModal()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(
|
||||
|
@ -2,6 +2,11 @@
|
||||
|
||||
.header {
|
||||
@extend %col-nowrap;
|
||||
display: block;
|
||||
|
||||
&__no-decoration {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&__top {
|
||||
@extend %row-nowrap;
|
||||
@ -27,10 +32,6 @@
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&__dialog {
|
||||
@extend %col-nowrap;
|
||||
border-radius: 0.875rem;
|
||||
@ -47,7 +48,7 @@
|
||||
}
|
||||
|
||||
&--disconnected {
|
||||
display: inherit;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import classnames from 'classnames'
|
||||
import { useWeb3Context, Connectors } from 'web3-react'
|
||||
import UAParser from 'ua-parser-js'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
|
||||
import Logo from '../Logo'
|
||||
import CoinbaseWalletLogo from '../../assets/images/coinbase-wallet-logo.png'
|
||||
import TrustLogo from '../../assets/images/trust-wallet-logo.svg'
|
||||
@ -13,6 +13,8 @@ import Web3Status from '../Web3Status'
|
||||
|
||||
import './header.scss'
|
||||
|
||||
const { Connector, InjectedConnector } = Connectors
|
||||
|
||||
const links = {
|
||||
coinbaseWallet: {
|
||||
android: 'https://play.google.com/store/apps/details?id=org.toshi',
|
||||
@ -33,8 +35,6 @@ const links = {
|
||||
}
|
||||
}
|
||||
|
||||
const ua = new UAParser(window.navigator.userAgent)
|
||||
|
||||
function getTrustLink() {
|
||||
const os = ua.getOS()
|
||||
|
||||
@ -73,99 +73,102 @@ function getMetamaskLink() {
|
||||
return links.metamask.chrome
|
||||
}
|
||||
|
||||
const ua = new UAParser(window.navigator.userAgent)
|
||||
function isMobile() {
|
||||
return ua.getDevice().type === 'mobile'
|
||||
}
|
||||
|
||||
class BlockingWarning extends Component {
|
||||
render() {
|
||||
const { t, isConnected, initialized, networkId } = this.props
|
||||
let content = []
|
||||
|
||||
const correctNetworkId = process.env.REACT_APP_NETWORK_ID || 1
|
||||
const correctNetwork = process.env.REACT_APP_NETWORK || 'Main Ethereum Network'
|
||||
|
||||
const wrongNetwork = networkId !== correctNetworkId
|
||||
|
||||
if (wrongNetwork && initialized) {
|
||||
content = [
|
||||
<div key="warning-title">{t('wrongNetwork')}</div>,
|
||||
<div key="warning-desc" className="header__dialog__description">
|
||||
{t('switchNetwork', { correctNetwork })}
|
||||
</div>
|
||||
]
|
||||
}
|
||||
|
||||
if (!isConnected && initialized) {
|
||||
content = [
|
||||
<div key="warning-title">{t('noWallet')}</div>,
|
||||
<div key="warning-desc" className="header__dialog__description">
|
||||
{isMobile() ? t('installWeb3MobileBrowser') : t('installMetamask')}
|
||||
</div>,
|
||||
<div key="warning-logos" className="header__download">
|
||||
{isMobile()
|
||||
? [
|
||||
<img
|
||||
alt="coinbase"
|
||||
src={CoinbaseWalletLogo}
|
||||
key="coinbase-wallet"
|
||||
onClick={() => window.open(getCoinbaseWalletLink(), '_blank')}
|
||||
/>,
|
||||
<img alt="trust" src={TrustLogo} key="trust" onClick={() => window.open(getTrustLink(), '_blank')} />
|
||||
]
|
||||
: [
|
||||
<img
|
||||
alt="metamask"
|
||||
src={MetamaskLogo}
|
||||
key="metamask"
|
||||
onClick={() => window.open(getMetamaskLink(), '_blank')}
|
||||
/>,
|
||||
<img alt="brave" src={BraveLogo} key="brave" onClick={() => window.open(getBraveLink(), '_blank')} />
|
||||
]}
|
||||
</div>
|
||||
]
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames('header__dialog', {
|
||||
'header__dialog--disconnected': (!isConnected || wrongNetwork) && initialized
|
||||
})}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function Header(props) {
|
||||
function BaseBlockingWarning({ title, description, children }) {
|
||||
return (
|
||||
<div className="header">
|
||||
<BlockingWarning {...props} />
|
||||
<div
|
||||
className={classnames('header__top', {
|
||||
'header--inactive': !props.isConnected
|
||||
})}
|
||||
>
|
||||
<Logo />
|
||||
<div className="header__center-group">
|
||||
<span className="header__title">Uniswap</span>
|
||||
</div>
|
||||
<Web3Status isConnected />
|
||||
<div
|
||||
className={classnames('header__dialog', {
|
||||
'header__dialog--disconnected': true
|
||||
})}
|
||||
>
|
||||
<div key="warning-title">{title}</div>
|
||||
<div key="warning-desc" className="header__dialog__description">
|
||||
{description}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
currentAddress: PropTypes.string,
|
||||
isConnected: PropTypes.bool.isRequired
|
||||
function BlockingWarning() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const correctNetwork = process.env.REACT_APP_NETWORK_NAME || 'Main Ethereum Network'
|
||||
const context = useWeb3Context()
|
||||
|
||||
if (context.error && context.error.code === Connector.errorCodes.UNSUPPORTED_NETWORK) {
|
||||
return <BaseBlockingWarning title={t('wrongNetwork')} description={t('switchNetwork', { correctNetwork })} />
|
||||
}
|
||||
|
||||
// this is an intermediate state before infura is set
|
||||
if (context.error && context.error.code === InjectedConnector.errorCodes.UNLOCK_REQUIRED) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (context.error) {
|
||||
console.error(context.error)
|
||||
return <BaseBlockingWarning title={t('disconnected')} />
|
||||
}
|
||||
|
||||
if (!context.account) {
|
||||
return (
|
||||
<BaseBlockingWarning
|
||||
title={t('noWallet')}
|
||||
description={isMobile() ? t('installWeb3MobileBrowser') : t('installMetamask')}
|
||||
>
|
||||
<div key="warning-logos" className="header__download">
|
||||
{isMobile() ? (
|
||||
<>
|
||||
<img
|
||||
alt="coinbase"
|
||||
src={CoinbaseWalletLogo}
|
||||
key="coinbase-wallet"
|
||||
onClick={() => window.open(getCoinbaseWalletLink(), '_blank')}
|
||||
/>
|
||||
<img alt="trust" src={TrustLogo} key="trust" onClick={() => window.open(getTrustLink(), '_blank')} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<img
|
||||
alt="metamask"
|
||||
src={MetamaskLogo}
|
||||
key="metamask"
|
||||
onClick={() => window.open(getMetamaskLink(), '_blank')}
|
||||
/>
|
||||
<img alt="brave" src={BraveLogo} key="brave" onClick={() => window.open(getBraveLink(), '_blank')} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</BaseBlockingWarning>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
currentAddress: state.web3connect.account,
|
||||
initialized: state.web3connect.initialized,
|
||||
isConnected: !!state.web3connect.account,
|
||||
web3: state.web3connect.web3,
|
||||
networkId: state.web3connect.networkId
|
||||
}))(withTranslation()(Header))
|
||||
export default function Header() {
|
||||
const context = useWeb3Context()
|
||||
|
||||
return (
|
||||
<div className="header">
|
||||
<BlockingWarning />
|
||||
<div className={classnames('header__top')}>
|
||||
<a className="header__no-decoration" href="https://uniswap.io" target="_blank" rel="noopener noreferrer">
|
||||
<Logo />
|
||||
</a>
|
||||
|
||||
<div className="header__center-group">
|
||||
<a className="header__no-decoration" href="https://uniswap.io" target="_blank" rel="noopener noreferrer">
|
||||
<span className="header__title">Uniswap</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<Web3Status isConnected={!!(context.active && context.account)} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
@import '../../variables';
|
||||
|
||||
.beta-message {
|
||||
@extend %row-nowrap;
|
||||
flex: 1 0 auto;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: 0.5rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid rgba($wisteria-purple, 0.4);
|
||||
background-color: rgba($wisteria-purple, 0.1);
|
||||
border-radius: 2rem;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
text-align: center;
|
||||
color: $wisteria-purple;
|
||||
|
||||
&:after {
|
||||
content: '✕';
|
||||
top: 0.5rem;
|
||||
right: 1rem;
|
||||
position: absolute;
|
||||
color: $wisteria-purple;
|
||||
}
|
||||
}
|
@ -1,57 +1,76 @@
|
||||
import React, { Component } from 'react'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import React, { useCallback } from 'react'
|
||||
import { withRouter, NavLink } from 'react-router-dom'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { dismissBetaMessage } from '../../ducks/app'
|
||||
import { Tab, Tabs } from '../Tab'
|
||||
import { useBodyKeyDown } from '../../hooks'
|
||||
|
||||
import './beta-message.scss'
|
||||
import './navigation-tabs.scss'
|
||||
|
||||
class NavigationTabs extends Component {
|
||||
static propTypes = {
|
||||
history: PropTypes.shape({
|
||||
push: PropTypes.func.isRequired
|
||||
}),
|
||||
className: PropTypes.string,
|
||||
dismissBetaMessage: PropTypes.func.isRequired,
|
||||
showBetaMessage: PropTypes.bool.isRequired
|
||||
const tabOrder = [
|
||||
{
|
||||
path: '/swap',
|
||||
textKey: 'swap',
|
||||
regex: /\/swap/
|
||||
},
|
||||
{
|
||||
path: '/send',
|
||||
textKey: 'send',
|
||||
regex: /\/send/
|
||||
},
|
||||
{
|
||||
path: 'add-liquidity',
|
||||
textKey: 'pool',
|
||||
regex: /\/add-liquidity|\/remove-liquidity|\/create-exchange.*/
|
||||
}
|
||||
]
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
selectedPath: this.props.location.pathname,
|
||||
className: '',
|
||||
showWarning: true
|
||||
}
|
||||
}
|
||||
function NavigationTabs({ location: { pathname }, history, dismissBetaMessage, showBetaMessage }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
renderTab(name, path, regex) {
|
||||
const { push } = this.props.history
|
||||
return <Tab text={name} onClick={() => push(path)} isSelected={regex.test(this.props.location.pathname)} />
|
||||
}
|
||||
const navigate = useCallback(
|
||||
direction => {
|
||||
const tabIndex = tabOrder.findIndex(({ regex }) => pathname.match(regex))
|
||||
history.push(tabOrder[(tabIndex + tabOrder.length + direction) % tabOrder.length].path)
|
||||
},
|
||||
[pathname, history]
|
||||
)
|
||||
const navigateRight = useCallback(() => {
|
||||
navigate(1)
|
||||
}, [navigate])
|
||||
const navigateLeft = useCallback(() => {
|
||||
navigate(-1)
|
||||
}, [navigate])
|
||||
|
||||
render() {
|
||||
const { t, showBetaMessage, className, dismissBetaMessage } = this.props
|
||||
return (
|
||||
<div>
|
||||
<Tabs className={className}>
|
||||
{this.renderTab(t('swap'), '/swap', /swap/)}
|
||||
{this.renderTab(t('send'), '/send', /send/)}
|
||||
{this.renderTab(t('pool'), '/add-liquidity', /add-liquidity|remove-liquidity|create-exchange/)}
|
||||
</Tabs>
|
||||
{showBetaMessage && (
|
||||
<div className="beta-message" onClick={dismissBetaMessage}>
|
||||
<span role="img" aria-label="warning">
|
||||
💀
|
||||
</span>{' '}
|
||||
{t('betaWarning')}
|
||||
</div>
|
||||
)}
|
||||
useBodyKeyDown('ArrowRight', navigateRight)
|
||||
useBodyKeyDown('ArrowLeft', navigateLeft)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="tabs">
|
||||
{tabOrder.map(({ path, textKey, regex }) => (
|
||||
<NavLink
|
||||
key={path}
|
||||
to={path}
|
||||
className="tab"
|
||||
activeClassName="tab--selected"
|
||||
isActive={(_, { pathname }) => pathname.match(regex)}
|
||||
>
|
||||
{t(textKey)}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{showBetaMessage && (
|
||||
<div className="beta-message" onClick={dismissBetaMessage}>
|
||||
<span role="img" aria-label="warning">
|
||||
💀
|
||||
</span>{' '}
|
||||
{t('betaWarning')}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(
|
||||
@ -62,5 +81,5 @@ export default withRouter(
|
||||
dispatch => ({
|
||||
dismissBetaMessage: () => dispatch(dismissBetaMessage())
|
||||
})
|
||||
)(withTranslation()(NavigationTabs))
|
||||
)(NavigationTabs)
|
||||
)
|
||||
|
58
src/components/NavigationTabs/navigation-tabs.scss
Normal file
58
src/components/NavigationTabs/navigation-tabs.scss
Normal file
@ -0,0 +1,58 @@
|
||||
@import '../../variables';
|
||||
|
||||
.beta-message {
|
||||
@extend %row-nowrap;
|
||||
cursor: pointer;
|
||||
flex: 1 0 auto;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: 0.5rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid rgba($wisteria-purple, 0.4);
|
||||
background-color: rgba($wisteria-purple, 0.1);
|
||||
border-radius: 2rem;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
text-align: center;
|
||||
color: $wisteria-purple;
|
||||
|
||||
&:after {
|
||||
content: '✕';
|
||||
top: 0.5rem;
|
||||
right: 1rem;
|
||||
position: absolute;
|
||||
color: $wisteria-purple;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs {
|
||||
@extend %row-nowrap;
|
||||
align-items: center;
|
||||
height: 2.5rem;
|
||||
background-color: $concrete-gray;
|
||||
border-radius: 3rem;
|
||||
box-shadow: 0 0 0 0.5px darken($concrete-gray, 5);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tab {
|
||||
@extend %row-nowrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 2.5rem;
|
||||
flex: 1 0 auto;
|
||||
border-radius: 3rem;
|
||||
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: $dove-gray;
|
||||
font-size: 1rem;
|
||||
|
||||
&--selected {
|
||||
background-color: $white;
|
||||
border-radius: 3rem;
|
||||
box-shadow: 0 0 0.5px 0.5px $mercury-gray;
|
||||
font-weight: 500;
|
||||
color: $royal-blue;
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import './tab.scss'
|
||||
|
||||
export const Tabs = props => {
|
||||
return <div className={classnames('tabs', props.className)}>{props.children}</div>
|
||||
}
|
||||
|
||||
export const Tab = props => {
|
||||
return (
|
||||
<div
|
||||
className={classnames('tab', {
|
||||
'tab--selected': props.isSelected
|
||||
})}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{props.text ? <span>{props.text}</span> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Tab.propTypes = {
|
||||
className: PropTypes.string,
|
||||
text: PropTypes.string,
|
||||
isSelected: PropTypes.bool,
|
||||
onClick: PropTypes.func
|
||||
}
|
||||
|
||||
Tab.defaultProps = {
|
||||
className: ''
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
@import '../../variables.scss';
|
||||
|
||||
.tabs {
|
||||
@extend %row-nowrap;
|
||||
align-items: center;
|
||||
height: 2.5rem;
|
||||
background-color: $concrete-gray;
|
||||
border-radius: 3rem;
|
||||
box-shadow: 0 0 0 0.5px darken($concrete-gray, 5);
|
||||
|
||||
.tab:first-child {
|
||||
//margin-left: -1px;
|
||||
}
|
||||
|
||||
.tab:last-child {
|
||||
//margin-right: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.tab {
|
||||
@extend %row-nowrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 2.5rem;
|
||||
flex: 1 0 auto;
|
||||
border-radius: 3rem;
|
||||
transition: 300ms ease-in-out;
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
color: $dove-gray;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
&--selected {
|
||||
background-color: $white;
|
||||
border-radius: 3rem;
|
||||
box-shadow: 0 0 0.5px 0.5px $mercury-gray;
|
||||
font-weight: 500;
|
||||
|
||||
span {
|
||||
color: $royal-blue;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,9 +6,11 @@ import Jazzicon from 'jazzicon'
|
||||
import { CSSTransitionGroup } from 'react-transition-group'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { ethers } from 'ethers'
|
||||
import './web3-status.scss'
|
||||
|
||||
import Modal from '../Modal'
|
||||
|
||||
import './web3-status.scss'
|
||||
|
||||
function getEtherscanLink(tx) {
|
||||
return `https://etherscan.io/tx/${tx}`
|
||||
}
|
||||
@ -138,7 +140,6 @@ Web3Status.defaultProps = {
|
||||
export default connect(state => {
|
||||
return {
|
||||
address: state.web3connect.account,
|
||||
isConnected: !!(state.web3connect.web3 && state.web3connect.account),
|
||||
pending: state.web3connect.transactions.pending,
|
||||
confirmed: state.web3connect.transactions.confirmed
|
||||
}
|
||||
|
@ -8,8 +8,6 @@ export const SET_WEB3_CONNECTION_STATUS = 'WEB3_CONNECTION_STATUS'
|
||||
export const CHECK_WEB3_CONNECTION = 'CHECK_WEB3_CONNECTION'
|
||||
export const SET_CURRENT_MASK_ADDRESS = 'SET_CURRENT_MASK_ADDRESS'
|
||||
|
||||
export const METAMASK_LOCKED = 'METAMASK_LOCKED'
|
||||
export const METAMASK_UNLOCKED = 'METAMASK_UNLOCKED'
|
||||
export const SET_INTERACTION_STATE = 'SET_INTERACTION_STATE'
|
||||
export const SET_NETWORK_MESSAGE = 'SET_NETWORK_MESSAGE'
|
||||
|
||||
|
@ -210,6 +210,10 @@ export const setAddresses = networkId => {
|
||||
// Rinkeby
|
||||
case 4:
|
||||
case '4':
|
||||
return {
|
||||
type: SET_ADDRESSES,
|
||||
payload: RINKEBY
|
||||
}
|
||||
default:
|
||||
return {
|
||||
type: SET_ADDRESSES,
|
||||
|
@ -1,13 +1,11 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { BigNumber as BN } from 'bignumber.js'
|
||||
import Web3 from 'web3'
|
||||
import ERC20_ABI from '../abi/erc20'
|
||||
import ERC20_WITH_BYTES_ABI from '../abi/erc20_symbol_bytes32'
|
||||
|
||||
export const INITIALIZE = 'we3connect/initialize'
|
||||
export const UPDATE_ACCOUNT = 'we3connect/updateAccount'
|
||||
export const INITIALIZE = 'web3connect/initialize'
|
||||
export const INITIALIZE_WEB3 = 'web3connect/initializeWeb3'
|
||||
export const UPDATE_ACCOUNT = 'web3connect/updateAccount'
|
||||
export const WATCH_ETH_BALANCE = 'web3connect/watchEthBalance'
|
||||
export const WATCH_TOKEN_BALANCE = 'web3connect/watchTokenBalance'
|
||||
export const UPDATE_ETH_BALANCE = 'web3connect/updateEthBalance'
|
||||
@ -24,7 +22,7 @@ const initialState = {
|
||||
web3: null,
|
||||
networkId: 0,
|
||||
initialized: false,
|
||||
account: '',
|
||||
account: null,
|
||||
balances: {
|
||||
ethereum: {}
|
||||
},
|
||||
@ -63,10 +61,6 @@ export const selectors = () => (dispatch, getState) => {
|
||||
}
|
||||
|
||||
const getBalance = (address, tokenAddress) => {
|
||||
if (process.env.NODE_ENV !== 'production' && !tokenAddress) {
|
||||
console.warn('No token address found - return ETH balance')
|
||||
}
|
||||
|
||||
if (!tokenAddress || tokenAddress === 'ETH') {
|
||||
const balance = state.balances.ethereum[address]
|
||||
if (!balance) {
|
||||
@ -106,46 +100,30 @@ const Balance = (value, label = '', decimals = 0) => ({
|
||||
decimals: +decimals
|
||||
})
|
||||
|
||||
export const initialize = () => (dispatch, getState) => {
|
||||
const { web3connect } = getState()
|
||||
export const initialize = () => async dispatch => {
|
||||
await dispatch({ type: INITIALIZE })
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (web3connect.web3) {
|
||||
resolve(web3connect.web3)
|
||||
return
|
||||
}
|
||||
export const updateNetwork = (passedProvider, networkId) => async dispatch => {
|
||||
const web3 = new Web3(passedProvider)
|
||||
|
||||
if (typeof window.ethereum !== 'undefined') {
|
||||
try {
|
||||
const web3 = new Web3(window.ethereum)
|
||||
await window.ethereum.enable()
|
||||
dispatch({
|
||||
type: INITIALIZE,
|
||||
payload: web3
|
||||
})
|
||||
resolve(web3)
|
||||
return
|
||||
} catch (error) {
|
||||
console.error('User denied access.')
|
||||
dispatch({ type: INITIALIZE })
|
||||
reject()
|
||||
return
|
||||
}
|
||||
}
|
||||
const dispatches = [
|
||||
dispatch({ type: INITIALIZE_WEB3, payload: web3 }),
|
||||
dispatch({ type: UPDATE_NETWORK_ID, payload: networkId })
|
||||
]
|
||||
|
||||
if (typeof window.web3 !== 'undefined') {
|
||||
const web3 = new Web3(window.web3.currentProvider)
|
||||
dispatch({
|
||||
type: INITIALIZE,
|
||||
payload: web3
|
||||
})
|
||||
resolve(web3)
|
||||
return
|
||||
}
|
||||
await Promise.all(dispatches)
|
||||
}
|
||||
|
||||
dispatch({ type: INITIALIZE })
|
||||
reject()
|
||||
})
|
||||
export const updateAccount = account => async dispatch => {
|
||||
if (account !== null) {
|
||||
const dispatches = [
|
||||
dispatch({ type: UPDATE_ACCOUNT, payload: account }),
|
||||
dispatch(watchBalance({ balanceOf: account }))
|
||||
]
|
||||
|
||||
await Promise.all(dispatches)
|
||||
}
|
||||
}
|
||||
|
||||
export const watchBalance = ({ balanceOf, tokenAddress }) => (dispatch, getState) => {
|
||||
@ -216,29 +194,14 @@ export const updateApprovals = ({ tokenAddress, tokenOwner, spender, balance })
|
||||
|
||||
export const sync = () => async (dispatch, getState) => {
|
||||
const { getBalance, getApprovals } = dispatch(selectors())
|
||||
const web3 = await dispatch(initialize())
|
||||
|
||||
const {
|
||||
account,
|
||||
web3,
|
||||
watched,
|
||||
contracts,
|
||||
networkId,
|
||||
transactions: { pending }
|
||||
} = getState().web3connect
|
||||
|
||||
// Sync Account
|
||||
const accounts = await web3.eth.getAccounts()
|
||||
if (account !== accounts[0]) {
|
||||
dispatch({ type: UPDATE_ACCOUNT, payload: accounts[0] })
|
||||
dispatch(watchBalance({ balanceOf: accounts[0] }))
|
||||
}
|
||||
|
||||
if (!networkId) {
|
||||
dispatch({
|
||||
type: UPDATE_NETWORK_ID,
|
||||
payload: await web3.eth.net.getId()
|
||||
})
|
||||
}
|
||||
|
||||
// Sync Ethereum Balances
|
||||
watched.balances.ethereum.forEach(async address => {
|
||||
const balance = await web3.eth.getBalance(address)
|
||||
@ -264,7 +227,6 @@ export const sync = () => async (dispatch, getState) => {
|
||||
}
|
||||
|
||||
const contract = contracts[tokenAddress] || new web3.eth.Contract(ERC20_ABI, tokenAddress)
|
||||
const contractBytes32 = contracts[tokenAddress] || new web3.eth.Contract(ERC20_WITH_BYTES_ABI, tokenAddress)
|
||||
|
||||
if (!contracts[tokenAddress]) {
|
||||
dispatch({
|
||||
@ -291,6 +253,7 @@ export const sync = () => async (dispatch, getState) => {
|
||||
.catch())
|
||||
} catch (e) {
|
||||
try {
|
||||
const contractBytes32 = new web3.eth.Contract(ERC20_WITH_BYTES_ABI, tokenAddress)
|
||||
symbol =
|
||||
symbol ||
|
||||
web3.utils.hexToString(
|
||||
@ -320,34 +283,34 @@ export const sync = () => async (dispatch, getState) => {
|
||||
// Update Approvals
|
||||
Object.entries(watched.approvals).forEach(([tokenAddress, token]) => {
|
||||
const contract = contracts[tokenAddress] || new web3.eth.Contract(ERC20_ABI, tokenAddress)
|
||||
const contractBytes32 = contracts[tokenAddress] || new web3.eth.Contract(ERC20_WITH_BYTES_ABI, tokenAddress)
|
||||
|
||||
Object.entries(token).forEach(([tokenOwnerAddress, tokenOwner]) => {
|
||||
tokenOwner.forEach(async spenderAddress => {
|
||||
const approvalBalance = getApprovals(tokenAddress, tokenOwnerAddress, spenderAddress)
|
||||
const balance = await contract.methods.allowance(tokenOwnerAddress, spenderAddress).call()
|
||||
const decimals = approvalBalance.decimals || (await contract.methods.decimals().call())
|
||||
let symbol = approvalBalance.label
|
||||
try {
|
||||
symbol = symbol || (await contract.methods.symbol().call())
|
||||
} catch (e) {
|
||||
if (tokenOwnerAddress !== null && tokenOwnerAddress !== 'null') {
|
||||
const approvalBalance = getApprovals(tokenAddress, tokenOwnerAddress, spenderAddress)
|
||||
const balance = await contract.methods.allowance(tokenOwnerAddress, spenderAddress).call()
|
||||
const decimals = approvalBalance.decimals || (await contract.methods.decimals().call())
|
||||
let symbol = approvalBalance.label
|
||||
try {
|
||||
symbol = symbol || web3.utils.hexToString(await contractBytes32.methods.symbol().call())
|
||||
} catch (err) {}
|
||||
symbol = symbol || (await contract.methods.symbol().call())
|
||||
} catch (e) {
|
||||
try {
|
||||
const contractBytes32 = new web3.eth.Contract(ERC20_WITH_BYTES_ABI, tokenAddress)
|
||||
symbol = symbol || web3.utils.hexToString(await contractBytes32.methods.symbol().call())
|
||||
} catch (err) {}
|
||||
}
|
||||
if (approvalBalance.label && approvalBalance.value.isEqualTo(BN(balance))) {
|
||||
return
|
||||
}
|
||||
dispatch(
|
||||
updateApprovals({
|
||||
tokenAddress,
|
||||
tokenOwner: tokenOwnerAddress,
|
||||
spender: spenderAddress,
|
||||
balance: Balance(balance, symbol, decimals)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (approvalBalance.label && approvalBalance.value.isEqualTo(BN(balance))) {
|
||||
return
|
||||
}
|
||||
|
||||
dispatch(
|
||||
updateApprovals({
|
||||
tokenAddress,
|
||||
tokenOwner: tokenOwnerAddress,
|
||||
spender: spenderAddress,
|
||||
balance: Balance(balance, symbol, decimals)
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -384,22 +347,25 @@ export const sync = () => async (dispatch, getState) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const startWatching = () => async (dispatch, getState) => {
|
||||
const { account } = getState().web3connect
|
||||
const timeout = !account ? 1000 : 5000
|
||||
|
||||
dispatch(sync())
|
||||
setTimeout(() => dispatch(startWatching()), timeout)
|
||||
export const startWatching = () => async dispatch => {
|
||||
await dispatch(sync())
|
||||
setTimeout(() => dispatch(startWatching()), 5000)
|
||||
}
|
||||
|
||||
export default function web3connectReducer(state = initialState, { type, payload }) {
|
||||
switch (type) {
|
||||
case INITIALIZE_WEB3:
|
||||
return {
|
||||
...state,
|
||||
web3: payload
|
||||
}
|
||||
case INITIALIZE:
|
||||
return {
|
||||
...state,
|
||||
web3: payload,
|
||||
initialized: true
|
||||
}
|
||||
case UPDATE_NETWORK_ID:
|
||||
return { ...state, networkId: payload }
|
||||
case UPDATE_ACCOUNT:
|
||||
return {
|
||||
...state,
|
||||
@ -496,8 +462,6 @@ export default function web3connectReducer(state = initialState, { type, payload
|
||||
}
|
||||
}
|
||||
}
|
||||
case UPDATE_NETWORK_ID:
|
||||
return { ...state, networkId: payload }
|
||||
case ADD_PENDING_TX:
|
||||
return {
|
||||
...state,
|
||||
@ -530,32 +494,3 @@ export default function web3connectReducer(state = initialState, { type, payload
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
// Connect Component
|
||||
export class _Web3Connect extends Component {
|
||||
static propTypes = {
|
||||
initialize: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
initialize() {}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.initialize().then(this.props.startWatching())
|
||||
}
|
||||
|
||||
render() {
|
||||
return <noscript />
|
||||
}
|
||||
}
|
||||
|
||||
export const Web3Connect = connect(
|
||||
({ web3connect }) => ({
|
||||
web3: web3connect.web3
|
||||
}),
|
||||
dispatch => ({
|
||||
initialize: () => dispatch(initialize()),
|
||||
startWatching: () => dispatch(startWatching())
|
||||
})
|
||||
)(_Web3Connect)
|
||||
|
51
src/hooks/index.js
Normal file
51
src/hooks/index.js
Normal file
@ -0,0 +1,51 @@
|
||||
import { useMemo, useEffect } from 'react'
|
||||
import { useWeb3Context } from 'web3-react'
|
||||
|
||||
import FACTORY_ABI from '../abi/factory'
|
||||
import { getSignerOrProvider, getContract } from '../utils'
|
||||
|
||||
const factoryAddresses = {
|
||||
1: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95',
|
||||
4: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36'
|
||||
}
|
||||
|
||||
export function useSignerOrProvider() {
|
||||
const { library, account } = useWeb3Context()
|
||||
|
||||
return useMemo(() => getSignerOrProvider(library, account), [library, account])
|
||||
}
|
||||
|
||||
// returns null if the contract cannot be created for any reason
|
||||
function useContract(contractAddress, ABI) {
|
||||
const signerOrProvider = useSignerOrProvider()
|
||||
|
||||
return useMemo(() => {
|
||||
try {
|
||||
return getContract(contractAddress, ABI, signerOrProvider)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}, [contractAddress, ABI, signerOrProvider])
|
||||
}
|
||||
|
||||
export function useFactoryContract() {
|
||||
const { networkId } = useWeb3Context()
|
||||
|
||||
return useContract(factoryAddresses[networkId], FACTORY_ABI)
|
||||
}
|
||||
|
||||
// modified from https://usehooks.com/useKeyPress/
|
||||
export function useBodyKeyDown(targetKey, onKeyDown, suppressOnKeyDown = false) {
|
||||
function downHandler({ target: { tagName }, key }) {
|
||||
if (key === targetKey && tagName === 'BODY' && !suppressOnKeyDown) {
|
||||
onKeyDown()
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', downHandler)
|
||||
return () => {
|
||||
window.removeEventListener('keydown', downHandler)
|
||||
}
|
||||
}, [targetKey, onKeyDown, suppressOnKeyDown])
|
||||
}
|
10
src/i18n.js
10
src/i18n.js
@ -14,13 +14,15 @@ i18next
|
||||
// https://www.i18next.com/overview/configuration-options
|
||||
.init({
|
||||
backend: {
|
||||
loadPath: './locales/{{lng}}.json'
|
||||
loadPath: '/locales/{{lng}}.json'
|
||||
},
|
||||
react: {
|
||||
useSuspense: false
|
||||
},
|
||||
lng: 'en',
|
||||
fallbackLng: 'en',
|
||||
keySeparator: false,
|
||||
interpolation: {
|
||||
escapeValue: false // not needed for react as it escapes by default
|
||||
}
|
||||
interpolation: { escapeValue: false }
|
||||
})
|
||||
|
||||
export default i18next
|
||||
|
20
src/index.js
20
src/index.js
@ -1,7 +1,8 @@
|
||||
import React, { Suspense } from 'react'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Provider } from 'react-redux'
|
||||
import ReactGA from 'react-ga'
|
||||
import Web3Provider, { Connectors } from 'web3-react'
|
||||
|
||||
import './i18n'
|
||||
import App from './pages/App'
|
||||
@ -13,15 +14,20 @@ if (process.env.NODE_ENV === 'production') {
|
||||
} else {
|
||||
ReactGA.initialize('test', { testMode: true })
|
||||
}
|
||||
|
||||
ReactGA.pageview(window.location.pathname + window.location.search)
|
||||
|
||||
const { InjectedConnector, NetworkOnlyConnector } = Connectors
|
||||
const Injected = new InjectedConnector({ supportedNetworks: [Number(process.env.REACT_APP_NETWORK_ID) || 1] })
|
||||
const Infura = new NetworkOnlyConnector({
|
||||
providerURL: process.env.REACT_APP_NETWORK_URL || ''
|
||||
})
|
||||
const connectors = { Injected, Infura }
|
||||
|
||||
ReactDOM.render(
|
||||
// catch the suspense in case translations are not yet loaded
|
||||
<Suspense fallback={null}>
|
||||
<Provider store={store}>
|
||||
<Provider store={store}>
|
||||
<Web3Provider connectors={connectors} libraryName="ethers.js">
|
||||
<App />
|
||||
</Provider>
|
||||
</Suspense>,
|
||||
</Web3Provider>
|
||||
</Provider>,
|
||||
document.getElementById('root')
|
||||
)
|
||||
|
119
src/pages/App.js
119
src/pages/App.js
@ -1,8 +1,10 @@
|
||||
import React, { Component } from 'react'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'
|
||||
import MediaQuery from 'react-responsive'
|
||||
import { Web3Connect, startWatching, initialize } from '../ducks/web3connect'
|
||||
import { useWeb3Context, Connectors } from 'web3-react'
|
||||
|
||||
import NavigationTabs from '../components/NavigationTabs'
|
||||
import { updateNetwork, updateAccount, initialize, startWatching } from '../ducks/web3connect'
|
||||
import { setAddresses } from '../ducks/addresses'
|
||||
import Header from '../components/Header'
|
||||
import Swap from './Swap'
|
||||
@ -11,63 +13,102 @@ import Pool from './Pool'
|
||||
|
||||
import './App.scss'
|
||||
|
||||
class App extends Component {
|
||||
componentWillMount() {
|
||||
const { initialize, startWatching } = this.props
|
||||
initialize().then(startWatching)
|
||||
}
|
||||
const { Connector, InjectedConnector } = Connectors
|
||||
|
||||
componentWillUpdate() {
|
||||
const { web3, setAddresses } = this.props
|
||||
function App({ initialized, setAddresses, updateNetwork, updateAccount, initialize, startWatching }) {
|
||||
const context = useWeb3Context()
|
||||
|
||||
if (this.hasSetNetworkId || !web3 || !web3.eth || !web3.eth.net || !web3.eth.net.getId) {
|
||||
return
|
||||
}
|
||||
|
||||
web3.eth.net.getId((err, networkId) => {
|
||||
if (!err && !this.hasSetNetworkId) {
|
||||
setAddresses(networkId)
|
||||
this.hasSetNetworkId = true
|
||||
// start web3-react on page-load
|
||||
useEffect(() => {
|
||||
context.setConnector('Injected', { suppressAndThrowErrors: true }).catch(error => {
|
||||
if (error.code === Connector.errorCodes.UNSUPPORTED_NETWORK) {
|
||||
context.setError(error, { connectorName: 'Injected' })
|
||||
} else {
|
||||
context.setConnector('Infura')
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
render() {
|
||||
if (!this.props.initialized) {
|
||||
return <noscript />
|
||||
// if the metamask user logs out, set the infura provider
|
||||
useEffect(() => {
|
||||
if (context.error && context.error.code === InjectedConnector.errorCodes.UNLOCK_REQUIRED) {
|
||||
context.setConnector('Infura')
|
||||
}
|
||||
}, [context.error, context.connectorName])
|
||||
|
||||
// initialize redux network
|
||||
const [reduxNetworkInitialized, setReduxNetworkInitialized] = useState(false)
|
||||
useEffect(() => {
|
||||
if (context.active) {
|
||||
setAddresses(context.networkId)
|
||||
updateNetwork(context.library._web3Provider, context.networkId)
|
||||
setReduxNetworkInitialized(true)
|
||||
}
|
||||
}, [context.active, context.networkId])
|
||||
|
||||
// initialize redux account
|
||||
const [reduxAccountInitialized, setReduxAccountInitialized] = useState(false)
|
||||
useEffect(() => {
|
||||
if (context.active) {
|
||||
updateAccount(context.account)
|
||||
setReduxAccountInitialized(true)
|
||||
}
|
||||
}, [context.active, context.account])
|
||||
|
||||
// initialize redux
|
||||
useEffect(() => {
|
||||
if (reduxNetworkInitialized && reduxAccountInitialized) {
|
||||
initialize().then(startWatching)
|
||||
}
|
||||
}, [reduxNetworkInitialized, reduxAccountInitialized])
|
||||
|
||||
// active state
|
||||
if (initialized || context.error) {
|
||||
return (
|
||||
<div id="app-container">
|
||||
<MediaQuery query="(min-width: 768px)">
|
||||
<Header />
|
||||
</MediaQuery>
|
||||
<Web3Connect />
|
||||
<BrowserRouter>
|
||||
<Header />
|
||||
{/* this is an intermediate state before infura is set */}
|
||||
{initialized && (!context.error || context.error.code === InjectedConnector.errorCodes.UNLOCK_REQUIRED) && (
|
||||
<div className="app__wrapper">
|
||||
<Switch>
|
||||
<Route exact path="/swap" component={Swap} />
|
||||
<Route exact path="/send" component={Send} />
|
||||
<Route exact path="/add-liquidity" component={Pool} />
|
||||
<Route exact path="/remove-liquidity" component={Pool} />
|
||||
<Route exact path="/create-exchange/:tokenAddress?" component={Pool} />
|
||||
<Redirect exact from="/" to="/swap" />
|
||||
</Switch>
|
||||
<div className="body">
|
||||
<div className="body__content">
|
||||
<BrowserRouter>
|
||||
<NavigationTabs />
|
||||
<Switch>
|
||||
<Route exact strict path="/swap" component={Swap} />
|
||||
<Route exact strict path="/send" component={Send} />
|
||||
<Route
|
||||
path={[
|
||||
'/add-liquidity',
|
||||
'/remove-liquidity',
|
||||
'/create-exchange',
|
||||
'/create-exchange/:tokenAddress?'
|
||||
]}
|
||||
component={Pool}
|
||||
/>
|
||||
<Redirect to="/swap" />
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// loading state
|
||||
return null
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
account: state.web3connect.account,
|
||||
initialized: state.web3connect.initialized,
|
||||
web3: state.web3connect.web3
|
||||
initialized: state.web3connect.initialized
|
||||
}),
|
||||
dispatch => ({
|
||||
setAddresses: networkId => dispatch(setAddresses(networkId)),
|
||||
updateNetwork: (passedProvider, networkId) => dispatch(updateNetwork(passedProvider, networkId)),
|
||||
updateAccount: account => dispatch(updateAccount(account)),
|
||||
initialize: () => dispatch(initialize()),
|
||||
startWatching: () => dispatch(startWatching())
|
||||
})
|
||||
|
@ -7,6 +7,7 @@
|
||||
margin: auto;
|
||||
max-width: 560px;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
& > div {
|
||||
position: absolute;
|
||||
@ -24,3 +25,16 @@
|
||||
|
||||
@extend %col-nowrap;
|
||||
}
|
||||
|
||||
.body {
|
||||
@extend %col-nowrap;
|
||||
height: 100%;
|
||||
background-color: $white;
|
||||
|
||||
&__content {
|
||||
padding: 1rem 0.75rem;
|
||||
flex: 1 1 auto;
|
||||
height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,25 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Provider } from 'react-redux'
|
||||
import Web3Provider, { Connectors } from 'web3-react'
|
||||
|
||||
import App from './App'
|
||||
import store from '../store'
|
||||
|
||||
// TODO, fix this hacky workaround
|
||||
const { NetworkOnlyConnector } = Connectors
|
||||
const Injected = new NetworkOnlyConnector({
|
||||
providerURL: process.env.REACT_APP_NETWORK_URL
|
||||
})
|
||||
export const connectors = { Injected }
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div')
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
<Web3Provider connectors={connectors} libraryName="ethers.js">
|
||||
<App />
|
||||
</Web3Provider>
|
||||
</Provider>,
|
||||
div
|
||||
)
|
||||
|
@ -2,29 +2,29 @@ import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { withTranslation, useTranslation } from 'react-i18next'
|
||||
import { useWeb3Context } from 'web3-react'
|
||||
import ReactGA from 'react-ga'
|
||||
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import OversizedPanel from '../../components/OversizedPanel'
|
||||
import ContextualInfo from '../../components/ContextualInfo'
|
||||
import NavigationTabs from '../../components/NavigationTabs'
|
||||
import { selectors, addPendingTx } from '../../ducks/web3connect'
|
||||
import PlusBlue from '../../assets/images/plus-blue.svg'
|
||||
import PlusGrey from '../../assets/images/plus-grey.svg'
|
||||
import { getBlockDeadline } from '../../helpers/web3-utils'
|
||||
import { retry } from '../../helpers/promise-utils'
|
||||
import ModeSelector from './ModeSelector'
|
||||
import { BigNumber as BN } from 'bignumber.js'
|
||||
import EXCHANGE_ABI from '../../abi/exchange'
|
||||
|
||||
import './pool.scss'
|
||||
import ReactGA from 'react-ga'
|
||||
|
||||
const INPUT = 0
|
||||
const OUTPUT = 1
|
||||
|
||||
class AddLiquidity extends Component {
|
||||
static propTypes = {
|
||||
isConnected: PropTypes.bool.isRequired,
|
||||
account: PropTypes.string.isRequired,
|
||||
account: PropTypes.string,
|
||||
selectors: PropTypes.func.isRequired,
|
||||
balances: PropTypes.object.isRequired,
|
||||
exchangeAddresses: PropTypes.shape({
|
||||
@ -50,11 +50,10 @@ class AddLiquidity extends Component {
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { t, isConnected, account, exchangeAddresses, balances, web3 } = this.props
|
||||
const { t, account, exchangeAddresses, balances, web3 } = this.props
|
||||
const { inputValue, outputValue, inputCurrency, outputCurrency, lastEditedField } = this.state
|
||||
|
||||
return (
|
||||
isConnected !== nextProps.isConnected ||
|
||||
t !== nextProps.t ||
|
||||
account !== nextProps.account ||
|
||||
exchangeAddresses !== nextProps.exchangeAddresses ||
|
||||
@ -434,6 +433,7 @@ class AddLiquidity extends Component {
|
||||
renderSummary(inputError, outputError) {
|
||||
const {
|
||||
t,
|
||||
account,
|
||||
selectors,
|
||||
exchangeAddresses: { fromToken }
|
||||
} = this.props
|
||||
@ -444,7 +444,11 @@ class AddLiquidity extends Component {
|
||||
let contextualInfo = ''
|
||||
let isError = false
|
||||
const { label } = selectors().getTokenBalance(outputCurrency, fromToken[outputCurrency])
|
||||
if (inputError || outputError) {
|
||||
|
||||
if (!account) {
|
||||
contextualInfo = t('noWallet')
|
||||
isError = true
|
||||
} else if (inputError || outputError) {
|
||||
contextualInfo = inputError || outputError
|
||||
isError = true
|
||||
} else if (!inputCurrency || !outputCurrency) {
|
||||
@ -493,7 +497,7 @@ class AddLiquidity extends Component {
|
||||
|
||||
if (this.isNewExchange()) {
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<div className="pool__summary-item">
|
||||
{t('youAreAdding')} {b(`${inputValue} ETH`)} {t('and')} {b(`${outputValue} ${label}`)} {t('intoPool')}
|
||||
</div>
|
||||
@ -510,7 +514,7 @@ class AddLiquidity extends Component {
|
||||
{t('youWillMint')} {b(`${inputValue}`)} {t('liquidityTokens')}
|
||||
</div>
|
||||
<div className="pool__summary-item">{t('totalSupplyIs0')}</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -523,7 +527,7 @@ class AddLiquidity extends Component {
|
||||
const adjTotalSupply = totalSupply.dividedBy(10 ** poolTokenDecimals)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<div className="pool__summary-modal__item">
|
||||
{t('youAreAdding')} {b(`${+BN(inputValue).toFixed(7)} ETH`)} {t('and')}{' '}
|
||||
{b(`${+minOutput.toFixed(7)} - ${+maxOutput.toFixed(7)} ${label}`)} {t('intoPool')}
|
||||
@ -538,14 +542,13 @@ class AddLiquidity extends Component {
|
||||
{t('tokenWorth')} {b(+ethReserve.dividedBy(totalSupply).toFixed(7))} ETH {t('and')}{' '}
|
||||
{b(+tokenReserve.dividedBy(totalSupply).toFixed(7))} {label}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
isConnected,
|
||||
exchangeAddresses: { fromToken },
|
||||
selectors
|
||||
} = this.props
|
||||
@ -555,19 +558,8 @@ class AddLiquidity extends Component {
|
||||
const { inputError, outputError, isValid } = this.validate()
|
||||
const { label } = selectors().getTokenBalance(outputCurrency, fromToken[outputCurrency])
|
||||
|
||||
return [
|
||||
<div
|
||||
key="content"
|
||||
className={classnames('swap__content', {
|
||||
'swap--inactive': !isConnected
|
||||
})}
|
||||
>
|
||||
<NavigationTabs
|
||||
className={classnames('header__navigation', {
|
||||
'header--inactive': !isConnected
|
||||
})}
|
||||
/>
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.isNewExchange() ? (
|
||||
<div className="pool__new-exchange-warning">
|
||||
<div className="pool__new-exchange-warning-text">
|
||||
@ -579,7 +571,7 @@ class AddLiquidity extends Component {
|
||||
<div className="pool__new-exchange-warning-text">{t('initialExchangeRate', { label })}</div>
|
||||
</div>
|
||||
) : null}
|
||||
<ModeSelector title={t('addLiquidity')} />
|
||||
|
||||
<CurrencyInputPanel
|
||||
title={t('deposit')}
|
||||
extraText={this.getBalance(inputCurrency)}
|
||||
@ -615,26 +607,33 @@ class AddLiquidity extends Component {
|
||||
<OversizedPanel hideBottom>{this.renderInfo()}</OversizedPanel>
|
||||
{this.renderSummary(inputError, outputError)}
|
||||
<div className="pool__cta-container">
|
||||
<button
|
||||
className={classnames('pool__cta-btn', {
|
||||
'swap--inactive': !this.props.isConnected,
|
||||
'pool__cta-btn--inactive': !isValid
|
||||
})}
|
||||
disabled={!isValid}
|
||||
onClick={this.onAddLiquidity}
|
||||
>
|
||||
{t('addLiquidity')}
|
||||
</button>
|
||||
<AddLiquidityButton callOnClick={this.onAddLiquidity} isValid={isValid} />
|
||||
</div>
|
||||
</div>
|
||||
]
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function AddLiquidityButton({ callOnClick, isValid }) {
|
||||
const { t } = useTranslation()
|
||||
const context = useWeb3Context()
|
||||
|
||||
const isActive = context.active && context.account
|
||||
return (
|
||||
<button
|
||||
className={classnames('pool__cta-btn', {
|
||||
'pool__cta-btn--inactive': !isActive
|
||||
})}
|
||||
disabled={!isValid}
|
||||
onClick={callOnClick}
|
||||
>
|
||||
{t('addLiquidity')}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
isConnected:
|
||||
Boolean(state.web3connect.account) && state.web3connect.networkId === (process.env.REACT_APP_NETWORK_ID || 1),
|
||||
account: state.web3connect.account,
|
||||
balances: state.web3connect.balances,
|
||||
web3: state.web3connect.web3,
|
||||
|
@ -1,243 +1,158 @@
|
||||
import React, { Component } from 'react'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { selectors, addPendingTx } from '../../ducks/web3connect'
|
||||
import { ethers } from 'ethers'
|
||||
import classnames from 'classnames'
|
||||
import NavigationTabs from '../../components/NavigationTabs'
|
||||
import ModeSelector from './ModeSelector'
|
||||
import { withRouter } from 'react-router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useWeb3Context } from 'web3-react'
|
||||
|
||||
import { addPendingTx } from '../../ducks/web3connect'
|
||||
import AddressInputPanel from '../../components/AddressInputPanel'
|
||||
import OversizedPanel from '../../components/OversizedPanel'
|
||||
import FACTORY_ABI from '../../abi/factory'
|
||||
import { addExchange } from '../../ducks/addresses'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useSignerOrProvider, useFactoryContract } from '../../hooks'
|
||||
import { isAddress, getTokenDetails, getExchangeDetails, errorCodes } from '../../utils'
|
||||
|
||||
class CreateExchange extends Component {
|
||||
static propTypes = {
|
||||
web3: PropTypes.object,
|
||||
selectors: PropTypes.func.isRequired,
|
||||
addExchange: PropTypes.func.isRequired,
|
||||
account: PropTypes.string,
|
||||
isConnected: PropTypes.bool.isRequired,
|
||||
factoryAddress: PropTypes.string.isRequired,
|
||||
exchangeAddresses: PropTypes.shape({
|
||||
fromToken: PropTypes.object.isRequired
|
||||
}).isRequired
|
||||
}
|
||||
function CreateExchange({ history, location, addExchange, addPendingTx }) {
|
||||
const { t } = useTranslation()
|
||||
const context = useWeb3Context()
|
||||
const signerOrProvider = useSignerOrProvider()
|
||||
const factory = useFactoryContract()
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
const {
|
||||
match: {
|
||||
params: { tokenAddress }
|
||||
}
|
||||
} = this.props
|
||||
const [tokenAddress, setTokenAddress] = useState(location.state && location.state.tokenAddress)
|
||||
const [errorMessage, _setErrorMessage] = useState(context.account ? undefined : t('noWallet'))
|
||||
const [tokenDetails, setTokenDetails] = useState()
|
||||
|
||||
this.state = {
|
||||
tokenAddress,
|
||||
label: '',
|
||||
decimals: 0
|
||||
}
|
||||
}
|
||||
|
||||
validate() {
|
||||
const { tokenAddress } = this.state
|
||||
const {
|
||||
t,
|
||||
web3,
|
||||
account,
|
||||
selectors,
|
||||
factoryAddress,
|
||||
exchangeAddresses: { fromToken },
|
||||
addExchange
|
||||
} = this.props
|
||||
|
||||
let isValid = true
|
||||
let errorMessage = ''
|
||||
|
||||
if (!tokenAddress) {
|
||||
return {
|
||||
isValid: false
|
||||
}
|
||||
}
|
||||
|
||||
if (web3 && web3.utils && !web3.utils.isAddress(tokenAddress)) {
|
||||
return {
|
||||
isValid: false,
|
||||
errorMessage: t('invalidTokenAddress')
|
||||
}
|
||||
}
|
||||
|
||||
const { label, decimals } = selectors().getBalance(account, tokenAddress)
|
||||
const factory = new web3.eth.Contract(FACTORY_ABI, factoryAddress)
|
||||
const exchangeAddress = fromToken[tokenAddress]
|
||||
|
||||
if (!exchangeAddress) {
|
||||
factory.methods.getExchange(tokenAddress).call((err, data) => {
|
||||
if (!err && data !== '0x0000000000000000000000000000000000000000') {
|
||||
addExchange({ label, tokenAddress, exchangeAddress: data })
|
||||
}
|
||||
})
|
||||
// wrap _setErrorMessage to ensure an account is in context
|
||||
function setErrorMessage(value) {
|
||||
if (value) {
|
||||
_setErrorMessage(value)
|
||||
} else if (!context.account) {
|
||||
_setErrorMessage(t('noWallet'))
|
||||
} else {
|
||||
errorMessage = t('exchangeExists', { label })
|
||||
}
|
||||
|
||||
if (!label) {
|
||||
errorMessage = t('invalidSymbol')
|
||||
}
|
||||
|
||||
if (!decimals) {
|
||||
errorMessage = t('invalidDecimals')
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: isValid && !errorMessage,
|
||||
errorMessage
|
||||
_setErrorMessage()
|
||||
}
|
||||
}
|
||||
|
||||
onChange = tokenAddress => {
|
||||
const { selectors, account, web3 } = this.props
|
||||
if (web3 && web3.utils && web3.utils.isAddress(tokenAddress)) {
|
||||
const { label, decimals } = selectors().getBalance(account, tokenAddress)
|
||||
this.setState({
|
||||
label,
|
||||
decimals,
|
||||
tokenAddress
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
label: '',
|
||||
decimals: 0,
|
||||
tokenAddress
|
||||
})
|
||||
// clear state, if it exists
|
||||
useEffect(() => {
|
||||
if (location.state) {
|
||||
history.replace(location.pathname)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
onCreateExchange = () => {
|
||||
const { tokenAddress } = this.state
|
||||
const { account, web3, factoryAddress } = this.props
|
||||
// handle changes to tokenAddress
|
||||
useEffect(() => {
|
||||
let stale = false
|
||||
|
||||
if (web3 && web3.utils && !web3.utils.isAddress(tokenAddress)) {
|
||||
return
|
||||
// happy path
|
||||
if (isAddress(tokenAddress)) {
|
||||
const tokenDetailsPromise = getTokenDetails(tokenAddress, signerOrProvider)
|
||||
const exchangeDetailsPromise = getExchangeDetails(context.networkId, tokenAddress, signerOrProvider)
|
||||
|
||||
Promise.all([tokenDetailsPromise, exchangeDetailsPromise])
|
||||
.then(([tokenDetails, exchangeDetails]) => {
|
||||
if (!stale) {
|
||||
if (exchangeDetails.exchangeAddress !== ethers.constants.AddressZero) {
|
||||
addExchange({
|
||||
tokenAddress,
|
||||
label: tokenDetails.symbol,
|
||||
exchangeAddress: exchangeDetails.exchangeAddress
|
||||
})
|
||||
setErrorMessage(t('exchangeExists', { tokenAddress }))
|
||||
}
|
||||
setTokenDetails(tokenDetails)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (!stale) {
|
||||
if (error.code === errorCodes.TOKEN_DETAILS_DECIMALS) {
|
||||
setErrorMessage(t('invalidDecimals'))
|
||||
} else if (error.code === errorCodes.TOKEN_DETAILS_SYMBOL) {
|
||||
setErrorMessage(t('invalidSymbol'))
|
||||
} else {
|
||||
setErrorMessage(t('invalidTokenAddress'))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// is tokenAddress is empty, there's no error
|
||||
else if (tokenAddress === undefined || tokenAddress === '') {
|
||||
setErrorMessage()
|
||||
}
|
||||
// tokenAddress is not a proper address
|
||||
else {
|
||||
setErrorMessage(t('invalidTokenAddress'))
|
||||
}
|
||||
|
||||
const factory = new web3.eth.Contract(FACTORY_ABI, factoryAddress)
|
||||
factory.methods.createExchange(tokenAddress).send({ from: account }, (err, data) => {
|
||||
if (!err) {
|
||||
this.setState({
|
||||
label: '',
|
||||
decimals: 0,
|
||||
tokenAddress: ''
|
||||
})
|
||||
this.props.addPendingTx(data)
|
||||
ReactGA.event({
|
||||
category: 'Pool',
|
||||
action: 'CreateExchange'
|
||||
})
|
||||
}
|
||||
return () => {
|
||||
stale = true
|
||||
setErrorMessage()
|
||||
setTokenDetails()
|
||||
}
|
||||
}, [tokenAddress, signerOrProvider, context.networkId])
|
||||
|
||||
async function createExchange() {
|
||||
const estimatedGasLimit = await factory.estimate.createExchange(tokenAddress)
|
||||
|
||||
factory.createExchange(tokenAddress, { gasLimit: estimatedGasLimit }).then(details => {
|
||||
addPendingTx(details.hash)
|
||||
setErrorMessage()
|
||||
setTokenAddress()
|
||||
ReactGA.event({
|
||||
category: 'Pool',
|
||||
action: 'CreateExchange'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
renderSummary() {
|
||||
const { tokenAddress } = this.state
|
||||
const { errorMessage } = this.validate()
|
||||
const isValid = isAddress(tokenAddress) && !errorMessage && tokenDetails && tokenDetails.tokenAddress === tokenAddress
|
||||
|
||||
if (!tokenAddress) {
|
||||
return (
|
||||
<div className="create-exchange__summary-panel">
|
||||
<div className="create-exchange__summary-text">{this.props.t('enterTokenCont')}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (errorMessage) {
|
||||
return (
|
||||
<div className="create-exchange__summary-panel">
|
||||
<div className="create-exchange__summary-text create-exchange--error">{errorMessage}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
render() {
|
||||
const { tokenAddress } = this.state
|
||||
const { t, isConnected, account, selectors, web3 } = this.props
|
||||
const { isValid, errorMessage } = this.validate()
|
||||
let label, decimals
|
||||
|
||||
if (web3 && web3.utils && web3.utils.isAddress(tokenAddress)) {
|
||||
const { label: _label, decimals: _decimals } = selectors().getBalance(account, tokenAddress)
|
||||
label = _label
|
||||
decimals = _decimals
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key="content"
|
||||
className={classnames('swap__content', {
|
||||
'swap--inactive': !isConnected
|
||||
})}
|
||||
>
|
||||
<NavigationTabs
|
||||
className={classnames('header__navigation', {
|
||||
'header--inactive': !isConnected
|
||||
})}
|
||||
/>
|
||||
<ModeSelector title={t('createExchange')} />
|
||||
<AddressInputPanel
|
||||
title={t('tokenAddress')}
|
||||
value={tokenAddress}
|
||||
onChange={this.onChange}
|
||||
errorMessage={errorMessage}
|
||||
/>
|
||||
<OversizedPanel hideBottom>
|
||||
<div className="pool__summary-panel">
|
||||
<div className="pool__exchange-rate-wrapper">
|
||||
<span className="pool__exchange-rate">{t('label')}</span>
|
||||
<span>{label || ' - '}</span>
|
||||
</div>
|
||||
<div className="pool__exchange-rate-wrapper">
|
||||
<span className="swap__exchange-rate">{t('decimals')}</span>
|
||||
<span>{decimals || ' - '}</span>
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
<AddressInputPanel
|
||||
title={t('tokenAddress')}
|
||||
value={tokenAddress}
|
||||
onChange={input => setTokenAddress(input)}
|
||||
errorMessage={errorMessage === t('noWallet') ? '' : errorMessage}
|
||||
/>
|
||||
<OversizedPanel hideBottom>
|
||||
<div className="pool__summary-panel">
|
||||
<div className="pool__exchange-rate-wrapper">
|
||||
<span className="pool__exchange-rate">{t('symbol')}</span>
|
||||
<span>{tokenDetails ? tokenDetails.symbol : ' - '}</span>
|
||||
</div>
|
||||
</OversizedPanel>
|
||||
{this.renderSummary()}
|
||||
<div className="pool__cta-container">
|
||||
<button
|
||||
className={classnames('pool__cta-btn', {
|
||||
'swap--inactive': !isConnected
|
||||
})}
|
||||
disabled={!isValid}
|
||||
onClick={this.onCreateExchange}
|
||||
>
|
||||
{t('createExchange')}
|
||||
</button>
|
||||
<div className="pool__exchange-rate-wrapper">
|
||||
<span className="swap__exchange-rate">{t('decimals')}</span>
|
||||
<span>{tokenDetails ? tokenDetails.decimals : ' - '}</span>
|
||||
</div>
|
||||
</div>
|
||||
</OversizedPanel>
|
||||
<div className="create-exchange__summary-panel">
|
||||
<div
|
||||
className={classnames('create-exchange__summary-text', {
|
||||
'create-exchange--error': !!errorMessage
|
||||
})}
|
||||
>
|
||||
{!!errorMessage ? errorMessage : t('enterTokenCont')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className="pool__cta-container">
|
||||
<button className="pool__cta-btn" disabled={!isValid} onClick={createExchange}>
|
||||
{t('createExchange')}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(
|
||||
connect(
|
||||
state => ({
|
||||
isConnected:
|
||||
Boolean(state.web3connect.account) && state.web3connect.networkId === (process.env.REACT_APP_NETWORK_ID || 1),
|
||||
account: state.web3connect.account,
|
||||
balances: state.web3connect.balances,
|
||||
web3: state.web3connect.web3,
|
||||
exchangeAddresses: state.addresses.exchangeAddresses,
|
||||
factoryAddress: state.addresses.factoryAddress
|
||||
}),
|
||||
undefined,
|
||||
dispatch => ({
|
||||
selectors: () => dispatch(selectors()),
|
||||
addExchange: opts => dispatch(addExchange(opts)),
|
||||
addPendingTx: id => dispatch(addPendingTx(id))
|
||||
})
|
||||
)(withTranslation()(CreateExchange))
|
||||
)(CreateExchange)
|
||||
)
|
||||
|
@ -1,83 +1,103 @@
|
||||
import React, { Component } from 'react'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import { withRouter, NavLink } from 'react-router-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { CSSTransitionGroup } from 'react-transition-group'
|
||||
|
||||
import OversizedPanel from '../../components/OversizedPanel'
|
||||
import Dropdown from '../../assets/images/dropdown-blue.svg'
|
||||
import Modal from '../../components/Modal'
|
||||
import { CSSTransitionGroup } from 'react-transition-group'
|
||||
import { useBodyKeyDown } from '../../hooks'
|
||||
|
||||
const ADD = 'Add Liquidity'
|
||||
const REMOVE = 'Remove Liquidity'
|
||||
const CREATE = 'Create Exchange'
|
||||
import './pool.scss'
|
||||
|
||||
class ModeSelector extends Component {
|
||||
state = {
|
||||
isShowingModal: false,
|
||||
selected: ADD
|
||||
const poolTabOrder = [
|
||||
{
|
||||
path: '/add-liquidity',
|
||||
textKey: 'addLiquidity',
|
||||
regex: /\/add-liquidity/
|
||||
},
|
||||
{
|
||||
path: '/remove-liquidity',
|
||||
textKey: 'removeLiquidity',
|
||||
regex: /\/remove-liquidity/
|
||||
},
|
||||
{
|
||||
path: '/create-exchange',
|
||||
textKey: 'createExchange',
|
||||
regex: /\/create-exchange.*/
|
||||
}
|
||||
]
|
||||
|
||||
changeView(view) {
|
||||
const { history } = this.props
|
||||
function ModeSelector({ location: { pathname }, history }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
this.setState({
|
||||
isShowingModal: false,
|
||||
selected: view
|
||||
})
|
||||
const [isShowingModal, setIsShowingModal] = useState(false)
|
||||
|
||||
switch (view) {
|
||||
case ADD:
|
||||
return history.push('/add-liquidity')
|
||||
case REMOVE:
|
||||
return history.push('/remove-liquidity')
|
||||
case CREATE:
|
||||
return history.push('/create-exchange')
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
const activeTabKey = poolTabOrder[poolTabOrder.findIndex(({ regex }) => pathname.match(regex))].textKey
|
||||
|
||||
renderModal() {
|
||||
if (!this.state.isShowingModal) {
|
||||
return
|
||||
}
|
||||
const navigate = useCallback(
|
||||
direction => {
|
||||
const tabIndex = poolTabOrder.findIndex(({ regex }) => pathname.match(regex))
|
||||
history.push(poolTabOrder[(tabIndex + poolTabOrder.length + direction) % poolTabOrder.length].path)
|
||||
},
|
||||
[pathname, history]
|
||||
)
|
||||
const navigateRight = useCallback(() => {
|
||||
navigate(1)
|
||||
}, [navigate])
|
||||
const navigateLeft = useCallback(() => {
|
||||
navigate(-1)
|
||||
}, [navigate])
|
||||
|
||||
return (
|
||||
<Modal onClose={() => this.setState({ isShowingModal: false })}>
|
||||
<CSSTransitionGroup
|
||||
transitionName="pool-modal"
|
||||
transitionAppear={true}
|
||||
transitionLeave={true}
|
||||
transitionAppearTimeout={200}
|
||||
transitionLeaveTimeout={200}
|
||||
transitionEnterTimeout={200}
|
||||
useBodyKeyDown('ArrowDown', navigateRight, isShowingModal)
|
||||
useBodyKeyDown('ArrowUp', navigateLeft, isShowingModal)
|
||||
|
||||
return (
|
||||
<OversizedPanel hideTop>
|
||||
<div
|
||||
className="pool__liquidity-container"
|
||||
onClick={() => {
|
||||
setIsShowingModal(true)
|
||||
}}
|
||||
>
|
||||
<span className="pool__liquidity-label">{t(activeTabKey)}</span>
|
||||
<img src={Dropdown} alt="dropdown" />
|
||||
</div>
|
||||
{isShowingModal && (
|
||||
<Modal
|
||||
onClose={() => {
|
||||
setIsShowingModal(false)
|
||||
}}
|
||||
>
|
||||
<div className="pool-modal">
|
||||
<div className="pool-modal__item" onClick={() => this.changeView(ADD)}>
|
||||
{this.props.t('addLiquidity')}
|
||||
<CSSTransitionGroup
|
||||
transitionName="pool-modal"
|
||||
transitionAppear={true}
|
||||
transitionLeave={true}
|
||||
transitionAppearTimeout={200}
|
||||
transitionLeaveTimeout={200}
|
||||
transitionEnterTimeout={200}
|
||||
>
|
||||
<div className="pool-modal">
|
||||
{poolTabOrder.map(({ path, textKey, regex }) => (
|
||||
<NavLink
|
||||
key={path}
|
||||
to={path}
|
||||
className="pool-modal__item"
|
||||
activeClassName="pool-modal__item--selected"
|
||||
isActive={(_, { pathname }) => pathname.match(regex)}
|
||||
onClick={() => {
|
||||
setIsShowingModal(false)
|
||||
}}
|
||||
>
|
||||
{t(textKey)}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
<div className="pool-modal__item" onClick={() => this.changeView(REMOVE)}>
|
||||
{this.props.t('removeLiquidity')}
|
||||
</div>
|
||||
<div className="pool-modal__item" onClick={() => this.changeView(CREATE)}>
|
||||
{this.props.t('createExchange')}
|
||||
</div>
|
||||
</div>
|
||||
</CSSTransitionGroup>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<OversizedPanel hideTop>
|
||||
<div className="pool__liquidity-container" onClick={() => this.setState({ isShowingModal: true })}>
|
||||
<span className="pool__liquidity-label">{this.props.title}</span>
|
||||
<img src={Dropdown} alt="dropdown" />
|
||||
</div>
|
||||
{this.renderModal()}
|
||||
</OversizedPanel>
|
||||
)
|
||||
}
|
||||
</CSSTransitionGroup>
|
||||
</Modal>
|
||||
)}
|
||||
</OversizedPanel>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(withTranslation()(ModeSelector))
|
||||
export default withRouter(ModeSelector)
|
||||
|
@ -3,9 +3,10 @@ import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import { connect } from 'react-redux'
|
||||
import { BigNumber as BN } from 'bignumber.js'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import NavigationTabs from '../../components/NavigationTabs'
|
||||
import ModeSelector from './ModeSelector'
|
||||
import { withTranslation, useTranslation } from 'react-i18next'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useWeb3Context } from 'web3-react'
|
||||
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import { selectors, addPendingTx } from '../../ducks/web3connect'
|
||||
import ContextualInfo from '../../components/ContextualInfo'
|
||||
@ -15,7 +16,6 @@ import ArrowDownGrey from '../../assets/images/arrow-down-grey.svg'
|
||||
import { getBlockDeadline } from '../../helpers/web3-utils'
|
||||
import { retry } from '../../helpers/promise-utils'
|
||||
import EXCHANGE_ABI from '../../abi/exchange'
|
||||
import ReactGA from 'react-ga'
|
||||
|
||||
class RemoveLiquidity extends Component {
|
||||
static propTypes = {
|
||||
@ -173,6 +173,7 @@ class RemoveLiquidity extends Component {
|
||||
renderSummary(errorMessage) {
|
||||
const {
|
||||
t,
|
||||
account,
|
||||
selectors,
|
||||
exchangeAddresses: { fromToken }
|
||||
} = this.props
|
||||
@ -181,7 +182,10 @@ class RemoveLiquidity extends Component {
|
||||
let contextualInfo = ''
|
||||
let isError = false
|
||||
|
||||
if (errorMessage) {
|
||||
if (!account) {
|
||||
contextualInfo = t('noWallet')
|
||||
isError = true
|
||||
} else if (errorMessage) {
|
||||
contextualInfo = errorMessage
|
||||
isError = true
|
||||
} else if (!tokenAddress) {
|
||||
@ -366,23 +370,12 @@ class RemoveLiquidity extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t, isConnected } = this.props
|
||||
const { t } = this.props
|
||||
const { tokenAddress, value } = this.state
|
||||
const { isValid, errorMessage } = this.validate()
|
||||
|
||||
return [
|
||||
<div
|
||||
key="content"
|
||||
className={classnames('swap__content', {
|
||||
'swap--inactive': !isConnected
|
||||
})}
|
||||
>
|
||||
<NavigationTabs
|
||||
className={classnames('header__navigation', {
|
||||
'header--inactive': !isConnected
|
||||
})}
|
||||
/>
|
||||
<ModeSelector title={t('removeLiquidity')} />
|
||||
return (
|
||||
<>
|
||||
<CurrencyInputPanel
|
||||
title={t('poolTokens')}
|
||||
extraText={this.getBalance(tokenAddress)}
|
||||
@ -401,26 +394,33 @@ class RemoveLiquidity extends Component {
|
||||
{this.renderOutput()}
|
||||
{this.renderSummary(errorMessage)}
|
||||
<div className="pool__cta-container">
|
||||
<button
|
||||
className={classnames('pool__cta-btn', {
|
||||
'swap--inactive': !isConnected,
|
||||
'pool__cta-btn--inactive': !isValid
|
||||
})}
|
||||
disabled={!isValid}
|
||||
onClick={this.onRemoveLiquidity}
|
||||
>
|
||||
{t('removeLiquidity')}
|
||||
</button>
|
||||
<RemoveLiquidityButton callOnClick={this.onRemoveLiquidity} isValid={isValid} />
|
||||
</div>
|
||||
</div>
|
||||
]
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function RemoveLiquidityButton({ callOnClick, isValid }) {
|
||||
const { t } = useTranslation()
|
||||
const context = useWeb3Context()
|
||||
|
||||
const isActive = context.active && context.account
|
||||
return (
|
||||
<button
|
||||
className={classnames('pool__cta-btn', {
|
||||
'pool__cta-btn--inactive': !isActive
|
||||
})}
|
||||
disabled={!isValid}
|
||||
onClick={callOnClick}
|
||||
>
|
||||
{t('removeLiquidity')}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
isConnected:
|
||||
Boolean(state.web3connect.account) && state.web3connect.networkId === (process.env.REACT_APP_NETWORK_ID || 1),
|
||||
web3: state.web3connect.web3,
|
||||
balances: state.web3connect.balances,
|
||||
account: state.web3connect.account,
|
||||
|
@ -1,31 +1,36 @@
|
||||
import React, { Component } from 'react'
|
||||
import Header from '../../components/Header'
|
||||
import React, { useEffect } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Switch, Route, Redirect } from 'react-router-dom'
|
||||
|
||||
import ModeSelector from './ModeSelector'
|
||||
import AddLiquidity from './AddLiquidity'
|
||||
import CreateExchange from './CreateExchange'
|
||||
import RemoveLiquidity from './RemoveLiquidity'
|
||||
import { Switch, Route } from 'react-router-dom'
|
||||
|
||||
import './pool.scss'
|
||||
import MediaQuery from 'react-responsive'
|
||||
import ReactGA from 'react-ga'
|
||||
|
||||
class Pool extends Component {
|
||||
componentWillMount() {
|
||||
export default function Pool() {
|
||||
useEffect(() => {
|
||||
ReactGA.pageview(window.location.pathname + window.location.search)
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="pool">
|
||||
<MediaQuery query="(max-width: 768px)">
|
||||
<Header />
|
||||
</MediaQuery>
|
||||
<Switch>
|
||||
<Route exact path="/add-liquidity" component={AddLiquidity} />
|
||||
<Route exact path="/remove-liquidity" component={RemoveLiquidity} />
|
||||
<Route exact path="/create-exchange/:tokenAddress?" component={CreateExchange} />
|
||||
</Switch>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
export default Pool
|
||||
return (
|
||||
<>
|
||||
<ModeSelector />
|
||||
<Switch>
|
||||
<Route exact strict path="/add-liquidity" component={AddLiquidity} />
|
||||
<Route exact strict path="/remove-liquidity" component={RemoveLiquidity} />
|
||||
<Route exact strict path="/create-exchange" component={CreateExchange} />
|
||||
<Route
|
||||
path="/create-exchange/:tokenAddress"
|
||||
render={({ match }) => {
|
||||
return (
|
||||
<Redirect to={{ pathname: '/create-exchange', state: { tokenAddress: match.params.tokenAddress } }} />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Redirect to="/add-liquidity" />
|
||||
</Switch>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -5,6 +5,13 @@
|
||||
height: 100%;
|
||||
background-color: $white;
|
||||
|
||||
&__content {
|
||||
padding: 1rem 0.75rem;
|
||||
flex: 1 1 auto;
|
||||
height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&__liquidity-container {
|
||||
@extend %row-nowrap;
|
||||
align-items: center;
|
||||
@ -114,20 +121,22 @@
|
||||
}
|
||||
|
||||
&__item {
|
||||
@extend %row-nowrap;
|
||||
padding: 1rem;
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: $dove-gray;
|
||||
font-size: 1rem;
|
||||
|
||||
&:hover {
|
||||
background-color: $concrete-gray;
|
||||
|
||||
.token-modal__token-label {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: darken($concrete-gray, 1);
|
||||
&--selected {
|
||||
background-color: $white;
|
||||
border-radius: 3rem;
|
||||
box-shadow: 0 0 0.5px 0.5px $mercury-gray;
|
||||
font-weight: 500;
|
||||
color: $royal-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,11 @@ import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import { BigNumber as BN } from 'bignumber.js'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { withTranslation, useTranslation } from 'react-i18next'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useWeb3Context } from 'web3-react'
|
||||
|
||||
import { selectors, addPendingTx } from '../../ducks/web3connect'
|
||||
import Header from '../../components/Header'
|
||||
import NavigationTabs from '../../components/NavigationTabs'
|
||||
import AddressInputPanel from '../../components/AddressInputPanel'
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import ContextualInfo from '../../components/ContextualInfo'
|
||||
@ -18,8 +19,6 @@ import { retry } from '../../helpers/promise-utils'
|
||||
import EXCHANGE_ABI from '../../abi/exchange'
|
||||
|
||||
import './send.scss'
|
||||
import MediaQuery from 'react-responsive'
|
||||
import ReactGA from 'react-ga'
|
||||
|
||||
const INPUT = 0
|
||||
const OUTPUT = 1
|
||||
@ -27,7 +26,6 @@ const OUTPUT = 1
|
||||
class Send extends Component {
|
||||
static propTypes = {
|
||||
account: PropTypes.string,
|
||||
isConnected: PropTypes.bool.isRequired,
|
||||
selectors: PropTypes.func.isRequired,
|
||||
web3: PropTypes.object.isRequired
|
||||
}
|
||||
@ -583,7 +581,10 @@ class Send extends Component {
|
||||
let contextualInfo = ''
|
||||
let isError = false
|
||||
|
||||
if (inputError || outputError) {
|
||||
if (!account) {
|
||||
contextualInfo = t('noWallet')
|
||||
isError = true
|
||||
} else if (inputError || outputError) {
|
||||
contextualInfo = inputError || outputError
|
||||
isError = true
|
||||
} else if (!inputCurrency || !outputCurrency) {
|
||||
@ -751,87 +752,81 @@ class Send extends Component {
|
||||
const { inputError, outputError, isValid } = this.validate()
|
||||
|
||||
return (
|
||||
<div className="send">
|
||||
<MediaQuery query="(max-width: 767px)">
|
||||
<Header />
|
||||
</MediaQuery>
|
||||
<div
|
||||
className={classnames('swap__content', {
|
||||
'swap--inactive': !this.props.isConnected
|
||||
})}
|
||||
>
|
||||
<NavigationTabs
|
||||
className={classnames('header__navigation', {
|
||||
'header--inactive': !this.props.isConnected
|
||||
})}
|
||||
/>
|
||||
|
||||
<CurrencyInputPanel
|
||||
title={t('input')}
|
||||
description={lastEditedField === OUTPUT ? estimatedText : ''}
|
||||
extraText={this.renderBalance(inputCurrency, inputBalance, inputDecimals)}
|
||||
onCurrencySelected={inputCurrency => this.setState({ inputCurrency }, this.recalcForm)}
|
||||
onValueChange={this.updateInput}
|
||||
selectedTokens={[inputCurrency, outputCurrency]}
|
||||
selectedTokenAddress={inputCurrency}
|
||||
value={inputValue}
|
||||
errorMessage={inputError}
|
||||
/>
|
||||
<OversizedPanel>
|
||||
<div className="swap__down-arrow-background">
|
||||
<img
|
||||
onClick={this.flipInputOutput}
|
||||
className="swap__down-arrow swap__down-arrow--clickable"
|
||||
alt="arrow"
|
||||
src={isValid ? ArrowDownBlue : ArrowDownGrey}
|
||||
/>
|
||||
</div>
|
||||
</OversizedPanel>
|
||||
<CurrencyInputPanel
|
||||
title={t('output')}
|
||||
description={lastEditedField === INPUT ? estimatedText : ''}
|
||||
extraText={this.renderBalance(outputCurrency, outputBalance, outputDecimals)}
|
||||
onCurrencySelected={outputCurrency => this.setState({ outputCurrency }, this.recalcForm)}
|
||||
onValueChange={this.updateOutput}
|
||||
selectedTokens={[inputCurrency, outputCurrency]}
|
||||
value={outputValue}
|
||||
selectedTokenAddress={outputCurrency}
|
||||
errorMessage={outputError}
|
||||
disableUnlock
|
||||
/>
|
||||
<OversizedPanel>
|
||||
<div className="swap__down-arrow-background">
|
||||
<img className="swap__down-arrow" src={isValid ? ArrowDownBlue : ArrowDownGrey} alt="arrow" />
|
||||
</div>
|
||||
</OversizedPanel>
|
||||
<AddressInputPanel
|
||||
t={this.props.t}
|
||||
value={recipient}
|
||||
onChange={address => this.setState({ recipient: address })}
|
||||
/>
|
||||
{this.renderExchangeRate()}
|
||||
{this.renderSummary(inputError, outputError)}
|
||||
<div className="swap__cta-container">
|
||||
<button
|
||||
className={classnames('swap__cta-btn', {
|
||||
'swap--inactive': !this.props.isConnected
|
||||
})}
|
||||
disabled={!isValid}
|
||||
onClick={this.onSend}
|
||||
>
|
||||
{t('send')}
|
||||
</button>
|
||||
<>
|
||||
<CurrencyInputPanel
|
||||
title={t('input')}
|
||||
description={lastEditedField === OUTPUT ? estimatedText : ''}
|
||||
extraText={this.renderBalance(inputCurrency, inputBalance, inputDecimals)}
|
||||
onCurrencySelected={inputCurrency => this.setState({ inputCurrency }, this.recalcForm)}
|
||||
onValueChange={this.updateInput}
|
||||
selectedTokens={[inputCurrency, outputCurrency]}
|
||||
selectedTokenAddress={inputCurrency}
|
||||
value={inputValue}
|
||||
errorMessage={inputError}
|
||||
/>
|
||||
<OversizedPanel>
|
||||
<div className="swap__down-arrow-background">
|
||||
<img
|
||||
onClick={this.flipInputOutput}
|
||||
className="swap__down-arrow swap__down-arrow--clickable"
|
||||
alt="arrow"
|
||||
src={isValid ? ArrowDownBlue : ArrowDownGrey}
|
||||
/>
|
||||
</div>
|
||||
</OversizedPanel>
|
||||
<CurrencyInputPanel
|
||||
title={t('output')}
|
||||
description={lastEditedField === INPUT ? estimatedText : ''}
|
||||
extraText={this.renderBalance(outputCurrency, outputBalance, outputDecimals)}
|
||||
onCurrencySelected={outputCurrency => this.setState({ outputCurrency }, this.recalcForm)}
|
||||
onValueChange={this.updateOutput}
|
||||
selectedTokens={[inputCurrency, outputCurrency]}
|
||||
value={outputValue}
|
||||
selectedTokenAddress={outputCurrency}
|
||||
errorMessage={outputError}
|
||||
disableUnlock
|
||||
/>
|
||||
<OversizedPanel>
|
||||
<div className="swap__down-arrow-background">
|
||||
<img className="swap__down-arrow" src={isValid ? ArrowDownBlue : ArrowDownGrey} alt="arrow" />
|
||||
</div>
|
||||
</OversizedPanel>
|
||||
<AddressInputPanel
|
||||
t={this.props.t}
|
||||
value={recipient}
|
||||
onChange={address => this.setState({ recipient: address })}
|
||||
/>
|
||||
{this.renderExchangeRate()}
|
||||
{this.renderSummary(inputError, outputError)}
|
||||
<div className="swap__cta-container">
|
||||
<SendButton callOnClick={this.onSend} isValid={isValid} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function SendButton({ callOnClick, isValid }) {
|
||||
const { t } = useTranslation()
|
||||
const context = useWeb3Context()
|
||||
|
||||
const isActive = context.active && context.account
|
||||
return (
|
||||
<button
|
||||
className={classnames('swap__cta-btn', {
|
||||
'swap--inactive': !isActive
|
||||
})}
|
||||
disabled={!isValid}
|
||||
onClick={callOnClick}
|
||||
>
|
||||
{t('send')}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
balances: state.web3connect.balances,
|
||||
isConnected: !!state.web3connect.account && state.web3connect.networkId === (process.env.REACT_APP_NETWORK_ID || 1),
|
||||
account: state.web3connect.account,
|
||||
web3: state.web3connect.web3,
|
||||
exchangeAddresses: state.addresses.exchangeAddresses
|
||||
|
@ -3,12 +3,11 @@ import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import { BigNumber as BN } from 'bignumber.js'
|
||||
import MediaQuery from 'react-responsive'
|
||||
import ReactGA from 'react-ga'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { withTranslation, useTranslation } from 'react-i18next'
|
||||
import { useWeb3Context } from 'web3-react'
|
||||
|
||||
import { selectors, addPendingTx } from '../../ducks/web3connect'
|
||||
import Header from '../../components/Header'
|
||||
import NavigationTabs from '../../components/NavigationTabs'
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import ContextualInfo from '../../components/ContextualInfo'
|
||||
import OversizedPanel from '../../components/OversizedPanel'
|
||||
@ -26,7 +25,6 @@ const OUTPUT = 1
|
||||
class Swap extends Component {
|
||||
static propTypes = {
|
||||
account: PropTypes.string,
|
||||
isConnected: PropTypes.bool.isRequired,
|
||||
selectors: PropTypes.func.isRequired,
|
||||
addPendingTx: PropTypes.func.isRequired,
|
||||
web3: PropTypes.object.isRequired
|
||||
@ -554,7 +552,7 @@ class Swap extends Component {
|
||||
|
||||
renderSummary(inputError, outputError) {
|
||||
const { inputValue, inputCurrency, outputValue, outputCurrency } = this.state
|
||||
const t = this.props.t
|
||||
const { t, account } = this.props
|
||||
|
||||
const inputIsZero = BN(inputValue).isZero()
|
||||
const outputIsZero = BN(outputValue).isZero()
|
||||
@ -582,6 +580,11 @@ class Swap extends Component {
|
||||
contextualInfo = t('unlockTokenCont')
|
||||
}
|
||||
|
||||
if (!account) {
|
||||
contextualInfo = t('noWallet')
|
||||
isError = true
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextualInfo
|
||||
openDetailsText={t('transactionDetails')}
|
||||
@ -731,77 +734,70 @@ class Swap extends Component {
|
||||
const { inputError, outputError, isValid } = this.validate()
|
||||
|
||||
return (
|
||||
<div className="swap">
|
||||
<MediaQuery query="(max-width: 767px)">
|
||||
<Header />
|
||||
</MediaQuery>
|
||||
<div
|
||||
className={classnames('swap__content', {
|
||||
'swap--inactive': !this.props.isConnected
|
||||
})}
|
||||
>
|
||||
<NavigationTabs
|
||||
className={classnames('header__navigation', {
|
||||
'header--inactive': !this.props.isConnected
|
||||
})}
|
||||
/>
|
||||
|
||||
<CurrencyInputPanel
|
||||
title={t('input')}
|
||||
description={lastEditedField === OUTPUT ? estimatedText : ''}
|
||||
extraText={this.renderBalance(inputCurrency, inputBalance, inputDecimals)}
|
||||
onCurrencySelected={inputCurrency => this.setState({ inputCurrency }, this.recalcForm)}
|
||||
onValueChange={this.updateInput}
|
||||
selectedTokens={[inputCurrency, outputCurrency]}
|
||||
selectedTokenAddress={inputCurrency}
|
||||
value={inputValue}
|
||||
errorMessage={inputError}
|
||||
/>
|
||||
<OversizedPanel>
|
||||
<div className="swap__down-arrow-background">
|
||||
<img
|
||||
onClick={this.flipInputOutput}
|
||||
className="swap__down-arrow swap__down-arrow--clickable"
|
||||
alt="swap"
|
||||
src={isValid ? ArrowDownBlue : ArrowDownGrey}
|
||||
/>
|
||||
</div>
|
||||
</OversizedPanel>
|
||||
<CurrencyInputPanel
|
||||
title={t('output')}
|
||||
description={lastEditedField === INPUT ? estimatedText : ''}
|
||||
extraText={this.renderBalance(outputCurrency, outputBalance, outputDecimals)}
|
||||
onCurrencySelected={outputCurrency => this.setState({ outputCurrency }, this.recalcForm)}
|
||||
onValueChange={this.updateOutput}
|
||||
selectedTokens={[inputCurrency, outputCurrency]}
|
||||
value={outputValue}
|
||||
selectedTokenAddress={outputCurrency}
|
||||
errorMessage={outputError}
|
||||
disableUnlock
|
||||
/>
|
||||
{this.renderExchangeRate()}
|
||||
{this.renderSummary(inputError, outputError)}
|
||||
<div className="swap__cta-container">
|
||||
<button
|
||||
className={classnames('swap__cta-btn', {
|
||||
'swap--inactive': !this.props.isConnected
|
||||
})}
|
||||
disabled={!isValid}
|
||||
onClick={this.onSwap}
|
||||
>
|
||||
{t('swap')}
|
||||
</button>
|
||||
<>
|
||||
<CurrencyInputPanel
|
||||
title={t('input')}
|
||||
description={lastEditedField === OUTPUT ? estimatedText : ''}
|
||||
extraText={this.renderBalance(inputCurrency, inputBalance, inputDecimals)}
|
||||
onCurrencySelected={inputCurrency => this.setState({ inputCurrency }, this.recalcForm)}
|
||||
onValueChange={this.updateInput}
|
||||
selectedTokens={[inputCurrency, outputCurrency]}
|
||||
selectedTokenAddress={inputCurrency}
|
||||
value={inputValue}
|
||||
errorMessage={inputError}
|
||||
/>
|
||||
<OversizedPanel>
|
||||
<div className="swap__down-arrow-background">
|
||||
<img
|
||||
onClick={this.flipInputOutput}
|
||||
className="swap__down-arrow swap__down-arrow--clickable"
|
||||
alt="swap"
|
||||
src={isValid ? ArrowDownBlue : ArrowDownGrey}
|
||||
/>
|
||||
</div>
|
||||
</OversizedPanel>
|
||||
<CurrencyInputPanel
|
||||
title={t('output')}
|
||||
description={lastEditedField === INPUT ? estimatedText : ''}
|
||||
extraText={this.renderBalance(outputCurrency, outputBalance, outputDecimals)}
|
||||
onCurrencySelected={outputCurrency => this.setState({ outputCurrency }, this.recalcForm)}
|
||||
onValueChange={this.updateOutput}
|
||||
selectedTokens={[inputCurrency, outputCurrency]}
|
||||
value={outputValue}
|
||||
selectedTokenAddress={outputCurrency}
|
||||
errorMessage={outputError}
|
||||
disableUnlock
|
||||
/>
|
||||
{this.renderExchangeRate()}
|
||||
{this.renderSummary(inputError, outputError)}
|
||||
<div className="swap__cta-container">
|
||||
<SwapButton callOnClick={this.onSwap} isValid={isValid} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function SwapButton({ callOnClick, isValid }) {
|
||||
const { t } = useTranslation()
|
||||
const context = useWeb3Context()
|
||||
|
||||
const isActive = context.active && context.account
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classnames('swap__cta-btn', { 'swap--inactive': !isActive })}
|
||||
disabled={!isValid}
|
||||
onClick={callOnClick}
|
||||
>
|
||||
{t('swap')}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
balances: state.web3connect.balances,
|
||||
isConnected: !!state.web3connect.account && state.web3connect.networkId === (process.env.REACT_APP_NETWORK_ID || 1),
|
||||
account: state.web3connect.account,
|
||||
web3: state.web3connect.web3,
|
||||
exchangeAddresses: state.addresses.exchangeAddresses
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
&--inactive {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&__content {
|
||||
|
71
src/utils/index.js
Normal file
71
src/utils/index.js
Normal file
@ -0,0 +1,71 @@
|
||||
import { ethers } from 'ethers'
|
||||
|
||||
import FACTORY_ABI from '../abi/factory'
|
||||
import ERC20_ABI from '../abi/erc20'
|
||||
import ERC20_WITH_BYTES_ABI from '../abi/erc20_symbol_bytes32'
|
||||
|
||||
const factoryAddresses = {
|
||||
1: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95',
|
||||
4: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36'
|
||||
}
|
||||
|
||||
export const errorCodes = ['TOKEN_DETAILS_DECIMALS', 'TOKEN_DETAILS_SYMBOL'].reduce(
|
||||
(accumulator, currentValue, currentIndex) => {
|
||||
accumulator[currentValue] = currentIndex
|
||||
return accumulator
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
function getFactoryContract(networkId, signerOrProvider) {
|
||||
return getContract(factoryAddresses[networkId], FACTORY_ABI, signerOrProvider)
|
||||
}
|
||||
|
||||
export function isAddress(value) {
|
||||
try {
|
||||
ethers.utils.getAddress(value)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function getSignerOrProvider(library, account) {
|
||||
return account ? library.getSigner(account) : library
|
||||
}
|
||||
|
||||
export function getContract(contractAddress, ABI, signerOrProvider) {
|
||||
return new ethers.Contract(contractAddress, ABI, signerOrProvider)
|
||||
}
|
||||
|
||||
export async function getTokenDetails(tokenAddress, signerOrProvider) {
|
||||
const contract = getContract(tokenAddress, ERC20_ABI, signerOrProvider)
|
||||
|
||||
const decimalsPromise = contract.decimals().catch(error => {
|
||||
console.log(error)
|
||||
error.code = errorCodes.TOKEN_DETAILS_DECIMALS
|
||||
throw error
|
||||
})
|
||||
const symbolPromise = contract
|
||||
.symbol()
|
||||
.catch(() => {
|
||||
const contractBytes32 = getContract(tokenAddress, ERC20_WITH_BYTES_ABI, signerOrProvider)
|
||||
return contractBytes32.symbol().then(bytes32 => ethers.utils.parseBytes32String(bytes32))
|
||||
})
|
||||
.catch(error => {
|
||||
error.code = errorCodes.TOKEN_DETAILS_SYMBOL
|
||||
throw error
|
||||
})
|
||||
|
||||
return Promise.all([decimalsPromise, symbolPromise]).then(([decimals, symbol]) => ({
|
||||
decimals,
|
||||
symbol,
|
||||
tokenAddress
|
||||
}))
|
||||
}
|
||||
|
||||
export async function getExchangeDetails(networkId, tokenAddress, signerOrProvider) {
|
||||
const factoryContract = getFactoryContract(networkId, signerOrProvider)
|
||||
|
||||
return factoryContract.getExchange(tokenAddress).then(exchangeAddress => ({ exchangeAddress, tokenAddress }))
|
||||
}
|
385
yarn.lock
385
yarn.lock
@ -871,7 +871,7 @@
|
||||
js-levenshtein "^1.1.3"
|
||||
semver "^5.3.0"
|
||||
|
||||
"@babel/preset-env@^7.1.6", "@babel/preset-env@^7.3.4":
|
||||
"@babel/preset-env@^7.1.6":
|
||||
version "7.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.3.tgz#e71e16e123dc0fbf65a52cbcbcefd072fbd02880"
|
||||
integrity sha512-FYbZdV12yHdJU5Z70cEg0f6lvtpZ8jFSDakTm7WXeJbLXh4R0ztGEu/SW7G1nJ2ZvKwDhz8YrbA84eYyprmGqw==
|
||||
@ -951,13 +951,6 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@babel/runtime@7.3.4":
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83"
|
||||
integrity sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1":
|
||||
version "7.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.3.tgz#79888e452034223ad9609187a0ad1fe0d2ad4bdc"
|
||||
@ -1064,43 +1057,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
|
||||
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
|
||||
|
||||
"@portis/web3-provider-engine@1.0.9":
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@portis/web3-provider-engine/-/web3-provider-engine-1.0.9.tgz#2d2f6e4d3701f0003308a1071bd8b9de4cca6ba5"
|
||||
integrity sha512-0N9uhKKGUBWKU96KlbOkQSz6WYXI46QP5oubxffFGdxKUBiRU2IPrRkQA2d0zoERLMgtT4OlGvog1pxLpjzLfg==
|
||||
dependencies:
|
||||
async "^2.5.0"
|
||||
backoff "^2.5.0"
|
||||
clone "^2.0.0"
|
||||
cross-fetch "^2.1.0"
|
||||
eth-block-tracker "^4.2.0"
|
||||
eth-json-rpc-filters "^4.0.2"
|
||||
eth-json-rpc-infura "^3.1.0"
|
||||
eth-json-rpc-middleware "github:radotzki/eth-json-rpc-middleware#patch-1"
|
||||
eth-sig-util "^1.4.2"
|
||||
ethereumjs-block "^1.2.2"
|
||||
ethereumjs-tx "^1.2.0"
|
||||
ethereumjs-util "^5.1.5"
|
||||
ethereumjs-vm "^2.3.4"
|
||||
json-rpc-error "^2.0.0"
|
||||
json-stable-stringify "^1.0.1"
|
||||
promise-to-callback "^1.0.0"
|
||||
readable-stream "^2.2.9"
|
||||
request "^2.85.0"
|
||||
semaphore "^1.0.3"
|
||||
ws "^5.1.1"
|
||||
xhr "^2.2.0"
|
||||
xtend "^4.0.1"
|
||||
|
||||
"@portis/web3@^2.0.0-beta.23":
|
||||
version "2.0.0-beta.23"
|
||||
resolved "https://registry.yarnpkg.com/@portis/web3/-/web3-2.0.0-beta.23.tgz#873ef9c47405cbc7cffd0bbb58d5a887095a3410"
|
||||
integrity sha512-rf1MkUvG9rtRdC/jNENit74X02ZtSClcC6/2dnyiNZHlufBAF+G5KUXVgrKZRDQSL/lGUCJL7dZYssltqDhXMg==
|
||||
dependencies:
|
||||
"@portis/web3-provider-engine" "1.0.9"
|
||||
ethereumjs-util "5.2.0"
|
||||
penpal "3.0.7"
|
||||
|
||||
"@svgr/babel-plugin-add-jsx-attribute@^4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1"
|
||||
@ -1296,55 +1252,6 @@
|
||||
dependencies:
|
||||
"@types/ethereum-protocol" "*"
|
||||
|
||||
"@walletconnect/browser@^1.0.0-beta.11":
|
||||
version "1.0.0-beta.11"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/browser/-/browser-1.0.0-beta.11.tgz#ca826e5c5253e59b492cb406b8eecc45bb33279e"
|
||||
integrity sha512-JTpUvMOBF6G1D46AJIOJRhsRvDZ9GpOJIcOg5jXv95oExtCRqjlVhoEx/i4KiYrIudaFg4+gzI3uhL829aCqjA==
|
||||
dependencies:
|
||||
"@walletconnect/core" "^1.0.0-beta.11"
|
||||
"@walletconnect/types" "^1.0.0-beta.10"
|
||||
"@walletconnect/utils" "^1.0.0-beta.11"
|
||||
|
||||
"@walletconnect/core@^1.0.0-beta.11":
|
||||
version "1.0.0-beta.11"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-1.0.0-beta.11.tgz#d83b17488cf0f3fff6818009d705081963ad9fbd"
|
||||
integrity sha512-Lh51x4J9yXTS87C0YtNigVRo+3XggStSTH0VlJrwW7l7XFkBJwDCzYwwKyM5h7w91WGlrn6tSjnJl6nE4V2W/w==
|
||||
dependencies:
|
||||
"@walletconnect/types" "^1.0.0-beta.10"
|
||||
"@walletconnect/utils" "^1.0.0-beta.11"
|
||||
|
||||
"@walletconnect/qrcode-modal@^1.0.0-beta.11":
|
||||
version "1.0.0-beta.11"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/qrcode-modal/-/qrcode-modal-1.0.0-beta.11.tgz#0dd14b76a1897a95a55153c73697e1ae46860e3b"
|
||||
integrity sha512-BbEv05Bp9n7pWoau1iwiyEggTujuFVKm+vC83aNPcsYNQ0uyxyRfhZkUhnIRg6ry1dsIqeyazBS/Q8/J8Q8PkQ==
|
||||
dependencies:
|
||||
"@walletconnect/types" "^1.0.0-beta.10"
|
||||
qr-image "^3.2.0"
|
||||
|
||||
"@walletconnect/types@^1.0.0-beta.10":
|
||||
version "1.0.0-beta.11"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-1.0.0-beta.11.tgz#e9e751268c73945255484adcbddaabda4a81dab4"
|
||||
integrity sha512-LgO/R5WGUIwbn9Euzwbx2tWC/XtlFVtRR3ulSldsl5wUA+y04XlAcHsirp2b3ebuaaOJGcAnfFdwK3Qa7k5K5g==
|
||||
|
||||
"@walletconnect/utils@^1.0.0-beta.11":
|
||||
version "1.0.0-beta.11"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-1.0.0-beta.11.tgz#25fadd17973086df8f10d625161c0c3cb1f61bc1"
|
||||
integrity sha512-El+OVX4T+5xZcfCYunHECkPBcX6AKiuhwxzQ1ElQ9jnujEr6Z1+KP1DptLnAGMfvNKZ/dbJlqh8Y1K0wYV0hOQ==
|
||||
dependencies:
|
||||
"@walletconnect/types" "^1.0.0-beta.10"
|
||||
lodash.isnumber "^3.0.3"
|
||||
|
||||
"@walletconnect/web3-subprovider@^1.0.0-beta.10":
|
||||
version "1.0.0-beta.11"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/web3-subprovider/-/web3-subprovider-1.0.0-beta.11.tgz#7d459e70cf0d8e6bca4cc4997d40c693228446e7"
|
||||
integrity sha512-0mRe45WkNKdZAidkIJBe3T91U7YpDLwPlno5B5SzwJZIVgrfLT2eU94shVHEUFsfYSP4J7d+FWiGpe3PqLJdEw==
|
||||
dependencies:
|
||||
"@walletconnect/browser" "^1.0.0-beta.11"
|
||||
"@walletconnect/qrcode-modal" "^1.0.0-beta.11"
|
||||
"@walletconnect/types" "^1.0.0-beta.10"
|
||||
"@walletconnect/utils" "^1.0.0-beta.11"
|
||||
web3-provider-engine "github:walletconnect/web3-provider-engine"
|
||||
|
||||
"@webassemblyjs/ast@1.7.11":
|
||||
version "1.7.11"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.11.tgz#b988582cafbb2b095e8b556526f30c90d057cace"
|
||||
@ -1922,11 +1829,6 @@ autoprefixer@^9.4.2:
|
||||
postcss "^7.0.14"
|
||||
postcss-value-parser "^3.3.1"
|
||||
|
||||
await-semaphore@^0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/await-semaphore/-/await-semaphore-0.1.3.tgz#2b88018cc8c28e06167ae1cdff02504f1f9688d3"
|
||||
integrity sha512-d1W2aNSYcz/sxYO4pMGX9vq65qOTu0P800epMud+6cYYX0QcT7zyqcxec3VWzpgvdXo57UWmVbZpLMjX2m1I7Q==
|
||||
|
||||
aws-sign2@~0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
||||
@ -2578,11 +2480,6 @@ babel-types@^6.0.0, babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.
|
||||
lodash "^4.17.4"
|
||||
to-fast-properties "^1.0.3"
|
||||
|
||||
babelify@^10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/babelify/-/babelify-10.0.0.tgz#fe73b1a22583f06680d8d072e25a1e0d1d1d7fb5"
|
||||
integrity sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg==
|
||||
|
||||
babelify@^7.3.0:
|
||||
version "7.3.0"
|
||||
resolved "https://registry.yarnpkg.com/babelify/-/babelify-7.3.0.tgz#aa56aede7067fd7bd549666ee16dc285087e88e5"
|
||||
@ -2959,7 +2856,7 @@ browserslist@^3.2.6:
|
||||
caniuse-lite "^1.0.30000844"
|
||||
electron-to-chromium "^1.3.47"
|
||||
|
||||
browserslist@^4.0.0, browserslist@^4.3.4, browserslist@^4.3.5, browserslist@^4.5.2, browserslist@^4.5.4:
|
||||
browserslist@^4.0.0, browserslist@^4.3.4, browserslist@^4.5.2, browserslist@^4.5.4:
|
||||
version "4.5.4"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.5.4.tgz#166c4ecef3b51737a42436ea8002aeea466ea2c7"
|
||||
integrity sha512-rAjx494LMjqKnMPhFkuLmLp8JWEX0o8ADTGeAbOqaF+XCvYLreZrG5uVjnPBlAQ8REZK4pzXGvp0bWgrFtKaag==
|
||||
@ -2968,6 +2865,15 @@ browserslist@^4.0.0, browserslist@^4.3.4, browserslist@^4.3.5, browserslist@^4.5
|
||||
electron-to-chromium "^1.3.122"
|
||||
node-releases "^1.1.13"
|
||||
|
||||
browserslist@^4.3.5:
|
||||
version "4.5.5"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.5.5.tgz#fe1a352330d2490d5735574c149a85bc18ef9b82"
|
||||
integrity sha512-0QFO1r/2c792Ohkit5XI8Cm8pDtZxgNl2H6HU4mHrpYz7314pEYcsAVVatM0l/YmxPnEzh9VygXouj4gkFUTKA==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30000960"
|
||||
electron-to-chromium "^1.3.124"
|
||||
node-releases "^1.1.14"
|
||||
|
||||
bs58@=4.0.1, bs58@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
|
||||
@ -2996,11 +2902,6 @@ bser@^2.0.0:
|
||||
dependencies:
|
||||
node-int64 "^0.4.0"
|
||||
|
||||
btoa@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73"
|
||||
integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==
|
||||
|
||||
buffer-alloc-unsafe@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
||||
@ -3209,11 +3110,16 @@ caniuse-api@^3.0.0:
|
||||
lodash.memoize "^4.1.2"
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000918, caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000955, caniuse-lite@^1.0.30000957:
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000955, caniuse-lite@^1.0.30000957:
|
||||
version "1.0.30000959"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000959.tgz#215d3455866da874179c6170202f0cc64f961cfd"
|
||||
integrity sha512-6BvqmS0VLmY4sJCz6AbIJRQfcns8McDxi424y+3kmtisJeA9/5qslP+K8sqremDau7UU4WSsqdRP032JrqZY8Q==
|
||||
|
||||
caniuse-lite@^1.0.30000918, caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000960:
|
||||
version "1.0.30000960"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000960.tgz#ec48297037e5607f582f246ae7b12bee66a78999"
|
||||
integrity sha512-7nK5qs17icQaX6V3/RYrJkOsZyRNnroA4+ZwxaKJzIKy+crIy0Mz5CBlLySd2SNV+4nbUZeqeNfiaEieUBu3aA==
|
||||
|
||||
capture-exit@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-1.2.0.tgz#1c5fcc489fd0ab00d4f1ac7ae1072e3173fbab6f"
|
||||
@ -3407,7 +3313,7 @@ clone-deep@^2.0.1:
|
||||
kind-of "^6.0.0"
|
||||
shallow-clone "^1.0.0"
|
||||
|
||||
clone@2.1.2, clone@^2.0.0, clone@^2.1.1:
|
||||
clone@2.1.2, clone@^2.0.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
|
||||
integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
|
||||
@ -3884,11 +3790,6 @@ css-loader@1.0.0:
|
||||
postcss-value-parser "^3.3.0"
|
||||
source-list-map "^2.0.0"
|
||||
|
||||
css-mediaquery@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0"
|
||||
integrity sha1-aiw3NEkoYYYxxUvTPO3TAdoYvqA=
|
||||
|
||||
css-prefers-color-scheme@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4"
|
||||
@ -4548,7 +4449,7 @@ ee-first@1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||
|
||||
electron-to-chromium@^1.3.103, electron-to-chromium@^1.3.122, electron-to-chromium@^1.3.47:
|
||||
electron-to-chromium@^1.3.103, electron-to-chromium@^1.3.122, electron-to-chromium@^1.3.124, electron-to-chromium@^1.3.47:
|
||||
version "1.3.124"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.124.tgz#861fc0148748a11b3e5ccebdf8b795ff513fa11f"
|
||||
integrity sha512-glecGr/kFdfeXUHOHAWvGcXrxNU+1wSO/t5B23tT1dtlvYB26GY8aHzZSWD7HqhqC800Lr+w/hQul6C5AF542w==
|
||||
@ -4929,15 +4830,6 @@ eth-block-tracker@^3.0.0:
|
||||
pify "^2.3.0"
|
||||
tape "^4.6.3"
|
||||
|
||||
eth-block-tracker@^4.2.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eth-block-tracker/-/eth-block-tracker-4.3.0.tgz#9a54bb8241510367003d1f183adfac05abfe0e87"
|
||||
integrity sha512-QbBsNaGFbpozqzUnjhaR6Skej0485tApPoKMWAW6qpR6J3kY2lgQv47vQvv0MjayxgWXnYH48sjTm2gNRS0NXA==
|
||||
dependencies:
|
||||
eth-query "^2.1.0"
|
||||
pify "^3.0.0"
|
||||
safe-event-emitter "^1.0.1"
|
||||
|
||||
eth-ens-namehash@2.0.8:
|
||||
version "2.0.8"
|
||||
resolved "https://registry.yarnpkg.com/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz#229ac46eca86d52e0c991e7cb2aef83ff0f68bcf"
|
||||
@ -4946,18 +4838,6 @@ eth-ens-namehash@2.0.8:
|
||||
idna-uts46-hx "^2.3.1"
|
||||
js-sha3 "^0.5.7"
|
||||
|
||||
eth-json-rpc-filters@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-4.0.2.tgz#881ea459f3edd6b4f1889914f4d0e7d2deeb2032"
|
||||
integrity sha512-936WGvom7gGMwVqqIdWyQZeXCk5i6yjAfd3oDcgfKG/jCNQnkB28qplviX2cEik/2onWvg8mpgy2D/eutv9xnw==
|
||||
dependencies:
|
||||
await-semaphore "^0.1.3"
|
||||
eth-json-rpc-middleware "^4.0.0"
|
||||
eth-query "^2.1.2"
|
||||
json-rpc-engine "^5.0.0"
|
||||
lodash.flatmap "^4.5.0"
|
||||
safe-event-emitter "^1.0.1"
|
||||
|
||||
eth-json-rpc-infura@^3.1.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eth-json-rpc-infura/-/eth-json-rpc-infura-3.2.0.tgz#62c3f516b51351038c32a548704467cec113ca8f"
|
||||
@ -4988,25 +4868,6 @@ eth-json-rpc-middleware@^1.5.0:
|
||||
promise-to-callback "^1.0.0"
|
||||
tape "^4.6.3"
|
||||
|
||||
eth-json-rpc-middleware@^4.0.0, eth-json-rpc-middleware@^4.1.1, "eth-json-rpc-middleware@github:radotzki/eth-json-rpc-middleware#patch-1":
|
||||
version "4.1.2"
|
||||
resolved "https://codeload.github.com/radotzki/eth-json-rpc-middleware/tar.gz/8acc43df4a8c0c7f6d212af9163d379211dad4c7"
|
||||
dependencies:
|
||||
btoa "^1.2.1"
|
||||
clone "^2.1.1"
|
||||
eth-query "^2.1.2"
|
||||
eth-sig-util "^1.4.2"
|
||||
ethereumjs-block "^1.6.0"
|
||||
ethereumjs-tx "^1.3.3"
|
||||
ethereumjs-util "^5.1.2"
|
||||
ethereumjs-vm "2.2.2"
|
||||
fetch-ponyfill "^4.0.0"
|
||||
json-rpc-engine "^5.0.0"
|
||||
json-rpc-error "^2.0.0"
|
||||
json-stable-stringify "^1.0.1"
|
||||
pify "^3.0.0"
|
||||
safe-event-emitter "^1.0.1"
|
||||
|
||||
eth-lib@0.1.27, eth-lib@^0.1.26:
|
||||
version "0.1.27"
|
||||
resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.1.27.tgz#f0b0fd144f865d2d6bf8257a40004f2e75ca1dd6"
|
||||
@ -5095,21 +4956,16 @@ eth-tx-summary@^3.1.2:
|
||||
ethereumjs-vm "^2.6.0"
|
||||
through2 "^2.0.3"
|
||||
|
||||
ethereum-common@0.0.18, ethereum-common@^0.0.18:
|
||||
version "0.0.18"
|
||||
resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.0.18.tgz#2fdc3576f232903358976eb39da783213ff9523f"
|
||||
integrity sha1-L9w1dvIykDNYl26znaeDIT/5Uj8=
|
||||
|
||||
ethereum-common@0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.1.0.tgz#874dd0fae5e962a56c50ebf28efa6fe39492b0e7"
|
||||
integrity sha1-h03Q+uXpYqVsUOvyjvpv45SSsOc=
|
||||
|
||||
ethereum-common@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.2.0.tgz#13bf966131cce1eeade62a1b434249bb4cb120ca"
|
||||
integrity sha512-XOnAR/3rntJgbCdGhqdaLIxDLWKLmsZOGhHdBKadEr6gEnJLH52k93Ou+TUdFaPN3hJc3isBZBal3U/XZ15abA==
|
||||
|
||||
ethereum-common@^0.0.18:
|
||||
version "0.0.18"
|
||||
resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.0.18.tgz#2fdc3576f232903358976eb39da783213ff9523f"
|
||||
integrity sha1-L9w1dvIykDNYl26znaeDIT/5Uj8=
|
||||
|
||||
ethereum-types@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ethereum-types/-/ethereum-types-2.1.2.tgz#7398873e0eede1dd0956a134e1037032f6ed3c14"
|
||||
@ -5164,17 +5020,6 @@ ethereumjs-block@^1.2.2, ethereumjs-block@^1.4.1, ethereumjs-block@^1.6.0:
|
||||
ethereumjs-util "^5.0.0"
|
||||
merkle-patricia-tree "^2.1.2"
|
||||
|
||||
ethereumjs-block@~1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-1.6.0.tgz#cded4962deaca1eef17372b4d290e84b35c84372"
|
||||
integrity sha1-ze1JYt6soe7xc3K00pDoSzXIQ3I=
|
||||
dependencies:
|
||||
async "^2.0.1"
|
||||
ethereum-common "0.0.18"
|
||||
ethereumjs-tx "^1.2.2"
|
||||
ethereumjs-util "^5.0.0"
|
||||
merkle-patricia-tree "^2.1.2"
|
||||
|
||||
ethereumjs-block@~2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-2.2.0.tgz#8c6c3ab4a5eff0a16d9785fbeedbe643f4dbcbef"
|
||||
@ -5204,17 +5049,6 @@ ethereumjs-tx@1.3.7, ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^
|
||||
ethereum-common "^0.0.18"
|
||||
ethereumjs-util "^5.0.0"
|
||||
|
||||
ethereumjs-util@4.5.0, ethereumjs-util@^4.3.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz#3e9428b317eebda3d7260d854fddda954b1f1bc6"
|
||||
integrity sha1-PpQosxfuvaPXJg2FT93alUsfG8Y=
|
||||
dependencies:
|
||||
bn.js "^4.8.0"
|
||||
create-hash "^1.1.2"
|
||||
keccakjs "^0.2.0"
|
||||
rlp "^2.0.0"
|
||||
secp256k1 "^3.0.1"
|
||||
|
||||
ethereumjs-util@5.2.0, ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereumjs-util@^5.1.2, ethereumjs-util@^5.1.3, ethereumjs-util@^5.1.5, ethereumjs-util@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz#3e0c0d1741471acf1036052d048623dee54ad642"
|
||||
@ -5228,6 +5062,17 @@ ethereumjs-util@5.2.0, ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumj
|
||||
safe-buffer "^5.1.1"
|
||||
secp256k1 "^3.0.1"
|
||||
|
||||
ethereumjs-util@^4.3.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz#3e9428b317eebda3d7260d854fddda954b1f1bc6"
|
||||
integrity sha1-PpQosxfuvaPXJg2FT93alUsfG8Y=
|
||||
dependencies:
|
||||
bn.js "^4.8.0"
|
||||
create-hash "^1.1.2"
|
||||
keccakjs "^0.2.0"
|
||||
rlp "^2.0.0"
|
||||
secp256k1 "^3.0.1"
|
||||
|
||||
ethereumjs-util@^6.0.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz#e9c51e5549e8ebd757a339cc00f5380507e799c8"
|
||||
@ -5241,22 +5086,6 @@ ethereumjs-util@^6.0.0:
|
||||
safe-buffer "^5.1.1"
|
||||
secp256k1 "^3.0.1"
|
||||
|
||||
ethereumjs-vm@2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.2.2.tgz#786317d2c63e547d936c1a468607510e70b4c262"
|
||||
integrity sha512-sAus9UxYjUnA42G91Q1/hR7ff35IJRpcLrUfbaIH7V4cl8qKsNs3wqf3dHvtj3wRqy12ke2Wd0tYdARyGKdD6g==
|
||||
dependencies:
|
||||
async "^2.1.2"
|
||||
async-eventemitter "^0.2.2"
|
||||
ethereum-common "0.1.0"
|
||||
ethereumjs-account "^2.0.3"
|
||||
ethereumjs-block "~1.6.0"
|
||||
ethereumjs-util "4.5.0"
|
||||
fake-merkle-patricia-tree "^1.0.1"
|
||||
functional-red-black-tree "^1.0.1"
|
||||
merkle-patricia-tree "^2.1.2"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
ethereumjs-vm@2.6.0, ethereumjs-vm@^2.1.0, ethereumjs-vm@^2.3.4, ethereumjs-vm@^2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz#76243ed8de031b408793ac33907fb3407fe400c6"
|
||||
@ -5861,13 +5690,6 @@ form-data@~2.3.2:
|
||||
combined-stream "^1.0.6"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
fortmatic@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/fortmatic/-/fortmatic-0.7.0.tgz#978fa01e56a0fb1e5fb167c8fb366e8f100b57cd"
|
||||
integrity sha512-ZLHy7g3z9jKkfba7Ow8H7ZR0IbKAG7wUy062BKBMGrDFqtM2ii7n4eMRYNWcqvsZXZ3E26CxXEqIFI81ibTdLw==
|
||||
dependencies:
|
||||
"@babel/runtime" "7.3.4"
|
||||
|
||||
forwarded@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
|
||||
@ -5964,7 +5786,15 @@ fsevents@1.2.4:
|
||||
nan "^2.9.2"
|
||||
node-pre-gyp "^0.10.0"
|
||||
|
||||
fsevents@^1.2.3, fsevents@^1.2.7:
|
||||
fsevents@^1.2.3:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.8.tgz#57ea5320f762cd4696e5e8e87120eccc8b11cacf"
|
||||
integrity sha512-tPvHgPGB7m40CZ68xqFGkKuzN+RnpGmSV+hgeKxhRpbxdqKXUFJGC3yonBOLzQBcJyGpdZFDfCsdOC2KFsXzeA==
|
||||
dependencies:
|
||||
nan "^2.12.1"
|
||||
node-pre-gyp "^0.12.0"
|
||||
|
||||
fsevents@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4"
|
||||
integrity sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==
|
||||
@ -6650,11 +6480,6 @@ https-browserify@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
|
||||
|
||||
hyphenate-style-name@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48"
|
||||
integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==
|
||||
|
||||
i18next-browser-languagedetector@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-3.0.1.tgz#a47c43176e8412c91e808afb7c6eb5367649aa8e"
|
||||
@ -7877,17 +7702,6 @@ json-rpc-engine@^3.4.0, json-rpc-engine@^3.6.0:
|
||||
promise-to-callback "^1.0.0"
|
||||
safe-event-emitter "^1.0.1"
|
||||
|
||||
json-rpc-engine@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-5.0.0.tgz#f048a5402f602604595f820229b71e8431206490"
|
||||
integrity sha512-bxzuHwoP/U7xBIcqxP9QaOMgW7GDbBNU2TLNWfndRfHxwPLKkGMBfgtns0oDvgjMPJLCgyFZjrnVuSHW5GSC7A==
|
||||
dependencies:
|
||||
"@babel/preset-env" "^7.3.4"
|
||||
async "^2.0.1"
|
||||
babelify "^10.0.0"
|
||||
promise-to-callback "^1.0.0"
|
||||
safe-event-emitter "^1.0.1"
|
||||
|
||||
json-rpc-error@2.0.0, json-rpc-error@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/json-rpc-error/-/json-rpc-error-2.0.0.tgz#a7af9c202838b5e905c7250e547f1aff77258a02"
|
||||
@ -8298,16 +8112,6 @@ lodash.clonedeep@^4.3.2:
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
||||
|
||||
lodash.flatmap@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e"
|
||||
integrity sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=
|
||||
|
||||
lodash.isnumber@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
|
||||
integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
|
||||
|
||||
lodash.memoize@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
@ -8486,13 +8290,6 @@ map-visit@^1.0.0:
|
||||
dependencies:
|
||||
object-visit "^1.0.0"
|
||||
|
||||
matchmediaquery@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/matchmediaquery/-/matchmediaquery-0.3.0.tgz#6f672bcdbc44de16825c6917fbcdcfb9b82607b1"
|
||||
integrity sha512-u0dlv+VENJ+3YepvwSPBieuvnA6DWfaYa/ctwysAR13y4XLJNyt7bEVKzNj/Nvjo+50d88Pj+xL9xaSo6JmX/w==
|
||||
dependencies:
|
||||
css-mediaquery "^0.1.2"
|
||||
|
||||
math-random@^1.0.1:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c"
|
||||
@ -9103,13 +8900,36 @@ node-pre-gyp@^0.11.0:
|
||||
semver "^5.3.0"
|
||||
tar "^4"
|
||||
|
||||
node-releases@^1.1.13, node-releases@^1.1.3:
|
||||
node-pre-gyp@^0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149"
|
||||
integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==
|
||||
dependencies:
|
||||
detect-libc "^1.0.2"
|
||||
mkdirp "^0.5.1"
|
||||
needle "^2.2.1"
|
||||
nopt "^4.0.1"
|
||||
npm-packlist "^1.1.6"
|
||||
npmlog "^4.0.2"
|
||||
rc "^1.2.7"
|
||||
rimraf "^2.6.1"
|
||||
semver "^5.3.0"
|
||||
tar "^4"
|
||||
|
||||
node-releases@^1.1.13:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.14.tgz#f1f41c83cac82caebd6739e6313d56b3b09c9189"
|
||||
integrity sha512-d58EpVZRhQE60kWiWUaaPlK9dyC4zg3ZoMcHcky2d4hDksyQj0rUozwInOl0C66mBsqo01Tuns8AvxnL5S7PKg==
|
||||
dependencies:
|
||||
semver "^5.3.0"
|
||||
|
||||
node-releases@^1.1.14, node-releases@^1.1.3:
|
||||
version "1.1.15"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.15.tgz#9e76a73b0eca3bf7801addaa0e6ce90c795f2b9a"
|
||||
integrity sha512-cKV097BQaZr8LTSRUa2+oc/aX5L8UkZtPQrMSTgiJEeaW7ymTDCoRaGCoaTqk0lqnalcoSHu4wjSl0Cmj2+bMw==
|
||||
dependencies:
|
||||
semver "^5.3.0"
|
||||
|
||||
node-sass@^4.11.0:
|
||||
version "4.11.0"
|
||||
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a"
|
||||
@ -9742,11 +9562,6 @@ pend@~1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
|
||||
|
||||
penpal@3.0.7:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/penpal/-/penpal-3.0.7.tgz#d252711ed93b30f1d867eb82342785b3a95f5f75"
|
||||
integrity sha512-WSXiq5HnEvzvY05SHhaXcsviUmCvh4Ze8AiIZzvmdzaaYAAx4rx8c6Xq6+MaVDG/Nfve3VmGD8HyRP3CkPvPbQ==
|
||||
|
||||
performance-now@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
@ -10756,11 +10571,6 @@ q@^1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
|
||||
|
||||
qr-image@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/qr-image/-/qr-image-3.2.0.tgz#9fa8295beae50c4a149cf9f909a1db464a8672e8"
|
||||
integrity sha1-n6gpW+rlDEoUnPn5CaHbRkqGcug=
|
||||
|
||||
qs@6.5.2, qs@~6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
@ -10951,15 +10761,6 @@ react-redux@^5.0.7:
|
||||
react-is "^16.6.0"
|
||||
react-lifecycles-compat "^3.0.0"
|
||||
|
||||
react-responsive@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-responsive/-/react-responsive-5.0.0.tgz#b6e559b2ce8c4d82b0d47a7a78a74645b977e4af"
|
||||
integrity sha512-oEimZ0FTCC3/pjGDEBHOz06nWbBNDIbMGOdRYp6K9SBUmrqgNAX77hTiqvmRQeLyI97zz4F4kiaFRxFspDxE+w==
|
||||
dependencies:
|
||||
hyphenate-style-name "^1.0.0"
|
||||
matchmediaquery "^0.3.0"
|
||||
prop-types "^15.6.1"
|
||||
|
||||
react-router-dom@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.0.0.tgz#542a9b86af269a37f0b87218c4c25ea8dcf0c073"
|
||||
@ -12821,15 +12622,6 @@ tr46@^1.0.1:
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
trezor-connect@^7.0.2:
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-7.0.2.tgz#8b1b0d1b3b6dc6564bc3d22fe8f54ba826f2a242"
|
||||
integrity sha512-KAFOqxEHHaFvrG8NGLFlM/QxHcwIa3gwfXcgTjCYM0g0zRpwIQBwe35AKsjAQO5yiTJQGa0Cu5MZufGJRGYjjw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
events "^3.0.0"
|
||||
whatwg-fetch "^3.0.0"
|
||||
|
||||
trim-newlines@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
|
||||
@ -13753,33 +13545,6 @@ web3-provider-engine@14.1.0:
|
||||
xhr "^2.2.0"
|
||||
xtend "^4.0.1"
|
||||
|
||||
"web3-provider-engine@github:walletconnect/web3-provider-engine":
|
||||
version "15.0.0"
|
||||
resolved "https://codeload.github.com/walletconnect/web3-provider-engine/tar.gz/48080be33018e8f24e4ba80ccb76787a33ed3e9b"
|
||||
dependencies:
|
||||
async "^2.5.0"
|
||||
backoff "^2.5.0"
|
||||
clone "^2.0.0"
|
||||
cross-fetch "^2.1.0"
|
||||
eth-block-tracker "^4.2.0"
|
||||
eth-json-rpc-filters "^4.0.2"
|
||||
eth-json-rpc-infura "^3.1.0"
|
||||
eth-json-rpc-middleware "^4.1.1"
|
||||
eth-sig-util "^1.4.2"
|
||||
ethereumjs-block "^1.2.2"
|
||||
ethereumjs-tx "^1.2.0"
|
||||
ethereumjs-util "^5.1.5"
|
||||
ethereumjs-vm "^2.3.4"
|
||||
json-rpc-error "^2.0.0"
|
||||
json-stable-stringify "^1.0.1"
|
||||
promise-to-callback "^1.0.0"
|
||||
readable-stream "^2.2.9"
|
||||
request "^2.85.0"
|
||||
semaphore "^1.0.3"
|
||||
ws "^5.1.1"
|
||||
xhr "^2.2.0"
|
||||
xtend "^4.0.1"
|
||||
|
||||
web3-providers-http@1.0.0-beta.35:
|
||||
version "1.0.0-beta.35"
|
||||
resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.0.0-beta.35.tgz#92059d9d6de6e9f82f4fae30b743efd841afc1e1"
|
||||
@ -13819,16 +13584,12 @@ web3-providers@1.0.0-beta.52:
|
||||
websocket "^1.0.28"
|
||||
xhr2-cookies "1.1.0"
|
||||
|
||||
web3-react@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/web3-react/-/web3-react-4.0.0.tgz#d364c96f0c3ac615129490efe3ad8526e415b022"
|
||||
integrity sha512-lcUXlv1m+eO1nesMFH7faNpVpTTiWzB+9J3TNPjibndtlqRw91EzDLO4uS1LK053aYUIK1dWikANjyX6GM6f9w==
|
||||
web3-react@^5.0.4:
|
||||
version "5.0.4"
|
||||
resolved "https://registry.yarnpkg.com/web3-react/-/web3-react-5.0.4.tgz#6006ee51c17255707f73d927f16eb48857957034"
|
||||
integrity sha512-K9Bz+CojWOt3Fiya9SRkYmDsTPjT4vuEs5JQCfEyWH9wWVrXJgGMQxglq5RNfRWBhqgN0PmDMLeYiERQcNt/6g==
|
||||
dependencies:
|
||||
"@0x/subproviders" "^4.0.5"
|
||||
"@portis/web3" "^2.0.0-beta.23"
|
||||
"@walletconnect/web3-subprovider" "^1.0.0-beta.10"
|
||||
fortmatic "^0.7.0"
|
||||
trezor-connect "^7.0.2"
|
||||
|
||||
web3-shh@1.0.0-beta.35:
|
||||
version "1.0.0-beta.35"
|
||||
@ -14085,7 +13846,7 @@ whatwg-fetch@2.0.4:
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
|
||||
integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==
|
||||
|
||||
whatwg-fetch@3.0.0, whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0:
|
||||
whatwg-fetch@3.0.0, whatwg-fetch@>=0.10.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
|
||||
integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==
|
||||
|
Loading…
Reference in New Issue
Block a user