diff --git a/.env.example b/.env.example index cb172be8..1e81bb2a 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,8 @@ # You can leave this empty for rinkeby or use "mainnet" REACT_APP_NETWORK= -# For Rinkeby network -REACT_APP_GOOGLE_ANALYTICS_ID_RINKEBY= - -# For Mainnet network (no needed on dev mode) -REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET= - # For all environments +REACT_APP_GOOGLE_ANALYTICS= REACT_APP_INFURA_TOKEN= REACT_APP_IPFS_GATEWAY=https://ipfs.io/ipfs PUBLIC_URL=/app/ diff --git a/.travis.yml b/.travis.yml index 0804243d..8de45145 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -if: (branch = development) OR (branch = master) OR (type = pull_request) OR (tag IS present) +if: (branch = development) OR (branch = master) OR (release/v2.13.0) OR (type = pull_request) OR (tag IS present) sudo: required dist: bionic language: node_js @@ -10,12 +10,19 @@ matrix: include: - env: - REACT_APP_NETWORK='mainnet' + - REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET} - STAGING_BUCKET_NAME=${STAGING_MAINNET_BUCKET_NAME} - REACT_APP_GNOSIS_APPS_URL=${REACT_APP_GNOSIS_APPS_URL_PROD} if: (branch = master AND NOT type = pull_request) OR tag IS present - env: - REACT_APP_NETWORK='rinkeby' + - REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_RINKEBY} - REACT_APP_GNOSIS_APPS_URL=${REACT_APP_GNOSIS_APPS_URL_STAGING} + - env: + - REACT_APP_NETWORK='xdai' + - REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_XDAI} + - STAGING_BUCKET_NAME=${STAGING_XDAI_BUCKET_NAME} + if: ((branch = master OR branch = release/v2.13.0) AND NOT type = pull_request) OR tag IS present cache: yarn: true before_script: @@ -47,31 +54,44 @@ deploy: secret_access_key: $AWS_SECRET_ACCESS_KEY skip_cleanup: true local_dir: build - upload-dir: app + upload_dir: app region: $AWS_DEFAULT_REGION on: branch: development - # Staging environment + # Staging environment - provider: s3 bucket: $STAGING_BUCKET_NAME access_key_id: $AWS_ACCESS_KEY_ID secret_access_key: $AWS_SECRET_ACCESS_KEY skip_cleanup: true local_dir: build - upload-dir: current/app + upload_dir: current/app region: $AWS_DEFAULT_REGION on: branch: master + + # xDai testing on staging + - provider: s3 + bucket: $STAGING_BUCKET_NAME + access_key_id: $AWS_ACCESS_KEY_ID + secret_access_key: $AWS_SECRET_ACCESS_KEY + skip_cleanup: true + local_dir: build + upload_dir: current/app + region: $AWS_DEFAULT_REGION + on: + branch: release/v2.13.0 + condition: $REACT_APP_NETWORK = xdai - # Prepare production deployment + # Prepare production deployment - provider: s3 bucket: $STAGING_BUCKET_NAME secret_access_key: $AWS_SECRET_ACCESS_KEY access_key_id: $AWS_ACCESS_KEY_ID skip_cleanup: true local_dir: build - upload-dir: releases/$TRAVIS_TAG + upload_dir: releases/$TRAVIS_TAG region: $AWS_DEFAULT_REGION on: tags: true diff --git a/config-overrides.js b/config-overrides.js index dd45a035..5c1d3fc4 100644 --- a/config-overrides.js +++ b/config-overrides.js @@ -5,7 +5,7 @@ module.exports = function override(config) { config.plugins = [] } config.plugins.push( - new webpack.ContextReplacementPlugin(/truffle-(contract|interface-adapter)/, (data) => { + new webpack.ContextReplacementPlugin(/@truffle\/(contract|interface-adapter)/, (data) => { delete data.dependencies[0].critical return data }), diff --git a/package.json b/package.json index 2b981864..da4bd9ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "safe-react", - "version": "2.12.3", + "version": "2.13.0", "description": "Allowing crypto users manage funds in a safer way", "website": "https://github.com/gnosis/safe-react#readme", "bugs": { @@ -61,8 +61,7 @@ "src/**/*.{js,jsx,ts,tsx}", "!src/**/*.{.test.*}", "!src/**/test/**/*", - "!src/**/assets/**", - "!src/config/**/*" + "!src/**/assets/**" ] }, "productName": "Safe Multisig", @@ -164,36 +163,38 @@ ] }, "dependencies": { - "@gnosis.pm/safe-apps-sdk": "0.4.0", + "@gnosis.pm/safe-apps-sdk": "0.4.2", "@gnosis.pm/safe-contracts": "1.1.1-dev.2", - "@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#1bf397f", + "@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#70e57bdd1e0fd5dfdf5768076577c1e000b5fe28", "@gnosis.pm/util-contracts": "2.0.6", - "@ledgerhq/hw-transport-node-hid": "5.22.0", + "@ledgerhq/hw-transport-node-hid": "5.26.0", "@material-ui/core": "4.11.0", "@material-ui/icons": "4.9.1", "@material-ui/lab": "4.0.0-alpha.56", "@openzeppelin/contracts": "3.1.0", + "@truffle/contract": "4.2.25", "async-sema": "^3.1.0", "axios": "0.20.0", - "bignumber.js": "9.0.0", - "bnc-onboard": "1.13.1", + "bignumber.js": "9.0.1", + "bnc-onboard": "1.13.2", "classnames": "^2.2.6", "concurrently": "^5.3.0", "connected-react-router": "6.8.0", "coveralls": "^3.1.0", "currency-flags": "2.1.2", - "date-fns": "2.15.0", + "date-fns": "2.16.1", + "detect-port": "^1.3.0", "electron-is-dev": "^1.2.0", "electron-log": "4.2.4", "electron-settings": "^4.0.2", - "electron-updater": "4.3.4", + "electron-updater": "4.3.5", "eth-sig-util": "^2.5.3", "ethereum-blockies-base64": "^1.0.2", "ethereumjs-abi": "0.6.8", "exponential-backoff": "^3.1.0", "express": "^4.17.1", "final-form": "^4.20.1", - "final-form-calculate": "^1.3.1", + "final-form-calculate": "^1.3.2", "history": "4.10.1", "immortal-db": "^1.1.0", "immutable": "^4.0.0-rc.12", @@ -202,16 +203,15 @@ "lodash.memoize": "^4.1.2", "material-ui-search-bar": "^1.0.0", "notistack": "https://github.com/gnosis/notistack.git#v0.9.4", - "open": "^7.2.0", "polished": "3.6.7", "qrcode.react": "1.0.0", - "query-string": "6.13.1", + "query-string": "6.13.5", "react": "16.13.1", "react-dom": "16.13.1", "react-final-form": "^6.5.1", "react-final-form-listeners": "^1.0.2", "react-ga": "3.1.2", - "react-hot-loader": "4.12.21", + "react-hot-loader": "4.13.0", "react-qr-reader": "^2.2.1", "react-redux": "7.2.1", "react-router-dom": "5.2.0", @@ -224,7 +224,6 @@ "reselect": "^4.0.0", "semver": "7.3.2", "styled-components": "^5.2.0", - "truffle-contract": "4.0.31", "web3": "1.2.9", "web3-core": "^1.2.11", "web3-eth-contract": "^1.2.11", @@ -242,12 +241,12 @@ "@types/history": "4.6.2", "@types/jest": "^26.0.14", "@types/lodash.memoize": "^4.1.6", - "@types/node": "14.11.2", - "@types/react": "^16.9.49", + "@types/node": "^14.11.8", + "@types/react": "^16.9.52", "@types/react-dom": "^16.9.6", "@types/react-redux": "^7.1.9", - "@types/react-router-dom": "^5.1.5", - "@types/styled-components": "^5.1.3", + "@types/react-router-dom": "^5.1.6", + "@types/styled-components": "^5.1.4", "@typescript-eslint/eslint-plugin": "3.9.1", "@typescript-eslint/parser": "3.9.1", "autoprefixer": "9.8.6", @@ -258,15 +257,15 @@ "electron-builder": "22.8.1", "electron-notarize": "1.0.0", "eslint": "6.8.0", - "eslint-config-prettier": "6.11.0", - "eslint-plugin-import": "2.22.0", + "eslint-config-prettier": "6.12.0", + "eslint-plugin-import": "2.22.1", "eslint-plugin-jsx-a11y": "^6.3.1", "eslint-plugin-prettier": "^3.1.4", - "eslint-plugin-react": "^7.20.6", + "eslint-plugin-react": "^7.21.4", "eslint-plugin-sort-destructure-keys": "1.3.5", "ethereumjs-abi": "0.6.8", - "husky": "^4.2.5", - "lint-staged": "10.4.0", + "husky": "^4.3.0", + "lint-staged": "^10.4.0", "node-sass": "^4.14.1", "prettier": "2.1.2", "react-app-rewired": "^2.1.6", diff --git a/public/electron.js b/public/electron.js index 91e62507..b9f45dbb 100644 --- a/public/electron.js +++ b/public/electron.js @@ -1,84 +1,88 @@ -const electron = require("electron"); -const express = require('express'); -const open = require('open'); -const log = require('electron-log'); -const fs = require('fs'); -const Menu = electron.Menu; -const https = require('https'); -const autoUpdater = require('./auto-updater'); +const electron = require('electron') +const express = require('express') +const log = require('electron-log') +const fs = require('fs') +const Menu = electron.Menu +const https = require('https') +const detect = require('detect-port') +const autoUpdater = require('./auto-updater') -const app = electron.app; -const session = electron.session; -const BrowserWindow = electron.BrowserWindow; +const { app, session, BrowserWindow, shell } = electron -const path = require("path"); -const isDev = require("electron-is-dev"); +const path = require('path') +const isDev = require('electron-is-dev') const options = { - key: fs.readFileSync(path.join(__dirname, './ssl/server.key')), + key: fs.readFileSync(path.join(__dirname, './ssl/server.key')), cert: fs.readFileSync(path.join(__dirname, './ssl/server.crt')), - ca: fs.readFileSync(path.join(__dirname, './ssl/rootCA.crt')) -}; - -const PORT = 5000; - -const createServer = () => { - const app = express(); - const staticRoute = path.join(__dirname, '../build'); - app.use(express.static(staticRoute)); - https.createServer(options, app).listen(PORT); + ca: fs.readFileSync(path.join(__dirname, './ssl/rootCA.crt')), } +const DEFAULT_PORT = 5000 -let mainWindow; +const createServer = async () => { + const app = express() + const staticRoute = path.join(__dirname, '../build') + app.use(express.static(staticRoute)) + let selectedPort = DEFAULT_PORT + try { + const _port = await detect(DEFAULT_PORT) + if (_port !== DEFAULT_PORT) selectedPort = _port + https.createServer(options, app).listen(selectedPort) + } catch (e) { + log.error(e) + } finally { + return selectedPort + } +} -function getOpenedWindow(url,options) { - let display = electron.screen.getPrimaryDisplay(); - let width = display.bounds.width; - let height = display.bounds.height; +let mainWindow + +function getOpenedWindow(url, options) { + let display = electron.screen.getPrimaryDisplay() + let width = display.bounds.width + let height = display.bounds.height // filter all requests to trezor-bridge and change origin to make it work - const filter = { - urls: ['http://127.0.0.1:21325/*'] - }; - - options.webPreferences.affinity = 'main-window'; - - if(url.includes('trezor')){ - session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => { - details.requestHeaders['Origin'] = 'https://connect.trezor.io'; - callback({cancel: false, requestHeaders: details.requestHeaders}); - }); + const filter = { + urls: ['http://127.0.0.1:21325/*'], } - if(url.includes('wallet.portis') || url.includes('trezor') || url.includes('app.tor.us')){ + options.webPreferences.affinity = 'main-window' + + if (url.includes('trezor')) { + session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => { + details.requestHeaders['Origin'] = 'https://connect.trezor.io' + callback({ cancel: false, requestHeaders: details.requestHeaders }) + }) + } + + if (url.includes('wallet.portis') || url.includes('trezor') || url.includes('app.tor.us')) { const win = new BrowserWindow({ - width:350, - height:700, + width: 350, + height: 700, x: width - 1300, - parent:mainWindow, + parent: mainWindow, y: height - (process.platform === 'win32' ? 750 : 200), webContents: options.webContents, // use existing webContents if provided fullscreen: false, show: false, - }); - win.webContents.on('new-window', function(event, url){ - if(url.includes('trezor') && url.includes('bridge')) - open(url); - }); - win.once('ready-to-show', () => win.show()); + }) + win.webContents.on('new-window', function (event, url) { + if (url.includes('trezor') && url.includes('bridge')) shell.openExternal(url) + }) + win.once('ready-to-show', () => win.show()) - if(!options.webPreferences){ - win.loadURL(url); + if (!options.webPreferences) { + win.loadURL(url) } return win } - return null; - + return null } -function createWindow() { +function createWindow(port = DEFAULT_PORT) { mainWindow = new BrowserWindow({ show: false, width: 1024, @@ -89,79 +93,77 @@ function createWindow() { nativeWindowOpen: true, // need to be set in order to display modal }, icon: electron.nativeImage.createFromPath(path.join(__dirname, './build/safe.png')), - }); + }) mainWindow.once('ready-to-show', () => { - mainWindow.show(); - }); + mainWindow.show() + }) - mainWindow.loadURL( - isDev - ? "http://localhost:3000" - : `https://localhost:${PORT}` - ) + mainWindow.loadURL(isDev ? 'http://localhost:3000' : `https://localhost:${port}`) if (isDev) { // Open the DevTools. - mainWindow.webContents.openDevTools(); + mainWindow.webContents.openDevTools() //BrowserWindow.addDevToolsExtension(''); } - mainWindow.setMenu(null); - mainWindow.setMenuBarVisibility(false); + mainWindow.setMenu(null) + mainWindow.setMenuBarVisibility(false) - mainWindow.webContents.on('new-window', function(event, url, frameName, disposition, options){ - event.preventDefault(); - const win = getOpenedWindow(url,options); - if(win){ - win.once('ready-to-show', () => win.show()); + mainWindow.webContents.on('new-window', function (event, url, frameName, disposition, options) { + event.preventDefault() + const win = getOpenedWindow(url, options) + if (win) { + win.once('ready-to-show', () => win.show()) - if(!options.webPreferences){ - win.loadURL(url); + if (!options.webPreferences) { + win.loadURL(url) } event.newGuest = win - } else open(url); - }); + } else shell.openExternal(url) + }) mainWindow.webContents.on('did-finish-load', () => { - autoUpdater.init(mainWindow); - }); + autoUpdater.init(mainWindow) + }) mainWindow.webContents.on('crashed', (event) => { - log.info(`App Crashed: ${event}`); - mainWindow.reload(); - }); + log.info(`App Crashed: ${event}`) + mainWindow.reload() + }) - mainWindow.on("closed", () => (mainWindow = null)); + mainWindow.on('closed', () => (mainWindow = null)) } -process.on('uncaughtException',function(error){ - log.error(error); -}); +process.on('uncaughtException', function (error) { + log.error(error) +}) -app.userAgentFallback = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) old-airport-include/1.0.0 Chrome Electron/7.1.7 Safari/537.36'; +app.userAgentFallback = + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) old-airport-include/1.0.0 Chrome Electron/7.1.7 Safari/537.36' // We have one non-context-aware module in node_modules/usb. This is used by @ledgerhq/hw-transport-node-hid // This type of modules will be impossible to use after electron 10 -app.allowRendererProcessReuse = false; +app.allowRendererProcessReuse = false -app.commandLine.appendSwitch('ignore-certificate-errors'); -app.on("ready", () =>{ +app.commandLine.appendSwitch('ignore-certificate-errors') +app.on('ready', async () => { // Hide the menu - Menu.setApplicationMenu(null); - if(!isDev) createServer(); - createWindow(); -}); + Menu.setApplicationMenu(null) + let usedPort = DEFAULT_PORT + if (!isDev) usedPort = await createServer() + createWindow(usedPort) +}) -app.on("window-all-closed", () => { - if (process.platform !== "darwin") { - app.quit(); +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit() } -}); +}) -app.on("activate", () => { +app.on('activate', () => { if (mainWindow === null) { - createWindow(); + createWindow() } -}); +}) diff --git a/src/components/AddressInfo/index.tsx b/src/components/AddressInfo/index.tsx index efd40e50..0e618a2a 100644 --- a/src/components/AddressInfo/index.tsx +++ b/src/components/AddressInfo/index.tsx @@ -1,6 +1,5 @@ import React from 'react' -import styled from 'styled-components' - +import { getNetworkInfo } from 'src/config' import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' import Identicon from 'src/components/Identicon' @@ -8,6 +7,7 @@ import Block from 'src/components/layout/Block' import Bold from 'src/components/layout/Bold' import Paragraph from 'src/components/layout/Paragraph' import { border, xs } from 'src/theme/variables' +import styled from 'styled-components' const Wrapper = styled.div` display: flex; @@ -42,6 +42,8 @@ interface Props { ethBalance?: string } +const { nativeCoin } = getNetworkInfo() + const AddressInfo = ({ ethBalance, safeAddress, safeName }: Props): React.ReactElement => { return ( @@ -59,12 +61,12 @@ const AddressInfo = ({ ethBalance, safeAddress, safeName }: Props): React.ReactE {safeAddress} - + {ethBalance && ( - Balance: {`${ethBalance} ETH`} + Balance: {`${ethBalance} ${nativeCoin.symbol}`} )} diff --git a/src/components/App/ReceiveModal.tsx b/src/components/App/ReceiveModal.tsx index 535a50bd..143b05ba 100644 --- a/src/components/App/ReceiveModal.tsx +++ b/src/components/App/ReceiveModal.tsx @@ -2,7 +2,7 @@ import IconButton from '@material-ui/core/IconButton' import { createStyles, makeStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' import QRCode from 'qrcode.react' -import * as React from 'react' +import React, { ReactElement } from 'react' import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' @@ -13,9 +13,11 @@ import Col from 'src/components/layout/Col' import Hairline from 'src/components/layout/Hairline' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' -import { lg, md, screenSm, secondaryText, sm } from 'src/theme/variables' +import { border, fontColor, lg, md, screenSm, secondaryText, sm } from 'src/theme/variables' import { copyToClipboard } from 'src/utils/clipboard' +import { getNetworkInfo } from 'src/config' +const networkInfo = getNetworkInfo() const useStyles = makeStyles( createStyles({ heading: { @@ -35,6 +37,12 @@ const useStyles = makeStyles( borderRadius: '6px', border: `1px solid ${secondaryText}`, }, + networkInfo: { + backgroundColor: `${networkInfo?.backgroundColor ?? border}`, + color: `${networkInfo?.textColor ?? fontColor}`, + padding: md, + marginBottom: 0, + }, annotation: { margin: lg, marginBottom: 0, @@ -79,7 +87,7 @@ type Props = { safeName: string } -const ReceiveModal = ({ onClose, safeAddress, safeName }: Props) => { +const ReceiveModal = ({ onClose, safeAddress, safeName }: Props): ReactElement => { const classes = useStyles() return ( @@ -93,9 +101,12 @@ const ReceiveModal = ({ onClose, safeAddress, safeName }: Props) => { + + {networkInfo.label} Network only send {networkInfo.label} assets to this Safe. + - This is the address of your Safe. Deposit funds by scanning the QR code or copying the address below. Only send - ETH and ERC-20 tokens to this address! + This is the address of your Safe. Deposit funds by scanning the QR code or copying the address below. Only send{' '} + {networkInfo.nativeCoin.name} and ERC-20 tokens to this address! @@ -115,7 +126,7 @@ const ReceiveModal = ({ onClose, safeAddress, safeName }: Props) => { {safeAddress} - + diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index ee90992a..a916ac98 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -16,8 +16,8 @@ import CookiesBanner from 'src/components/CookiesBanner' import Notifier from 'src/components/Notifier' import Backdrop from 'src/components/layout/Backdrop' import Img from 'src/components/layout/Img' -import { getNetwork } from 'src/config' -import { ETHEREUM_NETWORK } from 'src/logic/wallets/getWeb3' +import { getNetworkId } from 'src/config' +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' import { networkSelector } from 'src/logic/wallets/store/selectors' import { SAFELIST_ADDRESS, WELCOME_ADDRESS } from 'src/routes/routes' import { safeNameSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' @@ -26,11 +26,11 @@ import SendModal from 'src/routes/safe/components/Balances/SendModal' import { useLoadSafe } from 'src/logic/safe/hooks/useLoadSafe' import { useSafeScheduledUpdates } from 'src/logic/safe/hooks/useSafeScheduledUpdates' import useSafeActions from 'src/logic/safe/hooks/useSafeActions' -import { currentCurrencySelector, safeFiatBalancesTotalSelector } from 'src/logic/currencyValues/store/selectors/index' +import { currentCurrencySelector, safeFiatBalancesTotalSelector } from 'src/logic/currencyValues/store/selectors' import { formatAmountInUsFormat } from 'src/logic/tokens/utils/formatAmount' import { grantedSelector } from 'src/routes/safe/container/selector' -import Receive from './ReceiveModal' +import ReceiveModal from './ReceiveModal' import { useSidebarItems } from 'src/components/AppLayout/Sidebar/useSidebarItems' const notificationStyles = { @@ -46,6 +46,12 @@ const notificationStyles = { info: { background: '#fff', }, + receiveModal: { + height: 'auto', + maxWidth: 'calc(100% - 30px)', + minHeight: '544px', + overflow: 'hidden', + }, } const Frame = styled.div` @@ -55,7 +61,7 @@ const Frame = styled.div` max-width: 100%; ` -const desiredNetwork = getNetwork() +const desiredNetwork = getNetworkId() const useStyles = makeStyles(notificationStyles) @@ -67,7 +73,7 @@ const App: React.FC = ({ children }) => { const matchSafe = useRouteMatch({ path: `${SAFELIST_ADDRESS}`, strict: false }) const history = useHistory() const safeAddress = useSelector(safeParamAddressFromStateSelector) - const safeName = useSelector(safeNameSelector) + const safeName = useSelector(safeNameSelector) ?? '' const { safeActionsState, onShow, onHide, showSendFunds, hideSendFunds } = useSafeActions() const currentSafeBalance = useSelector(safeFiatBalancesTotalSelector) const currentCurrency = useSelector(currentCurrencySelector) @@ -77,7 +83,7 @@ const App: React.FC = ({ children }) => { useLoadSafe(safeAddress) useSafeScheduledUpdates(safeAddress) - const sendFunds = safeActionsState.sendFunds as { isOpen: boolean; selectedToken: string } + const sendFunds = safeActionsState.sendFunds const formattedTotalBalance = currentSafeBalance ? formatAmountInUsFormat(currentSafeBalance) : '' const balance = !!formattedTotalBalance && !!currentCurrency ? `${formattedTotalBalance} ${currentCurrency}` : undefined @@ -138,10 +144,11 @@ const App: React.FC = ({ children }) => { - + )} diff --git a/src/components/AppLayout/Header/components/CircleDot.tsx b/src/components/AppLayout/Header/components/CircleDot.tsx index 2ae0fe65..83ce6a0d 100644 --- a/src/components/AppLayout/Header/components/CircleDot.tsx +++ b/src/components/AppLayout/Header/components/CircleDot.tsx @@ -1,86 +1,27 @@ -import { withStyles } from '@material-ui/core/styles' -import Dot from '@material-ui/icons/FiberManualRecord' import * as React from 'react' +import { getNetworkInfo } from 'src/config' -import Block from 'src/components/layout/Block' -import Img from 'src/components/layout/Img' -import { border, fancy, screenSm, warning } from 'src/theme/variables' - -const key = require('../assets/key.svg') -const triangle = require('../assets/triangle.svg') - -const styles = () => ({ - root: { - display: 'none', - [`@media (min-width: ${screenSm}px)`]: { - display: 'flex', - }, - }, - dot: { - position: 'relative', - backgroundColor: '#ffffff', - color: fancy, - }, - key: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - backgroundColor: border, - }, - warning: { - position: 'relative', - top: '-2px', - }, -}) - -const buildKeyStyleFrom = (size, center, dotSize) => ({ - width: `${size}px`, - height: `${size}px`, - marginLeft: center ? `${dotSize}px` : 'none', - borderRadius: `${size}px`, -}) - -const buildDotStyleFrom = (size, top, right, mode) => ({ - width: `${size}px`, - height: `${size}px`, - borderRadius: `${size}px`, - top: `${top}px`, - right: `${right}px`, - color: mode === 'error' ? fancy : warning, -}) - -const KeyRing = ({ - center = false, - circleSize, - classes, - dotRight, - dotSize, - dotTop, - hideDot = false, - keySize, - mode, -}) => { - const keyStyle = buildKeyStyleFrom(circleSize, center, dotSize) - const dotStyle = buildDotStyleFrom(dotSize, dotTop, dotRight, mode) - const isWarning = mode === 'warning' - const img = isWarning ? triangle : key - - return ( - <> - - - Status connection - - {!hideDot && } - - - ) +type Props = { + className: string } -export default withStyles(styles as any)(KeyRing) +export const CircleDot = (props: Props): React.ReactElement => { + const networkInfo = getNetworkInfo() + + return ( +
+ + + +
+ ) +} diff --git a/src/components/AppLayout/Header/components/KeyRing.tsx b/src/components/AppLayout/Header/components/KeyRing.tsx new file mode 100644 index 00000000..10c39b74 --- /dev/null +++ b/src/components/AppLayout/Header/components/KeyRing.tsx @@ -0,0 +1,97 @@ +import { createStyles, makeStyles } from '@material-ui/core/styles' +import Dot from '@material-ui/icons/FiberManualRecord' +import * as React from 'react' + +import Block from 'src/components/layout/Block' +import Img from 'src/components/layout/Img' +import { border, fancy, screenSm, warning } from 'src/theme/variables' + +const key = require('../assets/key.svg') +const triangle = require('../assets/triangle.svg') + +const styles = createStyles({ + root: { + display: 'none', + [`@media (min-width: ${screenSm}px)`]: { + display: 'flex', + }, + }, + dot: { + position: 'relative', + backgroundColor: '#ffffff', + color: fancy, + }, + key: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: border, + }, + warning: { + position: 'relative', + top: '-2px', + }, +}) + +const useStyles = makeStyles(styles) + +const buildKeyStyleFrom = (size, center, dotSize) => ({ + width: `${size}px`, + height: `${size}px`, + marginLeft: center ? `${dotSize}px` : 'none', + borderRadius: `${size}px`, +}) + +const buildDotStyleFrom = (size, top, right, mode) => ({ + width: `${size}px`, + height: `${size}px`, + borderRadius: `${size}px`, + top: `${top}px`, + right: `${right}px`, + color: mode === 'error' ? fancy : warning, +}) + +type Props = { + center?: boolean + circleSize?: number + dotRight?: number + dotSize?: number + dotTop?: number + hideDot?: boolean + keySize: number + mode?: string +} + +export const KeyRing = ({ + center = false, + circleSize, + dotRight, + dotSize, + dotTop, + hideDot = false, + keySize, + mode, +}: Props): React.ReactElement => { + const classes = useStyles(styles) + const keyStyle = buildKeyStyleFrom(circleSize, center, dotSize) + const dotStyle = buildDotStyleFrom(dotSize, dotTop, dotRight, mode) + const isWarning = mode === 'warning' + const img = isWarning ? triangle : key + + return ( + <> + + + Status connection + + {!hideDot && } + + + ) +} diff --git a/src/components/AppLayout/Header/components/NetworkLabel.tsx b/src/components/AppLayout/Header/components/NetworkLabel.tsx index 594c0cee..edd88fe3 100644 --- a/src/components/AppLayout/Header/components/NetworkLabel.tsx +++ b/src/components/AppLayout/Header/components/NetworkLabel.tsx @@ -3,11 +3,10 @@ import * as React from 'react' import Col from 'src/components/layout/Col' import Paragraph from 'src/components/layout/Paragraph' -import { getNetwork } from 'src/config' -import { border, md, screenSm, sm, xs } from 'src/theme/variables' +import { getNetworkInfo } from 'src/config' +import { border, md, screenSm, sm, xs, fontColor } from 'src/theme/variables' -const interfaceNetwork = getNetwork() -const formatNetwork = (network: string): string => network[0].toUpperCase() + network.substring(1).toLowerCase() +const networkInfo = getNetworkInfo() const useStyles = makeStyles({ container: { @@ -19,7 +18,8 @@ const useStyles = makeStyles({ }, }, text: { - background: border, + backgroundColor: `${networkInfo?.backgroundColor ?? border}`, + color: `${networkInfo?.textColor ?? fontColor}`, borderRadius: '3px', lineHeight: 'normal', margin: '0', @@ -31,18 +31,13 @@ const useStyles = makeStyles({ }, }) -interface NetworkLabelProps { - network?: string -} - -const NetworkLabel = ({ network = interfaceNetwork }: NetworkLabelProps): React.ReactElement => { +const NetworkLabel = (): React.ReactElement => { const classes = useStyles() - const formattedNetwork = formatNetwork(network) return ( - {formattedNetwork} + {networkInfo.label} ) diff --git a/src/components/AppLayout/Header/components/ProviderDetails/ConnectDetails.tsx b/src/components/AppLayout/Header/components/ProviderDetails/ConnectDetails.tsx index 76300a96..0b52d4e5 100644 --- a/src/components/AppLayout/Header/components/ProviderDetails/ConnectDetails.tsx +++ b/src/components/AppLayout/Header/components/ProviderDetails/ConnectDetails.tsx @@ -2,11 +2,12 @@ import { withStyles } from '@material-ui/core/styles' import * as React from 'react' import ConnectButton from 'src/components/ConnectButton' -import CircleDot from 'src/components/AppLayout/Header/components/CircleDot' + import Block from 'src/components/layout/Block' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' import { lg, md } from 'src/theme/variables' +import { KeyRing } from 'src/components/AppLayout/Header/components/KeyRing' const styles = () => ({ container: { @@ -42,7 +43,7 @@ const ConnectDetails = ({ classes }) => ( - + diff --git a/src/components/AppLayout/Header/components/ProviderDetails/UserDetails.tsx b/src/components/AppLayout/Header/components/ProviderDetails/UserDetails.tsx index 1319e05f..0f3ff77f 100644 --- a/src/components/AppLayout/Header/components/ProviderDetails/UserDetails.tsx +++ b/src/components/AppLayout/Header/components/ProviderDetails/UserDetails.tsx @@ -1,10 +1,9 @@ -import { withStyles } from '@material-ui/core/styles' +import { makeStyles } from '@material-ui/core/styles' import Dot from '@material-ui/icons/FiberManualRecord' import classNames from 'classnames' import * as React from 'react' import { EthHashInfo, Identicon } from '@gnosis.pm/safe-react-components' -import CircleDot from 'src/components/AppLayout/Header/components/CircleDot' import Spacer from 'src/components/Spacer' import Block from 'src/components/layout/Block' import Button from 'src/components/layout/Button' @@ -14,11 +13,15 @@ import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' import { background, connected as connectedBg, lg, md, sm, warning, xs } from 'src/theme/variables' import { upperFirst } from 'src/utils/css' +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' +import { getExplorerInfo } from 'src/config' +import { KeyRing } from 'src/components/AppLayout/Header/components/KeyRing' +import { CircleDot } from '../CircleDot' +import { createStyles } from '@material-ui/core' -const dot = require('../../assets/dotRinkeby.svg') const walletIcon = require('../../assets/wallet.svg') -const styles = () => ({ +const styles = createStyles({ container: { padding: `${md} 12px`, display: 'flex', @@ -88,9 +91,29 @@ const styles = () => ({ }, }) -const UserDetails = ({ classes, connected, network, onDisconnect, openDashboard, provider, userAddress }) => { +type Props = { + connected: boolean + network: ETHEREUM_NETWORK + onDisconnect: () => void + openDashboard?: (() => void | null) | boolean + provider?: string + userAddress: string +} + +const useStyles = makeStyles(styles) + +export const UserDetails = ({ + connected, + network, + onDisconnect, + openDashboard, + provider, + userAddress, +}: Props): React.ReactElement => { const status = connected ? 'Connected' : 'Connection error' const color = connected ? 'primary' : 'warning' + const explorerUrl = getExplorerInfo(userAddress) + const classes = useStyles() return ( <> @@ -99,12 +122,12 @@ const UserDetails = ({ classes, connected, network, onDisconnect, openDashboard, {connected ? ( ) : ( - + )} {userAddress ? ( - + ) : ( 'Address not available' )} @@ -138,9 +161,9 @@ const UserDetails = ({ classes, connected, network, onDisconnect, openDashboard, Network - Network + - {upperFirst(network)} + {upperFirst(ETHEREUM_NETWORK[network])} @@ -170,5 +193,3 @@ const UserDetails = ({ classes, connected, network, onDisconnect, openDashboard, ) } - -export default withStyles(styles as any)(UserDetails) diff --git a/src/components/AppLayout/Header/components/ProviderInfo/ProviderAccessible.tsx b/src/components/AppLayout/Header/components/ProviderInfo/ProviderAccessible.tsx index 07989c05..563c3abb 100644 --- a/src/components/AppLayout/Header/components/ProviderInfo/ProviderAccessible.tsx +++ b/src/components/AppLayout/Header/components/ProviderInfo/ProviderAccessible.tsx @@ -3,11 +3,11 @@ import * as React from 'react' import { EthHashInfo, Text } from '@gnosis.pm/safe-react-components' import NetworkLabel from '../NetworkLabel' -import CircleDot from 'src/components/AppLayout/Header/components/CircleDot' import Col from 'src/components/layout/Col' import Paragraph from 'src/components/layout/Paragraph' import WalletIcon from '../WalletIcon' import { connected as connectedBg, screenSm, sm } from 'src/theme/variables' +import { KeyRing } from 'src/components/AppLayout/Header/components/KeyRing' const useStyles = makeStyles({ network: { @@ -62,16 +62,16 @@ const useStyles = makeStyles({ interface ProviderInfoProps { connected: boolean provider: string - network: string + // TODO: [xDai] Review. This may cause some issues with EthHashInfo. userAddress: string } -const ProviderInfo = ({ connected, provider, userAddress, network }: ProviderInfoProps): React.ReactElement => { +const ProviderInfo = ({ connected, provider, userAddress }: ProviderInfoProps): React.ReactElement => { const classes = useStyles() const addressColor = connected ? 'text' : 'warning' return ( <> - {!connected && } + {!connected && } ) : ( @@ -107,7 +106,7 @@ const ProviderInfo = ({ connected, provider, userAddress, network }: ProviderInf - + ) diff --git a/src/components/AppLayout/Header/components/ProviderInfo/ProviderDisconnected.tsx b/src/components/AppLayout/Header/components/ProviderInfo/ProviderDisconnected.tsx index c038f916..bb8e57fc 100644 --- a/src/components/AppLayout/Header/components/ProviderInfo/ProviderDisconnected.tsx +++ b/src/components/AppLayout/Header/components/ProviderInfo/ProviderDisconnected.tsx @@ -1,11 +1,10 @@ import { withStyles } from '@material-ui/core/styles' import * as React from 'react' -import CircleDot from 'src/components/AppLayout/Header/components/CircleDot' - import Col from 'src/components/layout/Col' import Paragraph from 'src/components/layout/Paragraph' import { sm } from 'src/theme/variables' +import { KeyRing } from 'src/components/AppLayout/Header/components/KeyRing' const styles = () => ({ network: { @@ -27,7 +26,7 @@ const styles = () => ({ const ProviderDisconnected = ({ classes }) => ( <> - + { return } - return + return } const getProviderDetailsBased = () => { diff --git a/src/components/AppLayout/Sidebar/SafeHeader/index.tsx b/src/components/AppLayout/Sidebar/SafeHeader/index.tsx index 9d3c2f3f..16a0268d 100644 --- a/src/components/AppLayout/Sidebar/SafeHeader/index.tsx +++ b/src/components/AppLayout/Sidebar/SafeHeader/index.tsx @@ -8,11 +8,13 @@ import { Identicon, Button, CopyToClipboardBtn, - EtherscanButton, + ExplorerButton, } from '@gnosis.pm/safe-react-components' -import { getNetwork } from 'src/config' import FlexSpacer from 'src/components/FlexSpacer' +import { getExplorerInfo, getNetworkInfo } from 'src/config' +import { NetworkSettings } from 'src/config/networks/network.d' +import { border, fontColor } from 'src/theme/variables' export const TOGGLE_SIDEBAR_BTN_TESTID = 'TOGGLE_SIDEBAR_BTN' @@ -46,6 +48,19 @@ const StyledButton = styled(Button)` margin: 0 4px 0 0; } ` + +type StyledTextLabelProps = { + networkInfo: NetworkSettings +} + +const StyledTextLabel = styled(Text)` + margin: -8px 0 4px -8px; + padding: 4px 8px; + width: 100%; + text-align: center; + color: ${(props: StyledTextLabelProps) => props.networkInfo?.textColor ?? fontColor}; + background-color: ${(props: StyledTextLabelProps) => props.networkInfo?.backgroundColor ?? border}; +` const StyledEthHashInfo = styled(EthHashInfo)` p { color: ${({ theme }) => theme.colors.placeHolder}; @@ -110,43 +125,50 @@ const SafeHeader = ({ ) } + const explorerUrl = getExplorerInfo(address) + const networkInfo = getNetworkInfo() return ( - - - - - - - - + <> + + {networkInfo.label} + + + + + + + + + - {safeName} - - - - - - - - + {safeName} + + + + + + + + - {granted ? null : ( - - - READ ONLY + {granted ? null : ( + + + READ ONLY + + + )} + + {balance} + + + + New Transaction - - )} - - {balance} - - - - New Transaction - - - + + + ) } diff --git a/src/components/AppLayout/Sidebar/useSidebarItems.tsx b/src/components/AppLayout/Sidebar/useSidebarItems.tsx index 58439e79..4cf8e76b 100644 --- a/src/components/AppLayout/Sidebar/useSidebarItems.tsx +++ b/src/components/AppLayout/Sidebar/useSidebarItems.tsx @@ -4,8 +4,13 @@ import { useRouteMatch } from 'react-router-dom' import { ListItemType } from 'src/components/List' import ListIcon from 'src/components/List/ListIcon' import { SAFELIST_ADDRESS } from 'src/routes/routes' +import { FEATURES } from 'src/config/networks/network.d' +import { useSelector } from 'react-redux' +import { safeFeaturesEnabledSelector } from 'src/logic/safe/store/selectors' const useSidebarItems = (): ListItemType[] => { + const featuresEnabled = useSelector(safeFeaturesEnabledSelector) + const safeAppsEnabled = Boolean(featuresEnabled?.includes(FEATURES.SAFE_APPS)) const matchSafe = useRouteMatch({ path: `${SAFELIST_ADDRESS}`, strict: false }) const matchSafeWithAddress = useRouteMatch<{ safeAddress: string }>({ path: `${SAFELIST_ADDRESS}/:safeAddress` }) const matchSafeWithAction = useRouteMatch({ path: `${SAFELIST_ADDRESS}/:safeAddress/:safeAction` }) as { @@ -13,11 +18,30 @@ const useSidebarItems = (): ListItemType[] => { params: Record } - const sidebarItems = useMemo((): ListItemType[] => { + return useMemo((): ListItemType[] => { if (!matchSafe || !matchSafeWithAddress) { return [] } + const settingsItem = { + label: 'Settings', + icon: , + selected: matchSafeWithAction?.params.safeAction === 'settings', + href: `${matchSafeWithAddress?.url}/settings`, + } + + const safeSidebar = safeAppsEnabled + ? [ + { + label: 'Apps', + icon: , + selected: matchSafeWithAction?.params.safeAction === 'apps', + href: `${matchSafeWithAddress?.url}/apps`, + }, + settingsItem, + ] + : [settingsItem] + return [ { label: 'ASSETS', @@ -37,22 +61,9 @@ const useSidebarItems = (): ListItemType[] => { selected: matchSafeWithAction?.params.safeAction === 'address-book', href: `${matchSafeWithAddress?.url}/address-book`, }, - { - label: 'Apps', - icon: , - selected: matchSafeWithAction?.params.safeAction === 'apps', - href: `${matchSafeWithAddress?.url}/apps`, - }, - { - label: 'Settings', - icon: , - selected: matchSafeWithAction?.params.safeAction === 'settings', - href: `${matchSafeWithAddress?.url}/settings`, - }, + ...safeSidebar, ] - }, [matchSafe, matchSafeWithAction, matchSafeWithAddress]) - - return sidebarItems + }, [matchSafe, matchSafeWithAction, matchSafeWithAddress, safeAppsEnabled]) } export { useSidebarItems } diff --git a/src/components/ConnectButton/index.tsx b/src/components/ConnectButton/index.tsx index a719776f..a520594b 100644 --- a/src/components/ConnectButton/index.tsx +++ b/src/components/ConnectButton/index.tsx @@ -3,15 +3,16 @@ import React from 'react' import Button from 'src/components/layout/Button' import { getNetworkId } from 'src/config' +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' import { getWeb3, setWeb3 } from 'src/logic/wallets/getWeb3' import { fetchProvider } from 'src/logic/wallets/store/actions' import transactionDataCheck from 'src/logic/wallets/transactionDataCheck' import { getSupportedWallets } from 'src/logic/wallets/utils/walletList' import { store } from 'src/store' +import { BLOCKNATIVE_KEY } from 'src/utils/constants' -const isMainnet = process.env.REACT_APP_NETWORK === 'mainnet' - -const BLOCKNATIVE_API_KEY = isMainnet ? process.env.REACT_APP_BLOCKNATIVE_KEY : '7fbb9cee-7e97-4436-8770-8b29a9a8814c' +const networkId = getNetworkId() +const BLOCKNATIVE_API_KEY = BLOCKNATIVE_KEY[networkId] ?? BLOCKNATIVE_KEY[ETHEREUM_NETWORK.RINKEBY] let lastUsedAddress = '' let providerName @@ -20,7 +21,7 @@ const wallets = getSupportedWallets() export const onboard = Onboard({ dappId: BLOCKNATIVE_API_KEY, - networkId: getNetworkId(), + networkId: networkId, subscriptions: { wallet: (wallet) => { if (wallet.provider) { diff --git a/src/components/EtherscanBtn/index.tsx b/src/components/EtherscanBtn/index.tsx index c81d0be8..f7609489 100644 --- a/src/components/EtherscanBtn/index.tsx +++ b/src/components/EtherscanBtn/index.tsx @@ -6,8 +6,8 @@ import React from 'react' import EtherscanOpenIcon from './img/etherscan-open.svg' import Img from 'src/components/layout/Img' -import { getEtherScanLink } from 'src/logic/wallets/getWeb3' import { xs } from 'src/theme/variables' +import { getExplorerInfo } from 'src/config' const useStyles = makeStyles({ container: { @@ -30,26 +30,23 @@ const useStyles = makeStyles({ interface EtherscanBtnProps { className?: string increaseZindex?: boolean - type: 'tx' | 'address' value: string } -const EtherscanBtn = ({ - className = '', - increaseZindex = false, - type, - value, -}: EtherscanBtnProps): React.ReactElement => { +const EtherscanBtn = ({ className = '', increaseZindex = false, value }: EtherscanBtnProps): React.ReactElement => { const classes = useStyles() const customClasses = increaseZindex ? { popper: classes.increasedPopperZindex } : {} + const explorerInfo = getExplorerInfo(value) + const { url } = explorerInfo() + return ( event.stopPropagation()} - href={getEtherScanLink(type, value)} + href={url} rel="noopener noreferrer" target="_blank" > diff --git a/src/components/EtherscanLink/index.tsx b/src/components/EtherscanLink/index.tsx index fe33a953..e37beeef 100644 --- a/src/components/EtherscanLink/index.tsx +++ b/src/components/EtherscanLink/index.tsx @@ -17,11 +17,10 @@ interface EtherscanLinkProps { className?: string cut?: number knownAddress?: boolean - type: 'tx' | 'address' value: string } -const EtherscanLink = ({ className, cut, knownAddress, type, value }: EtherscanLinkProps): React.ReactElement => { +const EtherscanLink = ({ className, cut, knownAddress, value }: EtherscanLinkProps): React.ReactElement => { const classes = useStyles() return ( @@ -30,7 +29,7 @@ const EtherscanLink = ({ className, cut, knownAddress, type, value }: EtherscanL {cut ? shortVersionOf(value, cut) : value} - + {knownAddress !== undefined ? : null} ) diff --git a/src/components/ListContentLayout/Layout.ts b/src/components/ListContentLayout/Layout.ts index feb7d423..dbb5b2a0 100644 --- a/src/components/ListContentLayout/Layout.ts +++ b/src/components/ListContentLayout/Layout.ts @@ -3,7 +3,7 @@ import styled from 'styled-components' export const Wrapper = styled.div` display: grid; grid-template-columns: 245px auto; - min-height: 560px; + min-height: 75vh; .background { box-shadow: 1px 2px 10px 0 rgba(212, 212, 211, 0.59); background-color: white; diff --git a/src/components/SafeListSidebar/SafeList/AddresWrapper.tsx b/src/components/SafeListSidebar/SafeList/AddresWrapper.tsx new file mode 100644 index 00000000..8bf621a3 --- /dev/null +++ b/src/components/SafeListSidebar/SafeList/AddresWrapper.tsx @@ -0,0 +1,81 @@ +import React from 'react' +import styled from 'styled-components' +import { ButtonLink, EthHashInfo, Text } from '@gnosis.pm/safe-react-components' +import { formatAmount } from 'src/logic/tokens/utils/formatAmount' +import { sameAddress } from 'src/logic/wallets/ethAddresses' +import DefaultBadge from './DefaultBadge' +import { SafeRecordProps } from 'src/logic/safe/store/models/safe' +import { DefaultSafe } from 'src/routes/safe/store/reducer/types/safe' +import { SetDefaultSafe } from 'src/logic/safe/store/actions/setDefaultSafe' +import { makeStyles } from '@material-ui/core/styles' +import { getNetworkInfo } from 'src/config' + +const StyledButtonLink = styled(ButtonLink)` + visibility: hidden; + white-space: nowrap; +` +const useStyles = makeStyles({ + wrapper: { + display: 'flex', + padding: '5px 0', + width: '100%', + justifyContent: 'space-between', + '& > nth-child(2)': { + display: 'flex', + alignItems: 'center', + }, + }, + addressDetails: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + width: '175px', + '& div': { + marginLeft: '0px', + padding: '5px 20px', + '& img': { + marginRight: '5px', + }, + '& p': { + marginTop: '3px', + }, + }, + }, +}) + +type Props = { + safe: SafeRecordProps + defaultSafe: DefaultSafe + setDefaultSafe: SetDefaultSafe +} + +const { nativeCoin } = getNetworkInfo() + +export const AddressWrapper = (props: Props): React.ReactElement => { + const classes = useStyles() + const { safe, defaultSafe, setDefaultSafe } = props + + return ( +
+ + +
+ {`${formatAmount(safe.ethBalance)} ${nativeCoin.name}`} + {sameAddress(defaultSafe, safe.address) ? ( + + ) : ( + { + setDefaultSafe(safe.address) + }} + color="primary" + > + Make default + + )} +
+
+ ) +} diff --git a/src/components/SafeListSidebar/SafeList/index.tsx b/src/components/SafeListSidebar/SafeList/index.tsx index 23ecd436..57b60238 100644 --- a/src/components/SafeListSidebar/SafeList/index.tsx +++ b/src/components/SafeListSidebar/SafeList/index.tsx @@ -1,62 +1,22 @@ import MuiList from '@material-ui/core/List' import ListItem from '@material-ui/core/ListItem' import { makeStyles } from '@material-ui/core/styles' -import { EthHashInfo, Icon, Text, ButtonLink } from '@gnosis.pm/safe-react-components' +import { Icon } from '@gnosis.pm/safe-react-components' import * as React from 'react' import styled from 'styled-components' - import { SafeRecord } from 'src/logic/safe/store/models/safe' import { DefaultSafe } from 'src/routes/safe/store/reducer/types/safe' import { SetDefaultSafe } from 'src/logic/safe/store/actions/setDefaultSafe' -import { getNetwork } from 'src/config' -import DefaultBadge from './DefaultBadge' import Hairline from 'src/components/layout/Hairline' import Link from 'src/components/layout/Link' -import { formatAmount } from 'src/logic/tokens/utils/formatAmount' import { sameAddress } from 'src/logic/wallets/ethAddresses' import { SAFELIST_ADDRESS } from 'src/routes/routes' - +import { AddressWrapper } from './AddresWrapper' export const SIDEBAR_SAFELIST_ROW_TESTID = 'SIDEBAR_SAFELIST_ROW_TESTID' const StyledIcon = styled(Icon)` margin-right: 4px; ` -const AddressWrapper = styled.div` - display: flex; - padding: 5px 0; - width: 100%; - justify-content: space-between; - - > nth-child(2) { - display: flex; - align-items: center; - } -` - -const StyledButtonLink = styled(ButtonLink)` - visibility: hidden; - white-space: nowrap; -` - -const AddressDetails = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - width: 175px; - - div { - margin-left: 0px; - padding: 5px 20px; - - img { - margin-right: 5px; - } - - p { - margin-top: 3px; - } - } -` const useStyles = makeStyles({ list: { @@ -107,34 +67,7 @@ const SafeList = ({ currentSafe, defaultSafe, onSafeClick, safes, setDefaultSafe ) : (
placeholder
)} - - - - - - {`${formatAmount(safe.ethBalance)} ETH`} - {sameAddress(defaultSafe, safe.address) ? ( - - ) : ( - { - setDefaultSafe(safe.address) - }} - color="primary" - > - Make default - - )} - - + diff --git a/src/components/forms/AddressInput/index.tsx b/src/components/forms/AddressInput/index.tsx index 4d09843a..8655d259 100644 --- a/src/components/forms/AddressInput/index.tsx +++ b/src/components/forms/AddressInput/index.tsx @@ -7,6 +7,7 @@ import { Validator, composeValidators, mustBeEthereumAddress, required } from 's import { trimSpaces } from 'src/utils/strings' import { getAddressFromENS } from 'src/logic/wallets/getWeb3' import { isValidEnsName } from 'src/logic/wallets/ethAddresses' +import { checksumAddress } from 'src/utils/checksumAddress' // an idea for second field was taken from here // https://github.com/final-form/react-final-form-listeners/blob/master/src/OnBlur.js @@ -56,11 +57,15 @@ const AddressInput = ({ if (isValidEnsName(address)) { try { const resolverAddr = await getAddressFromENS(address) - fieldMutator(resolverAddr) + const formattedAddress = checksumAddress(resolverAddr) + fieldMutator(formattedAddress) } catch (err) { console.error('Failed to resolve address for ENS name: ', err) } - } else fieldMutator(address) + } else { + const formattedAddress = checksumAddress(address) + fieldMutator(formattedAddress) + } }} diff --git a/src/components/layout/Img/index.tsx b/src/components/layout/Img/index.tsx index bab91c2b..425e719a 100644 --- a/src/components/layout/Img/index.tsx +++ b/src/components/layout/Img/index.tsx @@ -1,11 +1,17 @@ import classNames from 'classnames/bind' -import React from 'react' +import React, { ReactElement, ImgHTMLAttributes } from 'react' import styles from './index.module.scss' const cx = classNames.bind(styles) -const Img: any = ({ alt, bordered, className, fullwidth, style, testId = '', ...props }) => { +type ImgProps = ImgHTMLAttributes & { + bordered?: boolean + fullwidth?: boolean + testId?: string +} + +const Img = ({ alt, bordered, className, fullwidth, style, testId = '', ...props }: ImgProps): ReactElement => { const classes = cx(styles.img, { fullwidth, bordered }, className) return {alt} diff --git a/src/config/__tests__/config.test.ts b/src/config/__tests__/config.test.ts new file mode 100644 index 00000000..021a4a20 --- /dev/null +++ b/src/config/__tests__/config.test.ts @@ -0,0 +1,136 @@ +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' +import { default as networks } from 'src/config/networks' + +const { mainnet, xdai } = networks + +describe('Config Services', () => { + beforeEach(() => { + jest.resetModules() + }) + + it(`should load 'test' network config`, () => { + // Given + jest.mock('src/utils/constants', () => ({ + NODE_ENV: 'test', + })) + const { getNetworkInfo } = require('src/config') + + // When + const networkInfo = getNetworkInfo() + + // Then + expect(networkInfo.id).toBe(ETHEREUM_NETWORK.LOCAL) + }) + + it(`should load 'mainnet' network config`, () => { + // Given + jest.mock('src/utils/constants', () => ({ + NODE_ENV: '', + NETWORK: 'MAINNET', + })) + const { getNetworkInfo } = require('src/config') + + // When + const networkInfo = getNetworkInfo() + + // Then + expect(networkInfo.id).toBe(ETHEREUM_NETWORK.MAINNET) + }) + + it(`should load 'mainnet.dev' network config`, () => { + // Given + jest.mock('src/utils/constants', () => ({ + NODE_ENV: '', + NETWORK: 'MAINNET', + })) + const { getTxServiceUrl, getGnosisSafeAppsUrl } = require('src/config') + const TX_SERVICE_URL = mainnet.environment.dev?.txServiceUrl + const SAFE_APPS_URL = mainnet.environment.dev?.safeAppsUrl + + // When + const txServiceUrl = getTxServiceUrl() + const safeAppsUrl = getGnosisSafeAppsUrl() + + // Then + expect(TX_SERVICE_URL).toBe(txServiceUrl) + expect(SAFE_APPS_URL).toBe(safeAppsUrl) + }) + + it(`should load 'mainnet.staging' network config`, () => { + // Given + jest.mock('src/utils/constants', () => ({ + NODE_ENV: 'production', + NETWORK: 'MAINNET', + })) + const { getTxServiceUrl, getGnosisSafeAppsUrl } = require('src/config') + const TX_SERVICE_URL = mainnet.environment.staging?.txServiceUrl + const SAFE_APPS_URL = mainnet.environment.staging?.safeAppsUrl + + // When + const txServiceUrl = getTxServiceUrl() + const safeAppsUrl = getGnosisSafeAppsUrl() + + // Then + expect(TX_SERVICE_URL).toBe(txServiceUrl) + expect(SAFE_APPS_URL).toBe(safeAppsUrl) + }) + + it(`should load 'mainnet.production' network config`, () => { + // Given + jest.mock('src/utils/constants', () => ({ + NODE_ENV: 'production', + NETWORK: 'MAINNET', + APP_ENV: 'production' + })) + const { getTxServiceUrl, getGnosisSafeAppsUrl } = require('src/config') + const TX_SERVICE_URL = mainnet.environment.production.txServiceUrl + const SAFE_APPS_URL = mainnet.environment.production.safeAppsUrl + + // When + const txServiceUrl = getTxServiceUrl() + const safeAppsUrl = getGnosisSafeAppsUrl() + + // Then + expect(TX_SERVICE_URL).toBe(txServiceUrl) + expect(SAFE_APPS_URL).toBe(safeAppsUrl) + }) + + it(`should load 'xdai.production' network config`, () => { + // Given + jest.mock('src/utils/constants', () => ({ + NODE_ENV: 'production', + NETWORK: 'XDAI', + APP_ENV: 'production' + })) + const { getTxServiceUrl, getGnosisSafeAppsUrl } = require('src/config') + const TX_SERVICE_URL = xdai.environment.production.txServiceUrl + const SAFE_APPS_URL = xdai.environment.production.safeAppsUrl + + // When + const txServiceUrl = getTxServiceUrl() + const safeAppsUrl = getGnosisSafeAppsUrl() + + // Then + expect(TX_SERVICE_URL).toBe(txServiceUrl) + expect(SAFE_APPS_URL).toBe(safeAppsUrl) + }) + + it(`should default to 'xdai.production' network config if no environment is found`, () => { + // Given + jest.mock('src/utils/constants', () => ({ + NODE_ENV: '', + NETWORK: 'XDAI', + })) + const { getTxServiceUrl, getGnosisSafeAppsUrl } = require('src/config') + const TX_SERVICE_URL = xdai.environment.production.txServiceUrl + const SAFE_APPS_URL = xdai.environment.production.safeAppsUrl + + // When + const txServiceUrl = getTxServiceUrl() + const safeAppsUrl = getGnosisSafeAppsUrl() + + // Then + expect(TX_SERVICE_URL).toBe(txServiceUrl) + expect(SAFE_APPS_URL).toBe(safeAppsUrl) + }) +}) diff --git a/src/config/development-mainnet.ts b/src/config/development-mainnet.ts deleted file mode 100644 index a26a7075..00000000 --- a/src/config/development-mainnet.ts +++ /dev/null @@ -1,11 +0,0 @@ -// -import devConfig from './development' -import { TX_SERVICE_HOST, RELAY_API_URL } from 'src/config/names' - -const devMainnetConfig = { - ...devConfig, - [TX_SERVICE_HOST]: 'https://safe-transaction.mainnet.staging.gnosisdev.com/api/v1/', - [RELAY_API_URL]: 'https://safe-relay.mainnet.staging.gnosisdev.com/api/v1/', -} - -export default devMainnetConfig diff --git a/src/config/development.ts b/src/config/development.ts deleted file mode 100644 index fa26777e..00000000 --- a/src/config/development.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL, SAFE_APPS_URL } from 'src/config/names' - -const devConfig = { - [TX_SERVICE_HOST]: 'https://safe-transaction.staging.gnosisdev.com/api/v1/', - [SIGNATURES_VIA_METAMASK]: false, - [RELAY_API_URL]: 'https://safe-relay.staging.gnosisdev.com/api/v1/', - [SAFE_APPS_URL]: 'https://safe-apps.dev.gnosisdev.com/' - //[SAFE_APPS_URL]: 'http://localhost:3002/' -} - -export default devConfig diff --git a/src/config/index.ts b/src/config/index.ts index 929cbf1b..b07b908b 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,103 +1,165 @@ -import { checksumAddress } from 'src/utils/checksumAddress'; +import networks from 'src/config/networks' +import { EnvironmentSettings, ETHEREUM_NETWORK, NetworkSettings, SafeFeatures } from 'src/config/networks/network.d' +import { APP_ENV, ETHERSCAN_API_KEY, GOOGLE_ANALYTICS_ID, INFURA_TOKEN, NETWORK, NODE_ENV } from 'src/utils/constants' import { ensureOnce } from 'src/utils/singleton' -import { ETHEREUM_NETWORK } from 'src/logic/wallets/getWeb3' -import { - RELAY_API_URL, - SIGNATURES_VIA_METAMASK, - TX_SERVICE_HOST, - SAFE_APPS_URL -} from 'src/config/names' -import devConfig from './development' -import testConfig from './testing' -import stagingConfig from './staging' -import prodConfig from './production' -import mainnetDevConfig from './development-mainnet' -import mainnetProdConfig from './production-mainnet' -import mainnetStagingConfig from './staging-mainnet' +import memoize from 'lodash.memoize' -const configuration = () => { - if (process.env.NODE_ENV === 'test') { - return testConfig +export const getNetworkId = (): ETHEREUM_NETWORK => ETHEREUM_NETWORK[NETWORK] + +export const getNetworkName = (): string => ETHEREUM_NETWORK[getNetworkId()] + +const getCurrentEnvironment = (): string => { + switch (NODE_ENV) { + case 'test': { + return 'test' + } + case 'production': { + return APP_ENV === 'production' ? 'production' : 'staging' + } + default: { + return 'dev' + } + } +} + +type NetworkSpecificConfiguration = EnvironmentSettings & { + network: NetworkSettings, + disabledFeatures?: SafeFeatures, +} + +const configuration = (): NetworkSpecificConfiguration => { + const currentEnvironment = getCurrentEnvironment() + + // special case for test environment + if (currentEnvironment === 'test') { + const configFile = networks.local + + return { + ...configFile.environment.production, + network: configFile.network, + disabledFeatures: configFile.disabledFeatures, + } } - if (process.env.NODE_ENV === 'production') { - if (process.env.REACT_APP_NETWORK === 'mainnet') { - return process.env.REACT_APP_ENV === 'production' - ? mainnetProdConfig - : mainnetStagingConfig + // lookup the config file based on the network specified in the NETWORK variable + const configFile = networks[getNetworkName().toLowerCase()] + // defaults to 'production' as it's the only environment that is required for the network configs + const networkBaseConfig = configFile.environment[currentEnvironment] ?? configFile.environment.production + + return { + ...networkBaseConfig, + network: configFile.network, + disabledFeatures: configFile.disabledFeatures, + } +} + +const getConfig: () => NetworkSpecificConfiguration = ensureOnce(configuration) + +export const getTxServiceUrl = (): string => getConfig()?.txServiceUrl + +export const getRelayUrl = (): string | undefined => getConfig()?.relayApiUrl + +export const getGnosisSafeAppsUrl = (): string => getConfig()?.safeAppsUrl + +export const getRpcServiceUrl = (): string => { + const usesInfuraRPC = [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY].includes(getNetworkId()) + + if (usesInfuraRPC) { + return `${getConfig()?.rpcServiceUrl}/${INFURA_TOKEN}` + } + + return getConfig()?.rpcServiceUrl +} + +export const getNetworkExplorerInfo = (): { name: string; url: string; apiUrl: string } => ({ + name: getConfig()?.networkExplorerName, + url: getConfig()?.networkExplorerUrl, + apiUrl: getConfig()?.networkExplorerApiUrl, +}) + +export const getNetworkConfigDisabledFeatures = (): SafeFeatures => getConfig()?.disabledFeatures || [] + +export const getNetworkInfo = (): NetworkSettings => getConfig()?.network + +export const getTxServiceUriFrom = (safeAddress: string) => `/safes/${safeAddress}/transactions/` + +export const getIncomingTxServiceUriTo = (safeAddress: string) => `/safes/${safeAddress}/incoming-transfers/` + +export const getAllTransactionsUriFrom = (safeAddress: string) => `/safes/${safeAddress}/all-transactions/` + +export const getSafeCreationTxUri = (safeAddress: string) => `/safes/${safeAddress}/creation/` + +export const getGoogleAnalyticsTrackingID = (): string => GOOGLE_ANALYTICS_ID + +const fetchContractABI = memoize( + async (url: string, contractAddress: string, apiKey?: string) => { + let params: Record = { + module: 'contract', + action: 'getAbi', + address: contractAddress, } - return process.env.REACT_APP_ENV === 'production' - ? prodConfig - : stagingConfig + if (apiKey) { + params = { ...params, apiKey } + } + + const response = await fetch(`${url}?${new URLSearchParams(params)}`) + + if (!response.ok) { + return { status: 0, result: [] } + } + + return response.json() + }, + (url, contractAddress) => `${url}_${contractAddress}`, +) + +const getNetworkExplorerApiKey = (networkExplorerName: string): string | undefined=> { + switch (networkExplorerName.toLowerCase()) { + case 'etherscan': { + return ETHERSCAN_API_KEY + } + default: { + return undefined + } } - - return process.env.REACT_APP_NETWORK === 'mainnet' - ? mainnetDevConfig - : devConfig } -export const getNetwork = () => - process.env.REACT_APP_NETWORK === 'mainnet' - ? ETHEREUM_NETWORK.MAINNET - : ETHEREUM_NETWORK.RINKEBY +export const getContractABI = async (contractAddress: string) =>{ + const { apiUrl, name } = getNetworkExplorerInfo() -export const getNetworkId = () => - process.env.REACT_APP_NETWORK === 'mainnet' ? 1 : 4 + const apiKey = getNetworkExplorerApiKey(name) -const getConfig = ensureOnce(configuration) + try { + const { result, status } = await fetchContractABI(apiUrl, contractAddress, apiKey) -export const getTxServiceHost = () => { - const config = getConfig() + if (status === '0') { + return [] + } - return config[TX_SERVICE_HOST] + return result + } catch (e) { + console.error('Failed to retrieve ABI', e) + return undefined + } } -export const getTxServiceUriFrom = (safeAddress) => - `safes/${safeAddress}/transactions/` - -export const getIncomingTxServiceUriTo = (safeAddress) => - `safes/${safeAddress}/incoming-transfers/` - -export const getAllTransactionsUriFrom = (safeAddress: string): string => - `safes/${safeAddress}/all-transactions/` - -export const getSafeCreationTxUri = (safeAddress) => `safes/${safeAddress}/creation/` - -export const getRelayUrl = () => getConfig()[RELAY_API_URL] - -export const signaturesViaMetamask = () => { - const config = getConfig() - - return config[SIGNATURES_VIA_METAMASK] +export type BlockScanInfo = () => { + alt: string + url: string } -export const getGnosisSafeAppsUrl = () => { - const config = getConfig() +export const getExplorerInfo = (hash: string): BlockScanInfo => { + const { name, url } = getNetworkExplorerInfo() + const networkInfo = getNetworkInfo() - return config[SAFE_APPS_URL] -} - -export const getGoogleAnalyticsTrackingID = () => - getNetwork() === ETHEREUM_NETWORK.MAINNET - ? process.env.REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET - : process.env.REACT_APP_GOOGLE_ANALYTICS_ID_RINKEBY - -export const getIntercomId = () => - process.env.REACT_APP_ENV === 'production' - ? process.env.REACT_APP_INTERCOM_ID - : 'plssl1fl' - -export const getExchangeRatesUrl = () => 'https://api.exchangeratesapi.io/latest' - -export const getExchangeRatesUrlFallback = () => 'https://api.coinbase.com/v2/exchange-rates' - -export const getSafeLastVersion = () => process.env.REACT_APP_LATEST_SAFE_VERSION || '1.1.1' - -export const buildSafeCreationTxUrl = (safeAddress) => { - const host = getTxServiceHost() - const address = checksumAddress(safeAddress) - const base = getSafeCreationTxUri(address) - - return `${host}${base}` + switch (networkInfo.id) { + default: { + const type = hash.length > 42 ? 'tx' : 'address' + return () => ({ + url: `${url}${type}/${hash}`, + alt: name || '', + }) + } + } } diff --git a/src/config/names.ts b/src/config/names.ts deleted file mode 100644 index 3390fbec..00000000 --- a/src/config/names.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const TX_SERVICE_HOST = 'tsh' -export const SIGNATURES_VIA_METAMASK = 'svm' -export const RELAY_API_URL = 'rau' -export const SAFE_APPS_URL = 'sau' diff --git a/src/config/networks/__tests__/networks.test.ts b/src/config/networks/__tests__/networks.test.ts new file mode 100644 index 00000000..00a79dfd --- /dev/null +++ b/src/config/networks/__tests__/networks.test.ts @@ -0,0 +1,142 @@ +import fs from 'fs' + +import networks from 'src/config/networks' +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' +import { isValidURL } from 'src/utils/url' + +describe('Networks config files test', () => { + const environments = ['dev', 'staging', 'production'] + + const NETWORKS_PATH = 'src/config/networks/' + const configFiles = fs.readdirSync(NETWORKS_PATH) + const networksFileNames = configFiles + .filter((file) => !fs.lstatSync(`${NETWORKS_PATH}${file}`).isDirectory()) + .filter((file) => { + const [fileName, extension] = file.split('.') + return extension === 'ts' && fileName !== 'index' + }) + .map((file) => file.split('.')[0]) + + it(`should verify that the network file is exported in the networks/index.ts file`, () => { + networksFileNames.forEach((networkFileName) => { + const isValid = !!networks[networkFileName] + + if (!isValid) { + console.log(`Network file "${networkFileName}" is not exported in "networks/index.ts"`) + } + + expect(isValid).toBeTruthy() + }) + }) + + environments.forEach((environment) => { + networksFileNames.forEach((networkFileName) => { + it(`should validate "${environment}" environment URIs for ${networkFileName} config`, () => { + // Given + const networkConfig = networks[networkFileName] + + // When + const networkConfigElement = networkConfig.environment[environment] + if (!networkConfigElement) { + return + } + + const environmentConfigKeys = Object + .keys(networkConfigElement) + .filter((environmentConfigKey) => + environmentConfigKey.endsWith('Uri') && !!networkConfigElement[environmentConfigKey] + ) + + // Then + environmentConfigKeys.forEach((environmentConfigKey) => { + const networkConfigElementUri = networkConfigElement[environmentConfigKey] + const isValid = isValidURL(networkConfigElementUri) + + if (!isValid) { + console.log(`Invalid URI in "${networkFileName}" at ${environment}.${environmentConfigKey}:`, networkConfigElementUri) + } + + expect(isValid).toBeTruthy() + }) + }) + }) + }) + + networksFileNames.forEach((networkFileName) => { + it(`should have a valid 'decimal' value for 'nativeToken'`, () => { + // Given + const networkConfig = networks[networkFileName] + + // When + const { decimals } = networkConfig.network.nativeCoin + + // Then + const isValid = Number.isInteger(decimals) && decimals >= 0 + + if (!isValid) { + console.log(`Invalid value in "${networkFileName}" at network.decimals:`, decimals) + } + + expect(isValid).toBeTruthy() + }) + }) + + networksFileNames.forEach((networkFileName) => { + it(`should have one of 'ETHEREUM_NETWORK' values for 'network.id'`, () => { + // Given + const networkConfig = networks[networkFileName] + + // When + const { id } = networkConfig.network + + // Then + const isValid = ETHEREUM_NETWORK[id] + + if (!isValid) { + console.log(`Invalid value in "${networkFileName}" at network.id:`, id) + } + + expect(isValid).toBeTruthy() + }) + }) + + networksFileNames.forEach((networkFileName) => { + it(`should have a valid CSS color defined for 'network.backgroundColor'`, () => { + // Given + const networkConfig = networks[networkFileName] + + // When + const { backgroundColor } = networkConfig.network + + // Then + const s = new Option().style + s.color = backgroundColor + const isValid = s.color !== '' + + if (!isValid) { + console.log(`Invalid value in "${networkFileName}" at network.backgroundColor:`, backgroundColor) + } + + expect(isValid).toBeTruthy() + }) + + it(`should have a valid CSS color defined for 'network.textColor'`, () => { + // Given + const networkConfig = networks[networkFileName] + + // When + const { textColor } = networkConfig.network + + // Then + const s = new Option().style + s.color = textColor + const isValid = s.color !== '' + + if (!isValid) { + console.log(`Invalid value in "${networkFileName}" at network.textColor:`, textColor) + } + + expect(isValid).toBeTruthy() + }) + }) +}) diff --git a/src/config/networks/index.ts b/src/config/networks/index.ts new file mode 100644 index 00000000..8944d16d --- /dev/null +++ b/src/config/networks/index.ts @@ -0,0 +1,11 @@ +import local from './local' +import mainnet from './mainnet' +import rinkeby from './rinkeby' +import xdai from './xdai' + +export default { + local, + mainnet, + rinkeby, + xdai, +} diff --git a/src/config/networks/local.ts b/src/config/networks/local.ts new file mode 100644 index 00000000..ad51c883 --- /dev/null +++ b/src/config/networks/local.ts @@ -0,0 +1,37 @@ +import EtherLogo from 'src/assets/icons/icon_etherTokens.svg' +import { EnvironmentSettings, ETHEREUM_NETWORK, NetworkConfig } from 'src/config/networks/network.d' + +const baseConfig: EnvironmentSettings = { + txServiceUrl: 'http://localhost:8000/api/v1', + relayApiUrl: 'https://safe-relay.staging.gnosisdev.com/api/v1', + safeAppsUrl: 'http://localhost:3002', + gasPriceOracleUrl: 'https://ethgasstation.info/json/ethgasAPI.json', + rpcServiceUrl: 'http://localhost:4447', + networkExplorerName: 'Etherscan', + networkExplorerUrl: 'https://rinkeby.etherscan.io', + networkExplorerApiUrl: 'https://api-rinkeby.etherscan.io/api', +} + +const local: NetworkConfig = { + environment: { + production: { + ...baseConfig, + }, + }, + network: { + id: ETHEREUM_NETWORK.LOCAL, + backgroundColor: '#E8673C', + textColor: '#ffffff', + label: 'LocalRPC', + isTestNet: true, + nativeCoin: { + address: '0x000', + name: 'Ether', + symbol: 'ETH', + decimals: 18, + logoUri: EtherLogo, + }, + }, +} + +export default local diff --git a/src/config/networks/mainnet.ts b/src/config/networks/mainnet.ts new file mode 100644 index 00000000..89945ae6 --- /dev/null +++ b/src/config/networks/mainnet.ts @@ -0,0 +1,45 @@ +import EtherLogo from 'src/assets/icons/icon_etherTokens.svg' +import { EnvironmentSettings, ETHEREUM_NETWORK, NetworkConfig } from 'src/config/networks/network.d' + +const baseConfig: EnvironmentSettings = { + txServiceUrl: 'https://safe-transaction.mainnet.staging.gnosisdev.com/api/v1', + safeAppsUrl: 'https://safe-apps.dev.gnosisdev.com', + gasPriceOracleUrl: 'https://ethgasstation.info/json/ethgasAPI.json', + rpcServiceUrl: 'https://mainnet.infura.io:443/v3', + networkExplorerName: 'Etherscan', + networkExplorerUrl: 'https://etherscan.io', + networkExplorerApiUrl: 'https://api.etherscan.io/api', +} + +const mainnet: NetworkConfig = { + environment: { + dev: { + ...baseConfig, + }, + staging: { + ...baseConfig, + safeAppsUrl: 'https://safe-apps.staging.gnosisdev.com', + }, + production: { + ...baseConfig, + txServiceUrl: 'https://safe-transaction.mainnet.gnosis.io/api/v1', + safeAppsUrl: 'https://apps.gnosis-safe.io', + }, + }, + network: { + id: ETHEREUM_NETWORK.MAINNET, + backgroundColor: '#E8E7E6', + textColor: '#001428', + label: 'Mainnet', + isTestNet: false, + nativeCoin: { + address: '0x000', + name: 'Ether', + symbol: 'ETH', + decimals: 18, + logoUri: EtherLogo, + }, + } +} + +export default mainnet diff --git a/src/config/networks/network.d.ts b/src/config/networks/network.d.ts new file mode 100644 index 00000000..c42b6ef8 --- /dev/null +++ b/src/config/networks/network.d.ts @@ -0,0 +1,77 @@ +// matches src/logic/tokens/store/model/token.ts `TokenProps` type + +export enum FEATURES { + ERC721 = 'ERC721', + ERC1155 = 'ERC1155', + SAFE_APPS = 'SAFE_APPS', + CONTRACT_INTERACTION = 'CONTRACT_INTERACTION' +} + +type Token = { + address: string + name: string + symbol: string + decimals: number + logoUri?: string +} + +export enum ETHEREUM_NETWORK { + MAINNET = 1, + MORDEN = 2, + ROPSTEN = 3, + RINKEBY = 4, + GOERLI = 5, + KOVAN = 42, + XDAI = 100, + ENERGY_WEB_CHAIN = 246, + VOLTA = 73799, + UNKNOWN = 0, + LOCAL = 4447, +} + +export type NetworkSettings = { + // TODO: id now seems to be unnecessary + id: ETHEREUM_NETWORK, + backgroundColor: string, + textColor: string, + label: string, + isTestNet: boolean, + nativeCoin: Token, +} + +// something around this to display or not some critical sections in the app, depending on the network support +// I listed the ones that may conflict with the network. +// If non is present, all the sections are available. +export type SafeFeatures = FEATURES[] + +type GasPrice = { + gasPrice: number + gasPriceOracleUrl?: string +} | { + gasPrice?: number + // for infura there's a REST API Token required stored in: `REACT_APP_INFURA_TOKEN` + gasPriceOracleUrl: string +} + +export type EnvironmentSettings = GasPrice & { + txServiceUrl: string + // Shall we keep a reference to the relay? + relayApiUrl?: string + safeAppsUrl: string + rpcServiceUrl: string + networkExplorerName: string + networkExplorerUrl: string + networkExplorerApiUrl: string +} + +type SafeEnvironments = { + dev?: EnvironmentSettings + staging?: EnvironmentSettings + production: EnvironmentSettings +} + +export interface NetworkConfig { + network: NetworkSettings + disabledFeatures?: SafeFeatures + environment: SafeEnvironments +} diff --git a/src/config/networks/rinkeby.ts b/src/config/networks/rinkeby.ts new file mode 100644 index 00000000..2f0650b0 --- /dev/null +++ b/src/config/networks/rinkeby.ts @@ -0,0 +1,45 @@ +import EtherLogo from 'src/assets/icons/icon_etherTokens.svg' +import { EnvironmentSettings, ETHEREUM_NETWORK, NetworkConfig } from 'src/config/networks/network.d' + +const baseConfig: EnvironmentSettings = { + txServiceUrl: 'https://safe-transaction.staging.gnosisdev.com/api/v1', + safeAppsUrl: 'https://safe-apps.dev.gnosisdev.com', + gasPriceOracleUrl: 'https://ethgasstation.info/json/ethgasAPI.json', + rpcServiceUrl: 'https://rinkeby.infura.io:443/v3', + networkExplorerName: 'Etherscan', + networkExplorerUrl: 'https://rinkeby.etherscan.io', + networkExplorerApiUrl: 'https://api-rinkeby.etherscan.io/api', +} + +const rinkeby: NetworkConfig = { + environment: { + dev: { + ...baseConfig, + }, + staging: { + ...baseConfig, + safeAppsUrl: 'https://safe-apps.staging.gnosisdev.com', + }, + production: { + ...baseConfig, + txServiceUrl: 'https://safe-transaction.rinkeby.gnosis.io/api/v1', + safeAppsUrl: 'https://apps.gnosis-safe.io', + }, + }, + network: { + id: ETHEREUM_NETWORK.RINKEBY, + backgroundColor: '#E8673C', + textColor: '#ffffff', + label: 'Rinkeby', + isTestNet: true, + nativeCoin: { + address: '0x000', + name: 'Ether', + symbol: 'ETH', + decimals: 18, + logoUri: EtherLogo, + }, + }, +} + +export default rinkeby diff --git a/src/config/networks/xdai.ts b/src/config/networks/xdai.ts new file mode 100644 index 00000000..c14ea16d --- /dev/null +++ b/src/config/networks/xdai.ts @@ -0,0 +1,40 @@ +import { EnvironmentSettings, ETHEREUM_NETWORK, NetworkConfig } from 'src/config/networks/network.d' + +const baseConfig: EnvironmentSettings = { + txServiceUrl: 'https://safe-transaction.xdai.gnosis.io/api/v1', + safeAppsUrl: 'https://safe-apps-xdai.staging.gnosisdev.com', + gasPrice: 1e9, + rpcServiceUrl: 'https://dai.poa.network/', + networkExplorerName: 'Blockscout', + networkExplorerUrl: 'https://blockscout.com/poa/xdai', + networkExplorerApiUrl: 'https://blockscout.com/poa/xdai/api', +} + +const xDai: NetworkConfig = { + environment: { + staging: { + ...baseConfig + }, + production: { + ...baseConfig, + safeAppsUrl: 'https://apps-xdai.gnosis-safe.io', + + }, + }, + network: { + id: ETHEREUM_NETWORK.XDAI, + backgroundColor: '#48A8A6', + textColor: '#ffffff', + label: 'xDai', + isTestNet: false, + nativeCoin: { + address: '0x000', + name: 'xDai', + symbol: 'xDai', + decimals: 18, + logoUri: '', + }, + } +} + +export default xDai diff --git a/src/config/production-mainnet.ts b/src/config/production-mainnet.ts deleted file mode 100644 index d24d20e1..00000000 --- a/src/config/production-mainnet.ts +++ /dev/null @@ -1,11 +0,0 @@ -// -import prodConfig from './production' -import { TX_SERVICE_HOST, RELAY_API_URL } from 'src/config/names' - -const prodMainnetConfig = { - ...prodConfig, - [TX_SERVICE_HOST]: 'https://safe-transaction.mainnet.gnosis.io/api/v1/', - [RELAY_API_URL]: 'https://safe-relay.gnosis.io/api/v1/', -} - -export default prodMainnetConfig diff --git a/src/config/production.ts b/src/config/production.ts deleted file mode 100644 index 246fcb9c..00000000 --- a/src/config/production.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL, SAFE_APPS_URL } from 'src/config/names' - -const prodConfig = { - [TX_SERVICE_HOST]: 'https://safe-transaction.rinkeby.gnosis.io/api/v1/', - [SIGNATURES_VIA_METAMASK]: false, - [RELAY_API_URL]: 'https://safe-relay.rinkeby.gnosis.io/api/v1/', - [SAFE_APPS_URL]: 'https://apps.gnosis-safe.io/' -} - -export default prodConfig diff --git a/src/config/staging-mainnet.ts b/src/config/staging-mainnet.ts deleted file mode 100644 index 08f225fa..00000000 --- a/src/config/staging-mainnet.ts +++ /dev/null @@ -1,11 +0,0 @@ -// -import stagingConfig from './staging' -import { TX_SERVICE_HOST, RELAY_API_URL } from 'src/config/names' - -const stagingMainnetConfig = { - ...stagingConfig, - [TX_SERVICE_HOST]: 'https://safe-transaction.mainnet.staging.gnosisdev.com/api/v1/', - [RELAY_API_URL]: 'https://safe-relay.mainnet.staging.gnosisdev.com/api/v1/', -} - -export default stagingMainnetConfig diff --git a/src/config/staging.ts b/src/config/staging.ts deleted file mode 100644 index 61b4ebca..00000000 --- a/src/config/staging.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL, SAFE_APPS_URL } from 'src/config/names' - -const stagingConfig = { - [TX_SERVICE_HOST]: 'https://safe-transaction.staging.gnosisdev.com/api/v1/', - [SIGNATURES_VIA_METAMASK]: false, - [RELAY_API_URL]: 'https://safe-relay.staging.gnosisdev.com/api/v1/', - [SAFE_APPS_URL]: 'https://safe-apps.staging.gnosisdev.com' -} - -export default stagingConfig diff --git a/src/config/testing.ts b/src/config/testing.ts deleted file mode 100644 index cce8aa96..00000000 --- a/src/config/testing.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL, SAFE_APPS_URL } from 'src/config/names' - -const testConfig = { - [TX_SERVICE_HOST]: 'http://localhost:8000/api/v1/', - [SIGNATURES_VIA_METAMASK]: false, - [RELAY_API_URL]: 'https://safe-relay.staging.gnosisdev.com/api/v1', - [SAFE_APPS_URL]: 'http://localhost:3002/' -} - -export default testConfig diff --git a/src/logic/addressBook/store/middleware/addressBookMiddleware.ts b/src/logic/addressBook/store/middleware/addressBookMiddleware.ts index fb7075dd..0d982ace 100644 --- a/src/logic/addressBook/store/middleware/addressBookMiddleware.ts +++ b/src/logic/addressBook/store/middleware/addressBookMiddleware.ts @@ -7,6 +7,9 @@ import { saveAddressBook } from 'src/logic/addressBook/utils' import { enhanceSnackbarForAction, getNotificationsFromTxType } from 'src/logic/notifications' import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackbar' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' +import { safesListSelector } from 'src/logic/safe/store/selectors' +import { sameAddress } from 'src/logic/wallets/ethAddresses' +import updateSafe from 'src/logic/safe/store/actions/updateSafe' const watchedActions = [ADD_ENTRY, REMOVE_ENTRY, UPDATE_ENTRY, ADD_OR_UPDATE_ENTRY] @@ -17,6 +20,7 @@ const addressBookMiddleware = (store) => (next) => async (action) => { const state = store.getState() const { dispatch } = store const addressBook = addressBookSelector(state) + const safes = safesListSelector(state) if (addressBook.length) { await saveAddressBook(addressBook) } @@ -36,8 +40,13 @@ const addressBookMiddleware = (store) => (next) => async (action) => { break } case UPDATE_ENTRY: { + const { entry } = action.payload const notification = getNotificationsFromTxType(TX_NOTIFICATION_TYPES.ADDRESSBOOK_EDIT_ENTRY) dispatch(enqueueSnackbar(enhanceSnackbarForAction(notification.afterExecution.noMoreConfirmationsNeeded))) + const safeFound = safes.find((safe) => sameAddress(safe.address, entry.address)) + if (safeFound) { + dispatch(updateSafe({ address: safeFound.address, name: entry.name })) + } break } default: diff --git a/src/logic/addressBook/store/reducer/addressBook.ts b/src/logic/addressBook/store/reducer/addressBook.ts index ad23f4fd..a59e2ab0 100644 --- a/src/logic/addressBook/store/reducer/addressBook.ts +++ b/src/logic/addressBook/store/reducer/addressBook.ts @@ -29,9 +29,7 @@ export default handleActions( const entryFound = state.find((oldEntry) => oldEntry.address === entry.address) - // Only adds entries with valid names - const validName = getValidAddressBookName(entry.name) - if (!entryFound && validName) { + if (!entryFound) { state.push(entry) } return state diff --git a/src/logic/collectibles/sources/Gnosis.ts b/src/logic/collectibles/sources/Gnosis.ts new file mode 100644 index 00000000..c916a653 --- /dev/null +++ b/src/logic/collectibles/sources/Gnosis.ts @@ -0,0 +1,106 @@ +import { RateLimit } from 'async-sema' + +import { Collectibles, NFTAsset, NFTAssets, NFTTokens } from 'src/logic/collectibles/sources/collectibles.d' +import NFTIcon from 'src/routes/safe/components/Balances/assets/nft_icon.png' +import { fetchErc20AndErc721AssetsList, fetchSafeCollectibles } from 'src/logic/tokens/api' +import { TokenResult } from 'src/logic/tokens/api/fetchErc20AndErc721AssetsList' +import { CollectibleResult } from 'src/logic/tokens/api/fetchSafeCollectibles' + +type FetchResult = { + erc721Assets: TokenResult[] + erc721Tokens: CollectibleResult[] +} + +class Gnosis { + _rateLimit = async (): Promise => {} + + _fetch = async (safeAddress: string): Promise => { + const collectibles: FetchResult = { + erc721Assets: [], + erc721Tokens: [], + } + + try { + const { + data: { results: assets = [] }, + } = await fetchErc20AndErc721AssetsList() + collectibles.erc721Assets = assets.filter((token) => token.type.toLowerCase() === 'erc721') + } catch (e) { + console.error('no erc721 assets could be fetched', e) + } + + try { + const { data: tokens = [] } = await fetchSafeCollectibles(safeAddress) + collectibles.erc721Tokens = tokens + } catch (e) { + console.error('no erc721 tokens for the current safe', e) + } + + return collectibles + } + + /** + * OpenSea class constructor + * @param {object} options + * @param {number} options.rps - requests per second + */ + constructor(options: { rps: number }) { + // eslint-disable-next-line no-underscore-dangle + this._rateLimit = RateLimit(options.rps, { timeUnit: 60 * 1000, uniformDistribution: true }) + } + + static extractAssets(assets: TokenResult[], nftTokens: NFTTokens): NFTAssets { + const extractNFTAsset = (asset: TokenResult): NFTAsset => { + const numberOfTokens = nftTokens.filter(({ assetAddress }) => assetAddress === asset.address).length + + return { + address: asset.address, + description: asset.name, + image: asset.logoUri || NFTIcon, + name: asset.name, + numberOfTokens, + slug: `${asset.address}_${asset.name}`, + symbol: asset.symbol, + } + } + + return assets.reduce((acc, asset) => { + const address = asset.address + + if (acc[address] === undefined) { + acc[address] = extractNFTAsset(asset) + } + + return acc + }, {}) + } + + static extractTokens(tokens: CollectibleResult[]): NFTTokens { + return tokens.map((token) => ({ + assetAddress: token.address, + color: 'red', + description: token.description || '', + image: token.imageUri || NFTIcon, + name: token.name || '', + tokenId: token.id, + })) + } + + /** + * Fetches from OpenSea the list of collectibles, grouped by category, + * for the provided Safe Address in the specified Network + * @param {string} safeAddress + * @returns {Promise} + */ + async fetchCollectibles(safeAddress: string): Promise { + const { erc721Assets, erc721Tokens } = await this._fetch(safeAddress) + const nftTokens = Gnosis.extractTokens(erc721Tokens) + + return { + nftTokens, + nftAssets: Gnosis.extractAssets(erc721Assets, nftTokens), + } + } +} + +export default Gnosis diff --git a/src/logic/collectibles/sources/OpenSea.ts b/src/logic/collectibles/sources/OpenSea.ts index d626e825..97661b76 100644 --- a/src/logic/collectibles/sources/OpenSea.ts +++ b/src/logic/collectibles/sources/OpenSea.ts @@ -1,61 +1,11 @@ import { RateLimit } from 'async-sema' +import { getNetworkId } from 'src/config' -import { ETHEREUM_NETWORK } from 'src/logic/wallets/getWeb3' +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' +import { Collectibles, NFTAssets, NFTTokens, OpenSeaAssets } from 'src/logic/collectibles/sources/collectibles.d' import NFTIcon from 'src/routes/safe/components/Balances/assets/nft_icon.png' import { OPENSEA_API_KEY } from 'src/utils/constants' -export interface OpenSeaAssetContract { - address: string - name: string - image_url: string - symbol: string -} - -export interface OpenSeaCollection { - name: string - slug: string -} - -export interface OpenSeaAsset { - asset_contract: OpenSeaAssetContract - background_color: string - collection: OpenSeaCollection - description: string - image_thumbnail_url: string - name: string - token_id: string -} - -export type OpenSeaAssets = Array - -export interface NFTAsset { - address: string - assetContract: OpenSeaAssetContract - collection: OpenSeaCollection - description: string - image: string - name: string - numberOfTokens: number - slug: string - symbol: string -} -export type NFTAssets = Record - -export interface NFTToken { - assetAddress: string - color: string - description: string - image: string - name: string - tokenId: number | string -} -export type NFTTokens = Array - -export interface Collectibles { - nftAssets: NFTAssets - nftTokens: NFTTokens -} - class OpenSea { _rateLimit = async (): Promise => {} @@ -133,14 +83,11 @@ class OpenSea { * Fetches from OpenSea the list of collectibles, grouped by category, * for the provided Safe Address in the specified Network * @param {string} safeAddress - * @param {string} network * @returns {Promise} */ - async fetchAllUserCollectiblesByCategoryAsync(safeAddress: string, network: string): Promise { - // eslint-disable-next-line no-underscore-dangle - const metadataSourceUrl = this._endpointsUrls[network] + async fetchCollectibles(safeAddress: string): Promise { + const metadataSourceUrl = this._endpointsUrls[getNetworkId()] const url = `${metadataSourceUrl}/assets/?owner=${safeAddress}` - // eslint-disable-next-line no-underscore-dangle const assetsResponse = await this._fetch(url) const assetsResponseJson = await assetsResponse.json() return OpenSea.extractCollectiblesInfo(assetsResponseJson) diff --git a/src/logic/collectibles/sources/collectibles.d.ts b/src/logic/collectibles/sources/collectibles.d.ts new file mode 100644 index 00000000..88edabf8 --- /dev/null +++ b/src/logic/collectibles/sources/collectibles.d.ts @@ -0,0 +1,53 @@ +export interface OpenSeaAssetContract { + address: string + name: string + image_url: string + symbol: string +} + +export interface OpenSeaCollection { + name: string + slug: string +} + +export interface OpenSeaAsset { + asset_contract: OpenSeaAssetContract + background_color: string + collection: OpenSeaCollection + description: string + image_thumbnail_url: string + name: string + token_id: string +} + +export type OpenSeaAssets = Array + +export interface NFTAsset { + address: string + assetContract?: OpenSeaAssetContract + collection?: OpenSeaCollection + description: string + image: string + name: string + numberOfTokens: number + slug: string + symbol: string +} + +export type NFTAssets = Record + +export interface NFTToken { + assetAddress: string + color: string + description: string + image: string + name: string + tokenId: number | string +} + +export type NFTTokens = Array + +export interface Collectibles { + nftAssets: NFTAssets + nftTokens: NFTTokens +} diff --git a/src/logic/collectibles/sources/index.ts b/src/logic/collectibles/sources/index.ts index 14636bd2..439cf05f 100644 --- a/src/logic/collectibles/sources/index.ts +++ b/src/logic/collectibles/sources/index.ts @@ -1,13 +1,15 @@ import MockedOpenSea from 'src/logic/collectibles/sources/MockedOpenSea' import OpenSea from 'src/logic/collectibles/sources/OpenSea' +import Gnosis from 'src/logic/collectibles/sources/Gnosis' import { COLLECTIBLES_SOURCE } from 'src/utils/constants' const SOURCES = { opensea: new OpenSea({ rps: 4 }), + gnosis: new Gnosis({ rps: 4 }), mockedopensea: new MockedOpenSea({ rps: 4 }), } type Sources = typeof SOURCES -export const getConfiguredSource = (): Sources['opensea'] | Sources['mockedopensea'] => +export const getConfiguredSource = (): Sources['opensea'] | Sources['mockedopensea'] | Sources['gnosis'] => SOURCES[COLLECTIBLES_SOURCE.toLowerCase()] diff --git a/src/logic/collectibles/store/actions/fetchCollectibles.ts b/src/logic/collectibles/store/actions/fetchCollectibles.ts index c87e51df..40d3e6a5 100644 --- a/src/logic/collectibles/store/actions/fetchCollectibles.ts +++ b/src/logic/collectibles/store/actions/fetchCollectibles.ts @@ -1,15 +1,13 @@ import { batch } from 'react-redux' +import { Dispatch } from 'redux' -import { getNetwork } from 'src/config' import { getConfiguredSource } from 'src/logic/collectibles/sources' import { addNftAssets, addNftTokens } from 'src/logic/collectibles/store/actions/addCollectibles' -import { Dispatch } from 'redux' const fetchCollectibles = (safeAddress: string) => async (dispatch: Dispatch): Promise => { try { - const network = getNetwork() const source = getConfiguredSource() - const collectibles = await source.fetchAllUserCollectiblesByCategoryAsync(safeAddress, network) + const collectibles = await source.fetchCollectibles(safeAddress) batch(() => { dispatch(addNftAssets(collectibles.nftAssets)) diff --git a/src/logic/collectibles/store/selectors/index.ts b/src/logic/collectibles/store/selectors/index.ts index 3829eb00..3a922048 100644 --- a/src/logic/collectibles/store/selectors/index.ts +++ b/src/logic/collectibles/store/selectors/index.ts @@ -1,14 +1,18 @@ import { createSelector } from 'reselect' -import { NFTAsset, NFTAssets, NFTTokens } from 'src/logic/collectibles/sources/OpenSea' +import { NFTAsset, NFTAssets, NFTTokens } from 'src/logic/collectibles/sources/collectibles.d' import { AppReduxState } from 'src/store' import { NFT_ASSETS_REDUCER_ID, NFT_TOKENS_REDUCER_ID } from 'src/logic/collectibles/store/reducer/collectibles' import { safeActiveAssetsSelector } from 'src/logic/safe/store/selectors' -export const nftAssetsSelector = (state: AppReduxState): NFTAssets => state[NFT_ASSETS_REDUCER_ID] -export const nftTokensSelector = (state: AppReduxState): NFTTokens => state[NFT_TOKENS_REDUCER_ID] +export const nftAssets = (state: AppReduxState): NFTAssets => state[NFT_ASSETS_REDUCER_ID] +export const nftTokens = (state: AppReduxState): NFTTokens => state[NFT_TOKENS_REDUCER_ID] -export const nftAssetsListSelector = createSelector(nftAssetsSelector, (assets): NFTAsset[] => { +export const nftAssetsSelector = createSelector(nftAssets, (assets) => assets) + +export const nftTokensSelector = createSelector(nftTokens, (tokens) => tokens) + +export const nftAssetsListSelector = createSelector(nftAssets, (assets): NFTAsset[] => { return assets ? Object.values(assets) : [] }) diff --git a/src/logic/contractInteraction/sources/EtherscanService.ts b/src/logic/contractInteraction/sources/EtherscanService.ts deleted file mode 100644 index d9e7bfc5..00000000 --- a/src/logic/contractInteraction/sources/EtherscanService.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { RateLimit } from 'async-sema' -import memoize from 'lodash.memoize' - -import { ETHEREUM_NETWORK } from 'src/logic/wallets/getWeb3' -import { ETHERSCAN_API_KEY } from 'src/utils/constants' - -class EtherscanService { - _rateLimit = async () => {} - - _endpointsUrls = { - [ETHEREUM_NETWORK.MAINNET]: 'https://api.etherscan.io/api', - [ETHEREUM_NETWORK.RINKEBY]: 'https://api-rinkeby.etherscan.io/api', - } - - _fetch = memoize( - async (url: string, contractAddress: string) => { - let params: any = { - module: 'contract', - action: 'getAbi', - address: contractAddress, - } - - if (ETHERSCAN_API_KEY) { - const apiKey = ETHERSCAN_API_KEY - params = { ...params, apiKey } - } - - const response = await fetch(`${url}?${new URLSearchParams(params)}`) - - if (!response.ok) { - return { status: 0, result: [] } - } - - return response.json() - }, - (url, contractAddress) => `${url}_${contractAddress}`, - ) - - constructor(options) { - this._rateLimit = RateLimit(options.rps) - } - - async getContractABI(contractAddress, network) { - const etherscanUrl = this._endpointsUrls[network] - try { - const { result, status } = await this._fetch(etherscanUrl, contractAddress) - - if (status === '0') { - return [] - } - - return result - } catch (e) { - console.error('Failed to retrieve ABI', e) - return undefined - } - } -} - -export default EtherscanService diff --git a/src/logic/contractInteraction/sources/index.ts b/src/logic/contractInteraction/sources/index.ts deleted file mode 100644 index b5bc4f3f..00000000 --- a/src/logic/contractInteraction/sources/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import EtherscanService from 'src/logic/contractInteraction/sources/EtherscanService' - -const sources = { - etherscan: new EtherscanService({ rps: 4 }), -} - -export const getConfiguredSource = () => sources['etherscan'] diff --git a/src/logic/contracts/generateBatchRequests.ts b/src/logic/contracts/generateBatchRequests.ts index 2d48979b..0ed3c09c 100644 --- a/src/logic/contracts/generateBatchRequests.ts +++ b/src/logic/contracts/generateBatchRequests.ts @@ -1,4 +1,6 @@ import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3' +import { BatchRequest } from 'web3-core' +import { AbiItem } from 'web3-utils' /** * Generates a batch request for grouping RPC calls @@ -10,23 +12,33 @@ import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3' * @param {array<{ args: [any], method: string, type: 'eth'|undefined } | string>} args.methods - methods to be called * @returns {Promise<[*]>} */ -const generateBatchRequests = ({ abi, address, batch, context, methods }: any): any => { - const contractInstance: any = new web3.eth.Contract(abi, address) +type MethodsArgsType = Array + + interface Props { + abi: AbiItem[] + address: string + batch?: BatchRequest + context?: unknown + methods: Array + } + +const generateBatchRequests = ({ abi, address, batch, context, methods }: Props): Promise => { + const contractInstance = new web3.eth.Contract(abi, address) const localBatch = new web3.BatchRequest() const values = methods.map((methodObject) => { - let method, type, args = [] + let method, type, args: MethodsArgsType = [] if (typeof methodObject === 'string') { method = methodObject } else { - ;({ method, type, args = [] } = methodObject) + ({ method, type, args } = methodObject) } return new Promise((resolve) => { const resolver = (error, result) => { if (error) { - resolve(null) + resolve() } else { resolve(result) } @@ -43,7 +55,8 @@ const generateBatchRequests = ({ abi, address, batch, context, methods }: any): // If batch was provided add to external batch batch ? batch.add(request) : localBatch.add(request) } catch (e) { - resolve(null) + console.error('There was an error trying to batch request from web3.', e) + resolve() } }) }) @@ -54,9 +67,8 @@ const generateBatchRequests = ({ abi, address, batch, context, methods }: any): // in the outside function where the batch object is created. !batch && localBatch.execute() - const returnValues = context ? [context, ...values] : values - - return Promise.all(returnValues) + // @ts-ignore + return Promise.all([context, ...values]) } export default generateBatchRequests diff --git a/src/logic/contracts/safeContracts.ts b/src/logic/contracts/safeContracts.ts index 68d971f9..b9673cf9 100644 --- a/src/logic/contracts/safeContracts.ts +++ b/src/logic/contracts/safeContracts.ts @@ -1,17 +1,17 @@ import { AbiItem } from 'web3-utils' -import contract from 'truffle-contract' -import Web3 from 'web3' -import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafeProxyFactory.json' import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json' -import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafeProxy.json' -import { ensureOnce } from 'src/utils/singleton' import memoize from 'lodash.memoize' -import { getWeb3, getNetworkIdFrom } from 'src/logic/wallets/getWeb3' -import { calculateGasOf, calculateGasPrice } from 'src/logic/wallets/ethTransactions' -import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' +import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafeProxyFactory.json' +import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafeProxy.json' +import Web3 from 'web3' + +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' import { isProxyCode } from 'src/logic/contracts/historicProxyCode' -import { GnosisSafeProxyFactory } from 'src/types/contracts/GnosisSafeProxyFactory.d'; +import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' +import { calculateGasOf, calculateGasPrice } from 'src/logic/wallets/ethTransactions' +import { getWeb3, getNetworkIdFrom } from 'src/logic/wallets/getWeb3' import { GnosisSafe } from 'src/types/contracts/GnosisSafe.d' +import { GnosisSafeProxyFactory } from 'src/types/contracts/GnosisSafeProxyFactory.d' export const SENTINEL_ADDRESS = '0x0000000000000000000000000000000000000001' export const MULTI_SEND_ADDRESS = '0x8d29be29923b68abfdd21e541b9374737b49cdad' @@ -20,24 +20,39 @@ export const DEFAULT_FALLBACK_HANDLER_ADDRESS = '0xd5D82B6aDDc9027B22dCA772Aa68D export const SAFE_MASTER_COPY_ADDRESS_V10 = '0xb6029EA3B2c51D09a50B53CA8012FeEB05bDa35A' -let proxyFactoryMaster -let safeMaster +let proxyFactoryMaster: GnosisSafeProxyFactory +let safeMaster: GnosisSafe -const createGnosisSafeContract = (web3: Web3) => { - const gnosisSafe = contract(GnosisSafeSol) - gnosisSafe.setProvider(web3.currentProvider) - - return gnosisSafe +/** + * Creates a Contract instance of the GnosisSafe contract + * @param {Web3} web3 + * @param {ETHEREUM_NETWORK} networkId + */ +const createGnosisSafeContract = (web3: Web3, networkId: ETHEREUM_NETWORK) => { + const networks = GnosisSafeSol.networks + // TODO: this may not be the most scalable approach, + // but up until v1.2.0 the address is the same for all the networks. + // So, if we can't find the network in the Contract artifact, we fallback to MAINNET. + const contractAddress = networks[networkId]?.address ?? networks[ETHEREUM_NETWORK.MAINNET].address + return new web3.eth.Contract(GnosisSafeSol.abi as AbiItem[], contractAddress) as unknown as GnosisSafe } -const createProxyFactoryContract = (web3: Web3, networkId: number): GnosisSafeProxyFactory => { - const contractAddress = ProxyFactorySol.networks[networkId].address - const proxyFactory = new web3.eth.Contract(ProxyFactorySol.abi as AbiItem[], contractAddress) as unknown as GnosisSafeProxyFactory - - return proxyFactory +/** + * Creates a Contract instance of the GnosisSafeProxyFactory contract + * @param {Web3} web3 + * @param {ETHEREUM_NETWORK} networkId + */ +const createProxyFactoryContract = (web3: Web3, networkId: ETHEREUM_NETWORK): GnosisSafeProxyFactory => { + const networks = ProxyFactorySol.networks + // TODO: this may not be the most scalable approach, + // but up until v1.2.0 the address is the same for all the networks. + // So, if we can't find the network in the Contract artifact, we fallback to MAINNET. + const contractAddress = networks[networkId]?.address ?? networks[ETHEREUM_NETWORK.MAINNET].address + return new web3.eth.Contract(ProxyFactorySol.abi as AbiItem[], contractAddress) as unknown as GnosisSafeProxyFactory } export const getGnosisSafeContract = memoize(createGnosisSafeContract) + const getCreateProxyFactoryContract = memoize(createProxyFactoryContract) const instantiateMasterCopies = async () => { @@ -47,25 +62,11 @@ const instantiateMasterCopies = async () => { // Create ProxyFactory Master Copy proxyFactoryMaster = getCreateProxyFactoryContract(web3, networkId) - // Initialize Safe master copy - const GnosisSafe = getGnosisSafeContract(web3) - safeMaster = await GnosisSafe.deployed() + // Create Safe Master copy + safeMaster = getGnosisSafeContract(web3, networkId) } -// ONLY USED IN TEST ENVIRONMENT -const createMasterCopies = async () => { - const web3 = getWeb3() - const accounts = await web3.eth.getAccounts() - const userAccount = accounts[0] - - const ProxyFactory = getCreateProxyFactoryContract(web3, 4441) - proxyFactoryMaster = await ProxyFactory.deploy({ data: GnosisSafeSol.bytecode }).send({ from: userAccount, gas: 5000000 }) - - const GnosisSafe = getGnosisSafeContract(web3) - safeMaster = await GnosisSafe.new({ from: userAccount, gas: '7000000' }) -} - -export const initContracts = process.env.NODE_ENV === 'test' ? ensureOnce(createMasterCopies) : instantiateMasterCopies +export const initContracts = instantiateMasterCopies export const getSafeMasterContract = async () => { await initContracts() @@ -74,11 +75,11 @@ export const getSafeMasterContract = async () => { } export const getSafeDeploymentTransaction = (safeAccounts, numConfirmations) => { - const gnosisSafeData = safeMaster.contract.methods + const gnosisSafeData = safeMaster.methods .setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', DEFAULT_FALLBACK_HANDLER_ADDRESS, ZERO_ADDRESS, 0, ZERO_ADDRESS) .encodeABI() - return proxyFactoryMaster.methods.createProxy(safeMaster.address, gnosisSafeData) + return proxyFactoryMaster.methods.createProxy(safeMaster.options.address, gnosisSafeData) } export const estimateGasForDeployingSafe = async ( @@ -86,13 +87,13 @@ export const estimateGasForDeployingSafe = async ( numConfirmations, userAccount, ) => { - const gnosisSafeData = await safeMaster.contract.methods + const gnosisSafeData = await safeMaster.methods .setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', DEFAULT_FALLBACK_HANDLER_ADDRESS, ZERO_ADDRESS, 0, ZERO_ADDRESS) .encodeABI() const proxyFactoryData = proxyFactoryMaster.methods - .createProxy(safeMaster.address, gnosisSafeData) + .createProxy(safeMaster.options.address, gnosisSafeData) .encodeABI() - const gas = await calculateGasOf(proxyFactoryData, userAccount, proxyFactoryMaster.address) + const gas = await calculateGasOf(proxyFactoryData, userAccount, proxyFactoryMaster.options.address) const gasPrice = await calculateGasPrice() return gas * parseInt(gasPrice, 10) diff --git a/src/logic/cookies/utils/index.ts b/src/logic/cookies/utils/index.ts index 08313a64..7b9f8ce7 100644 --- a/src/logic/cookies/utils/index.ts +++ b/src/logic/cookies/utils/index.ts @@ -1,8 +1,8 @@ import Cookies from 'js-cookie' -import { getNetwork } from 'src/config' +import { getNetworkName } from 'src/config' -const PREFIX = `v1_${getNetwork()}` +const PREFIX = `v1_${getNetworkName()}` export const loadFromCookie = async (key) => { try { diff --git a/src/logic/currencyValues/__tests__/fetchSafeTokens.test.ts b/src/logic/currencyValues/__tests__/fetchSafeTokens.test.ts index 32bf362f..d4d8f76a 100644 --- a/src/logic/currencyValues/__tests__/fetchSafeTokens.test.ts +++ b/src/logic/currencyValues/__tests__/fetchSafeTokens.test.ts @@ -1,7 +1,7 @@ import { aNewStore } from 'src/store' -import fetchTokenCurrenciesBalances from 'src/logic/currencyValues/api/fetchTokenCurrenciesBalances' +import { fetchTokenCurrenciesBalances } from 'src/logic/currencyValues/api/fetchTokenCurrenciesBalances' import axios from 'axios' -import { getTxServiceHost } from 'src/config' +import { getTxServiceUrl } from 'src/config' jest.mock('axios') describe('fetchTokenCurrenciesBalances', () => { @@ -19,26 +19,28 @@ describe('fetchTokenCurrenciesBalances', () => { // given const expectedResult = [ { - balance: '849890000000000000', - balanceUsd: '337.2449', - token: null, tokenAddress: null, - usdConversion: '396.81', + token: null, + balance: '849890000000000000', + fiatBalance: '337.2449', + fiatConversion: '396.81', + fiatCode: 'USD', }, { - balance: '24698677800000000000', - balanceUsd: '29.3432', + tokenAddress: '0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa', token: { name: 'Dai', symbol: 'DAI', decimals: 18, logoUri: 'https://gnosis-safe-token-logos.s3.amazonaws.com/0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa.png', }, - tokenAddress: '0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa', - usdConversion: '1.188', + balance: '24698677800000000000', + fiatBalance: '29.3432', + fiatConversion: '1.188', + fiatCode: 'USD', }, ] - const apiUrl = getTxServiceHost() + const apiUrl = getTxServiceUrl() // @ts-ignore axios.get.mockImplementationOnce(() => Promise.resolve(expectedResult)) @@ -49,8 +51,6 @@ describe('fetchTokenCurrenciesBalances', () => { // then expect(result).toStrictEqual(expectedResult) expect(axios.get).toHaveBeenCalled() - expect(axios.get).toBeCalledWith(`${apiUrl}safes/${safeAddress}/balances/usd/?exclude_spam=${excludeSpamTokens}`, { - params: { limit: 3000 }, - }) + expect(axios.get).toBeCalledWith(`${apiUrl}/safes/${safeAddress}/balances/usd/?exclude_spam=${excludeSpamTokens}`) }) }) diff --git a/src/logic/currencyValues/api/fetchCurrenciesRates.ts b/src/logic/currencyValues/api/fetchCurrenciesRates.ts index 00e2c48a..45c73c71 100644 --- a/src/logic/currencyValues/api/fetchCurrenciesRates.ts +++ b/src/logic/currencyValues/api/fetchCurrenciesRates.ts @@ -1,8 +1,8 @@ import axios from 'axios' -import { getExchangeRatesUrl } from 'src/config' +import { EXCHANGE_RATE_URL } from 'src/utils/constants' import { AVAILABLE_CURRENCIES } from '../store/model/currencyValues' -import fetchTokenCurrenciesBalances from './fetchTokenCurrenciesBalances' +import { fetchTokenCurrenciesBalances } from './fetchTokenCurrenciesBalances' import BigNumber from 'bignumber.js' const fetchCurrenciesRates = async ( @@ -16,7 +16,7 @@ const fetchCurrenciesRates = async ( try { const result = await fetchTokenCurrenciesBalances(safeAddress) if (result?.data?.length) { - rate = new BigNumber(1).div(result.data[0].usdConversion).toNumber() + rate = new BigNumber(1).div(result.data[0].fiatConversion).toNumber() } } catch (error) { console.error('Fetching ETH data from the relayer errored', error) @@ -25,7 +25,7 @@ const fetchCurrenciesRates = async ( } try { - const url = `${getExchangeRatesUrl()}?base=${baseCurrency}&symbols=${targetCurrencyValue}` + const url = `${EXCHANGE_RATE_URL}?base=${baseCurrency}&symbols=${targetCurrencyValue}` const result = await axios.get(url) if (result?.data) { const { rates } = result.data diff --git a/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.ts b/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.ts index c2804708..59107c74 100644 --- a/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.ts +++ b/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.ts @@ -1,28 +1,24 @@ import axios, { AxiosResponse } from 'axios' -import { getTxServiceHost } from 'src/config' +import { getTxServiceUrl } from 'src/config' import { TokenProps } from 'src/logic/tokens/store/model/token' +import { AVAILABLE_CURRENCIES } from '../store/model/currencyValues' export type BalanceEndpoint = { - balance: string - balanceUsd: string tokenAddress: string token?: TokenProps - usdConversion: string + balance: string + fiatBalance: string + fiatConversion: string + fiatCode: AVAILABLE_CURRENCIES } -const fetchTokenCurrenciesBalances = ( +export const fetchTokenCurrenciesBalances = ( safeAddress: string, excludeSpamTokens = true, ): Promise> => { - const apiUrl = getTxServiceHost() - const url = `${apiUrl}safes/${safeAddress}/balances/usd/?exclude_spam=${excludeSpamTokens}` + const apiUrl = getTxServiceUrl() + const url = `${apiUrl}/safes/${safeAddress}/balances/usd/?exclude_spam=${excludeSpamTokens}` - return axios.get(url, { - params: { - limit: 3000, - }, - }) + return axios.get(url) } - -export default fetchTokenCurrenciesBalances diff --git a/src/logic/notifications/notificationTypes.ts b/src/logic/notifications/notificationTypes.ts index 7d4536af..97650456 100644 --- a/src/logic/notifications/notificationTypes.ts +++ b/src/logic/notifications/notificationTypes.ts @@ -1,7 +1,6 @@ import { OptionsObject } from 'notistack' -import { getNetwork } from 'src/config' -import { capitalize } from 'src/utils/css' +import { getNetworkName } from 'src/config' export const SUCCESS = 'success' export const ERROR = 'error' @@ -46,7 +45,7 @@ const NOTIFICATION_IDS = { SETTINGS_CHANGE_EXECUTED_MSG: 'SETTINGS_CHANGE_EXECUTED_MSG', SETTINGS_CHANGE_EXECUTED_MORE_CONFIRMATIONS_MSG: 'SETTINGS_CHANGE_EXECUTED_MORE_CONFIRMATIONS_MSG', SETTINGS_CHANGE_FAILED_MSG: 'SETTINGS_CHANGE_FAILED_MSG', - RINKEBY_VERSION_MSG: 'RINKEBY_VERSION_MSG', + TESTNET_VERSION_MSG: 'TESTNET_VERSION_MSG', WRONG_NETWORK_MSG: 'WRONG_NETWORK_MSG', ADDRESS_BOOK_NEW_ENTRY_SUCCESS: 'ADDRESS_BOOK_NEW_ENTRY_SUCCESS', ADDRESS_BOOK_EDIT_ENTRY_SUCCESS: 'ADDRESS_BOOK_EDIT_ENTRY_SUCCESS', @@ -193,12 +192,12 @@ export const NOTIFICATIONS: Record = { }, // Network - RINKEBY_VERSION_MSG: { - message: "Rinkeby Version: Don't send Mainnet assets to this Safe", + TESTNET_VERSION_MSG: { + message: "Testnet Version: Don't send production assets to this Safe", options: { variant: WARNING, persist: true, preventDuplicate: true }, }, WRONG_NETWORK_MSG: { - message: `Wrong network: Please use ${capitalize(getNetwork())}`, + message: `Wrong network: Please use ${getNetworkName()}`, options: { variant: WARNING, persist: true, preventDuplicate: true }, }, diff --git a/src/logic/safe/hooks/useSafeActions.tsx b/src/logic/safe/hooks/useSafeActions.tsx index 38bc655b..a97e09ab 100644 --- a/src/logic/safe/hooks/useSafeActions.tsx +++ b/src/logic/safe/hooks/useSafeActions.tsx @@ -1,6 +1,14 @@ import { useState, useMemo } from 'react' -const INITIAL_STATE = { +type SafeActionsState = { + sendFunds: { + isOpen: boolean + selectedToken?: string + } + showReceive: boolean +} + +const INITIAL_STATE: SafeActionsState = { sendFunds: { isOpen: false, selectedToken: undefined, @@ -13,7 +21,7 @@ type Response = { onHide: (action: string) => void showSendFunds: (token: string) => void hideSendFunds: () => void - safeActionsState: Record + safeActionsState: SafeActionsState } const useSafeActions = (): Response => { diff --git a/src/logic/safe/store/actions/__tests__/transactionHelpers.test.ts b/src/logic/safe/store/actions/__tests__/transactionHelpers.test.ts index 4076f002..6fb7a15d 100644 --- a/src/logic/safe/store/actions/__tests__/transactionHelpers.test.ts +++ b/src/logic/safe/store/actions/__tests__/transactionHelpers.test.ts @@ -572,18 +572,6 @@ describe('calculateTransactionStatus', () => { // then expect(result).toBe(TransactionStatus.PENDING) }) - it('It should return PENDING if the tx has no confirmations', () => { - // given - const transaction = makeTransaction({ confirmations: List(), isPending: false }) - const safe = makeSafe({ threshold: 3 }) - const currentUser = safeAddress - - // when - const result = calculateTransactionStatus(transaction, safe, currentUser) - - // then - expect(result).toBe(TransactionStatus.PENDING) - }) it('It should return AWAITING_CONFIRMATIONS if the tx has confirmations bellow the threshold, the user is owner and signed', () => { // given const userAddress = 'address1' @@ -762,7 +750,36 @@ describe('calculateTransactionType', () => { describe('buildTx', () => { it('Returns a valid transaction', async () => { // given - const cancelTx1 = makeTransaction() + const cancelTx1 = { + baseGas: 0, + blockNumber: 0, + confirmations: [], + confirmationsRequired: 2, + data: null, + dataDecoded: undefined, + ethGasPrice: '0', + executionDate: null, + executor: '', + fee: '', + gasPrice: '', + gasToken: '', + gasUsed: 0, + isExecuted: false, + isSuccessful: true, + modified: '', + nonce: 0, + operation: 0, + origin: null, + refundReceiver: '', + safe: '', + safeTxGas: 0, + safeTxHash: '', + signatures: '', + submissionDate: null, + to: '', + transactionHash: null, + value: '', + } const transaction = getMockedTxServiceModel({ to: safeAddress2, value: '0' }) const userAddress = 'address1' const cancellationTxs = List([cancelTx1]) @@ -776,7 +793,7 @@ describe('buildTx', () => { }) const knownTokens = Map & Readonly>() knownTokens.set('0x00Df91984582e6e96288307E9c2f20b38C8FeCE9', token) - const outgoingTxs = List([cancelTx1]) + const outgoingTxs = [cancelTx1] const safeInstance = makeSafe({ name: 'LOADED SAFE', address: safeAddress }) const expectedTx = makeTransaction({ baseGas: 0, @@ -826,7 +843,7 @@ describe('buildTx', () => { outgoingTxs, safe: safeInstance, tx: transaction, - txCode: null, + txCode: undefined, }) // then diff --git a/src/logic/safe/store/actions/addOrUpdateSafe.ts b/src/logic/safe/store/actions/addOrUpdateSafe.ts index 8aafa0ea..92dd0faf 100644 --- a/src/logic/safe/store/actions/addOrUpdateSafe.ts +++ b/src/logic/safe/store/actions/addOrUpdateSafe.ts @@ -1,9 +1,18 @@ import { createAction } from 'redux-actions' -import { SafeRecordProps } from '../models/safe' +import { SafeOwner, SafeRecordProps } from '../models/safe' +import { List } from 'immutable' +import { makeOwner } from '../models/owner' export const ADD_OR_UPDATE_SAFE = 'ADD_OR_UPDATE_SAFE' -export const addOrUpdateSafe = createAction(ADD_OR_UPDATE_SAFE, (safe: SafeRecordProps) => ({ +export const buildOwnersFrom = (names: string[], addresses: string[]): List => { + const owners = names.map((name, index) => makeOwner({ name, address: addresses[index] })) + + return List(owners) +} + +export const addOrUpdateSafe = createAction(ADD_OR_UPDATE_SAFE, (safe: SafeRecordProps, loadedFromStorage = false) => ({ safe, + loadedFromStorage, })) diff --git a/src/logic/safe/store/actions/addSafe.ts b/src/logic/safe/store/actions/addSafe.ts deleted file mode 100644 index 90851dab..00000000 --- a/src/logic/safe/store/actions/addSafe.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { List } from 'immutable' -import { createAction } from 'redux-actions' - -import setDefaultSafe from 'src/logic/safe/store/actions/setDefaultSafe' -import { makeOwner } from 'src/logic/safe/store/models/owner' - -import { safesListSelector } from 'src/logic/safe/store/selectors' - -import { Dispatch } from 'redux' -import { AppReduxState } from 'src/store' -import { SafeOwner, SafeRecordProps } from 'src/logic/safe/store/models/safe' - -export const ADD_SAFE = 'ADD_SAFE' - -export const buildOwnersFrom = (names: string[], addresses: string[]): List => { - const owners = names.map((name, index) => makeOwner({ name, address: addresses[index] })) - - return List(owners) -} - -export const addSafe = createAction(ADD_SAFE, (safe: SafeRecordProps, loadedFromStorage = false) => ({ - safe, - loadedFromStorage, -})) - -const saveSafe = (safe: SafeRecordProps) => (dispatch: Dispatch, getState: () => AppReduxState): void => { - const state = getState() - const safeList = safesListSelector(state) - - dispatch(addSafe(safe, true)) - - if (safeList.size === 0) { - dispatch(setDefaultSafe(safe.address)) - } -} - -export default saveSafe diff --git a/src/logic/safe/store/actions/allTransactions/loadAllTransactions.ts b/src/logic/safe/store/actions/allTransactions/loadAllTransactions.ts index b054b123..b06c9ba1 100644 --- a/src/logic/safe/store/actions/allTransactions/loadAllTransactions.ts +++ b/src/logic/safe/store/actions/allTransactions/loadAllTransactions.ts @@ -1,6 +1,6 @@ import axios, { AxiosResponse } from 'axios' -import { getAllTransactionsUriFrom, getTxServiceHost } from 'src/config' +import { getAllTransactionsUriFrom, getTxServiceUrl } from 'src/config' import { checksumAddress } from 'src/utils/checksumAddress' import { Transaction } from '../../models/types/transactions.d' @@ -21,11 +21,11 @@ type TransactionDTO = { } const getAllTransactionsUri = (safeAddress: string): string => { - const host = getTxServiceHost() + const host = getTxServiceUrl() const address = checksumAddress(safeAddress) const base = getAllTransactionsUriFrom(address) - return `${host}${base}` + return `${host}/${base}` } const fetchAllTransactions = async ( diff --git a/src/logic/safe/store/actions/fetchSafe.ts b/src/logic/safe/store/actions/fetchSafe.ts index a398ffb7..6813ea5f 100644 --- a/src/logic/safe/store/actions/fetchSafe.ts +++ b/src/logic/safe/store/actions/fetchSafe.ts @@ -1,5 +1,7 @@ import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json' import { List, Set, Map } from 'immutable' +import { Action, Dispatch } from 'redux' +import { AbiItem } from 'web3-utils' import generateBatchRequests from 'src/logic/contracts/generateBatchRequests' import { getLocalSafe, getSafeName } from 'src/logic/safe/utils' @@ -10,10 +12,8 @@ import addSafeOwner from 'src/logic/safe/store/actions/addSafeOwner' import removeSafeOwner from 'src/logic/safe/store/actions/removeSafeOwner' import updateSafe from 'src/logic/safe/store/actions/updateSafe' import { makeOwner } from 'src/logic/safe/store/models/owner' - import { checksumAddress } from 'src/utils/checksumAddress' import { ModulePair, SafeOwner, SafeRecordProps } from 'src/logic/safe/store/models/safe' -import { Action, Dispatch } from 'redux' import { SENTINEL_ADDRESS } from 'src/logic/contracts/safeContracts' import { AppReduxState } from 'src/store' import { latestMasterContractVersionSelector } from '../selectors' @@ -40,8 +40,8 @@ const buildOwnersFrom = (safeOwners: string[], localSafe?: SafeRecordProps): Lis return List(ownersList) } -const buildModulesLinkedList = (modules: string[] | undefined, nextModule: string): Array | null => { - if (modules?.length) { +const buildModulesLinkedList = (modules?: string[], nextModule?: string): Array | null => { + if (modules?.length && nextModule) { return modules.map((moduleAddress, index, modules) => { const prevModule = modules[index + 1] return [moduleAddress, prevModule !== undefined ? prevModule : nextModule] @@ -58,9 +58,9 @@ export const buildSafe = async ( const safeAddress = checksumAddress(safeAdd) const safeParams = ['getThreshold', 'nonce', 'VERSION', 'getOwners'] - const [[thresholdStr, nonceStr, currentVersion, remoteOwners], localSafe, ethBalance] = await Promise.all([ - generateBatchRequests({ - abi: GnosisSafeSol.abi, + const [[, thresholdStr, nonceStr, currentVersion, remoteOwners = []], localSafe, ethBalance] = await Promise.all([ + generateBatchRequests<[undefined, string | undefined, string | undefined, string | undefined, string[]]>({ + abi: GnosisSafeSol.abi as AbiItem[], address: safeAddress, methods: safeParams, }), @@ -81,7 +81,7 @@ export const buildSafe = async ( owners, ethBalance, nonce, - currentVersion, + currentVersion: currentVersion ?? '', needsUpdate, featuresEnabled, balances: Map(), @@ -104,9 +104,23 @@ export const checkAndUpdateSafe = (safeAdd: string) => async (dispatch: Dispatch // TODO: 100 is an arbitrary large number, to avoid the need for pagination. But pagination must be properly handled { method: 'getModulesPaginated', args: [SENTINEL_ADDRESS, 100] }, ] - const [[remoteThreshold, remoteNonce, remoteOwners, modules], localSafe] = await Promise.all([ - generateBatchRequests({ - abi: GnosisSafeSol.abi, + const [[, remoteThreshold, remoteNonce, remoteOwners, modules], localSafe] = await Promise.all([ + generateBatchRequests< + [ + undefined, + string | undefined, + string | undefined, + string[], + ( + | { + array: string[] + next: string + } + | undefined + ), + ] + >({ + abi: GnosisSafeSol.abi as AbiItem[], address: safeAddress, methods: safeParams, }), @@ -123,6 +137,9 @@ export const checkAndUpdateSafe = (safeAdd: string) => async (dispatch: Dispatch modules: buildModulesLinkedList(modules?.array, modules?.next), nonce: Number(remoteNonce), threshold: Number(remoteThreshold), + featuresEnabled: localSafe?.currentVersion + ? enabledFeatures(localSafe?.currentVersion) + : localSafe?.featuresEnabled, }), ) diff --git a/src/logic/safe/store/actions/fetchSafeCreationTx.ts b/src/logic/safe/store/actions/fetchSafeCreationTx.ts index d0e79de4..6504d226 100644 --- a/src/logic/safe/store/actions/fetchSafeCreationTx.ts +++ b/src/logic/safe/store/actions/fetchSafeCreationTx.ts @@ -1,7 +1,7 @@ import axios from 'axios' import { List } from 'immutable' -import { buildSafeCreationTxUrl } from 'src/config' +import { buildSafeCreationTxUrl } from 'src/logic/safe/utils/buildSafeCreationTxUrl' import { addOrUpdateTransactions } from './transactions/addOrUpdateTransactions' import { makeTransaction } from 'src/logic/safe/store/models/transaction' import { TransactionTypes, TransactionStatus } from 'src/logic/safe/store/models/types/transaction' diff --git a/src/logic/safe/store/actions/loadSafesFromStorage.ts b/src/logic/safe/store/actions/loadSafesFromStorage.ts index c6009857..00e3d99f 100644 --- a/src/logic/safe/store/actions/loadSafesFromStorage.ts +++ b/src/logic/safe/store/actions/loadSafesFromStorage.ts @@ -5,7 +5,7 @@ import { SafeRecordProps } from 'src/logic/safe/store/models/safe' import { buildSafe } from 'src/logic/safe/store/reducer/safe' import { loadFromStorage } from 'src/utils/storage' -import { addSafe } from './addSafe' +import { addOrUpdateSafe } from './addOrUpdateSafe' const loadSafesFromStorage = () => async (dispatch: Dispatch): Promise => { try { @@ -13,7 +13,7 @@ const loadSafesFromStorage = () => async (dispatch: Dispatch): Promise => if (safes) { Object.values(safes).forEach((safeProps) => { - dispatch(addSafe(buildSafe(safeProps), true)) + dispatch(addOrUpdateSafe(buildSafe(safeProps), true)) }) } } catch (err) { diff --git a/src/logic/safe/store/actions/transactions/fetchTransactions/loadIncomingTransactions.ts b/src/logic/safe/store/actions/transactions/fetchTransactions/loadIncomingTransactions.ts index ec79aa23..d52130a9 100644 --- a/src/logic/safe/store/actions/transactions/fetchTransactions/loadIncomingTransactions.ts +++ b/src/logic/safe/store/actions/transactions/fetchTransactions/loadIncomingTransactions.ts @@ -1,6 +1,9 @@ import bn from 'bignumber.js' import { List, Map } from 'immutable' +import { Transaction, TransactionReceipt } from 'web3-core' +import { AbiItem } from 'web3-utils' +import { getNetworkInfo } from 'src/config' import generateBatchRequests from 'src/logic/contracts/generateBatchRequests' import { ALTERNATIVE_TOKEN_ABI } from 'src/logic/tokens/utils/alternativeAbi' import { web3ReadOnly } from 'src/logic/wallets/getWeb3' @@ -43,6 +46,7 @@ const buildIncomingTransactionFrom = ([tx, symbol, decimals, fee]: [ const batchIncomingTxsTokenDataRequest = (txs: IncomingTxServiceModel[]) => { const batch = new web3ReadOnly.BatchRequest() + const { nativeCoin } = getNetworkInfo() const whenTxsValues = txs.map((tx) => { const methods = [ @@ -52,8 +56,16 @@ const batchIncomingTxsTokenDataRequest = (txs: IncomingTxServiceModel[]) => { { method: 'getTransactionReceipt', args: [tx.transactionHash], type: 'eth' }, ] - return generateBatchRequests({ - abi: ALTERNATIVE_TOKEN_ABI, + return generateBatchRequests< + [ + IncomingTxServiceModel, + string | undefined, + string | undefined, + Transaction | undefined, + TransactionReceipt | undefined, + ] + >({ + abi: ALTERNATIVE_TOKEN_ABI as AbiItem[], address: tx.tokenAddress, batch, context: tx, @@ -64,11 +76,11 @@ const batchIncomingTxsTokenDataRequest = (txs: IncomingTxServiceModel[]) => { batch.execute() return Promise.all(whenTxsValues).then((txsValues) => - txsValues.map(([tx, symbol, decimals, { gasPrice }, { gasUsed }]) => [ + txsValues.map(([tx, symbol, decimals, ethTx, ethTxReceipt]) => [ tx, - symbol === null ? 'ETH' : symbol, - decimals === null ? '18' : decimals, - new bn(gasPrice).times(gasUsed), + symbol ? symbol : nativeCoin.symbol, + decimals ? decimals : nativeCoin.decimals, + new bn(ethTx?.gasPrice ?? 0).times(ethTxReceipt?.gasUsed ?? 0), ]), ) } diff --git a/src/logic/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.ts b/src/logic/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.ts index 89d53d95..4dca08fb 100644 --- a/src/logic/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.ts +++ b/src/logic/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.ts @@ -1,7 +1,7 @@ import { fromJS, List, Map } from 'immutable' import generateBatchRequests from 'src/logic/contracts/generateBatchRequests' -import { TOKEN_REDUCER_ID } from 'src/logic/tokens/store/reducer/tokens' +import { TOKEN_REDUCER_ID, TokenState } from 'src/logic/tokens/store/reducer/tokens' import { web3ReadOnly } from 'src/logic/wallets/getWeb3' import { PROVIDER_REDUCER_ID } from 'src/logic/wallets/store/reducer/provider' import { buildTx, isCancelTransaction } from 'src/logic/safe/store/actions/transactions/utils/transactionHelpers' @@ -59,8 +59,8 @@ export type SafeTransactionsType = { } export type OutgoingTxs = { - cancellationTxs: any - outgoingTxs: any + cancellationTxs: Record + outgoingTxs: TxServiceModel[] } export type BatchProcessTxsProps = OutgoingTxs & { @@ -97,12 +97,14 @@ const extractCancelAndOutgoingTxs = (safeAddress: string, outgoingTxs: TxService ) } +type BatchRequestReturnValues = [TxServiceModel, string | undefined] + /** * Requests Contract's code for all the Contracts the Safe has interacted with * @param transactions * @returns {Promise<[Promise<*[]>, Promise<*[]>, Promise<*[]>, Promise<*[]>, Promise<*[]>, Promise<*[]>, Promise<*[]>, Promise<*[]>, Promise<*[]>, Promise<*[]>]>} */ -const batchRequestContractCode = (transactions: any[]): Promise => { +const batchRequestContractCode = (transactions: TxServiceModel[]): Promise => { if (!transactions || !Array.isArray(transactions)) { throw new Error('`transactions` must be provided in order to lookup information') } @@ -110,7 +112,7 @@ const batchRequestContractCode = (transactions: any[]): Promise => { const batch = new web3ReadOnly.BatchRequest() const whenTxsValues = transactions.map((tx) => { - return generateBatchRequests({ + return generateBatchRequests({ abi: [], address: tx.to, batch, @@ -141,7 +143,7 @@ const batchProcessOutgoingTransactions = async ({ safe, }: BatchProcessTxsProps): Promise<{ cancel: Record - outgoing: Array + outgoing: Transaction[] }> => { // cancellation transactions const cancelTxsValues = Object.values(cancellationTxs) @@ -193,9 +195,9 @@ export const loadOutgoingTransactions = async (safeAddress: string): Promise { ) } -export const isOutgoingTransaction = (tx: TxServiceModel, safeAddress: string): boolean => { +export const isOutgoingTransaction = (tx: TxServiceModel, safeAddress?: string): boolean => { return !sameAddress(tx.to, safeAddress) && !isEmptyData(tx.data) } export const isCustomTransaction = async ( tx: TxServiceModel, - txCode: string | null, - safeAddress: string, - knownTokens: Map, + txCode?: string, + safeAddress?: string, + knownTokens?: TokenState, ): Promise => { const isOutgoing = isOutgoingTransaction(tx, safeAddress) const isErc20 = await isSendERC20Transaction(tx, txCode, knownTokens) @@ -100,12 +100,13 @@ export const getRefundParams = async ( tx: TxServiceModel, tokenInfo: (string) => Promise<{ decimals: number; symbol: string } | null>, ): Promise => { + const { nativeCoin } = getNetworkInfo() const txGasPrice = Number(tx.gasPrice) let refundParams: RefundParams | null = null if (txGasPrice > 0) { - let refundSymbol = 'ETH' - let refundDecimals = 18 + let refundSymbol = nativeCoin.symbol + let refundDecimals = nativeCoin.decimals if (tx.gasToken !== ZERO_ADDRESS) { const gasToken = await tokenInfo(tx.gasToken) @@ -161,7 +162,7 @@ export const getConfirmations = (tx: TxServiceModel): List => { export const isTransactionCancelled = ( tx: TxServiceModel, outgoingTxs: Array, - cancellationTxs: { number: TxServiceModel }, + cancellationTxs: Record, ): boolean => { return ( // not executed @@ -175,20 +176,20 @@ export const isTransactionCancelled = ( export const calculateTransactionStatus = ( tx: Transaction, - { owners, threshold }: SafeRecord, + { owners, threshold, nonce }: SafeRecord, currentUser?: string | null, ): TransactionStatusValues => { let txStatus if (tx.isExecuted && tx.isSuccessful) { txStatus = TransactionStatus.SUCCESS - } else if (tx.cancelled) { + } else if (tx.cancelled || nonce > tx.nonce) { txStatus = TransactionStatus.CANCELLED } else if (tx.confirmations.size === threshold) { txStatus = TransactionStatus.AWAITING_EXECUTION } else if (tx.creationTx) { txStatus = TransactionStatus.SUCCESS - } else if (!tx.confirmations.size || !!tx.isPending) { + } else if (!!tx.isPending) { txStatus = TransactionStatus.PENDING } else { const userConfirmed = tx.confirmations.filter((conf) => conf.owner === currentUser).size === 1 @@ -230,7 +231,7 @@ export const calculateTransactionType = (tx: Transaction): TransactionTypeValues export type BuildTx = BatchProcessTxsProps & { tx: TxServiceModel - txCode: string | null + txCode?: string } export const buildTx = async ({ @@ -243,6 +244,7 @@ export const buildTx = async ({ txCode, }: BuildTx): Promise => { const safeAddress = safe.address + const { nativeCoin } = getNetworkInfo() const isModifySettingsTx = isModifySettingsTransaction(tx, safeAddress) const isTxCancelled = isTransactionCancelled(tx, outgoingTxs, cancellationTxs) const isSendERC721Tx = isSendERC721Transaction(tx, txCode, knownTokens) @@ -255,8 +257,8 @@ export const buildTx = async ({ const decodedParams = getDecodedParams(tx) const confirmations = getConfirmations(tx) - let tokenDecimals = 18 - let tokenSymbol = 'ETH' + let tokenDecimals = nativeCoin.decimals + let tokenSymbol = nativeCoin.symbol try { if (isSendERC20Tx) { const { decimals, symbol } = await getERC20DecimalsAndSymbol(tx.to) diff --git a/src/logic/safe/store/middleware/notificationsMiddleware.ts b/src/logic/safe/store/middleware/notificationsMiddleware.ts index 617bdd3d..c07ea64e 100644 --- a/src/logic/safe/store/middleware/notificationsMiddleware.ts +++ b/src/logic/safe/store/middleware/notificationsMiddleware.ts @@ -10,7 +10,6 @@ import { userAccountSelector } from 'src/logic/wallets/store/selectors' import { getIncomingTxAmount } from 'src/routes/safe/components/Transactions/TxsTable/columns' import { grantedSelector } from 'src/routes/safe/container/selector' import { ADD_INCOMING_TRANSACTIONS } from 'src/logic/safe/store/actions/addIncomingTransactions' -import { ADD_SAFE } from 'src/logic/safe/store/actions/addSafe' import { ADD_OR_UPDATE_TRANSACTIONS } from 'src/logic/safe/store/actions/transactions/addOrUpdateTransactions' import updateSafe from 'src/logic/safe/store/actions/updateSafe' import { @@ -20,8 +19,9 @@ import { } from 'src/logic/safe/store/selectors' import { loadFromStorage, saveToStorage } from 'src/utils/storage' +import { ADD_OR_UPDATE_SAFE } from '../actions/addOrUpdateSafe' -const watchedActions = [ADD_OR_UPDATE_TRANSACTIONS, ADD_INCOMING_TRANSACTIONS, ADD_SAFE] +const watchedActions = [ADD_OR_UPDATE_TRANSACTIONS, ADD_INCOMING_TRANSACTIONS, ADD_OR_UPDATE_SAFE] const sendAwaitingTransactionNotification = async ( dispatch, @@ -146,7 +146,7 @@ const notificationsMiddleware = (store) => (next) => async (action) => { }) break } - case ADD_SAFE: { + case ADD_OR_UPDATE_SAFE: { const state = store.getState() const { safe } = action.payload const currentSafeAddress = safeParamAddressFromStateSelector(state) || safe.address diff --git a/src/logic/safe/store/middleware/safeStorage.ts b/src/logic/safe/store/middleware/safeStorage.ts index ee9152c2..90b26346 100644 --- a/src/logic/safe/store/middleware/safeStorage.ts +++ b/src/logic/safe/store/middleware/safeStorage.ts @@ -1,9 +1,7 @@ -import { addAddressBookEntry } from 'src/logic/addressBook/store/actions/addAddressBookEntry' import { saveDefaultSafe, saveSafes } from 'src/logic/safe/utils' import { tokensSelector } from 'src/logic/tokens/store/selectors' import { saveActiveTokens } from 'src/logic/tokens/utils/tokensStorage' import { ACTIVATE_TOKEN_FOR_ALL_SAFES } from 'src/logic/safe/store/actions/activateTokenForAllSafes' -import { ADD_SAFE } from 'src/logic/safe/store/actions/addSafe' import { ADD_SAFE_OWNER } from 'src/logic/safe/store/actions/addSafeOwner' import { EDIT_SAFE_OWNER } from 'src/logic/safe/store/actions/editSafeOwner' import { REMOVE_SAFE } from 'src/logic/safe/store/actions/removeSafe' @@ -14,17 +12,13 @@ import { UPDATE_SAFE } from 'src/logic/safe/store/actions/updateSafe' import { UPDATE_TOKENS_LIST } from 'src/logic/safe/store/actions/updateTokensList' import { UPDATE_ASSETS_LIST } from 'src/logic/safe/store/actions/updateAssetsList' import { getActiveTokensAddressesForAllSafes, safesMapSelector } from 'src/logic/safe/store/selectors' -import { checksumAddress } from 'src/utils/checksumAddress' -import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook' -import { addOrUpdateAddressBookEntry } from 'src/logic/addressBook/store/actions/addOrUpdateAddressBookEntry' -import { checkIfEntryWasDeletedFromAddressBook, isValidAddressBookName } from 'src/logic/addressBook/utils' -import { addressBookSelector } from 'src/logic/addressBook/store/selectors' -import { sameAddress } from 'src/logic/wallets/ethAddresses' -import { updateAddressBookEntry } from 'src/logic/addressBook/store/actions/updateAddressBookEntry' import { ADD_OR_UPDATE_SAFE } from 'src/logic/safe/store/actions/addOrUpdateSafe' +import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook' +import { checksumAddress } from 'src/utils/checksumAddress' +import { isValidAddressBookName } from 'src/logic/addressBook/utils' +import { addOrUpdateAddressBookEntry } from 'src/logic/addressBook/store/actions/addOrUpdateAddressBookEntry' const watchedActions = [ - ADD_SAFE, UPDATE_SAFE, REMOVE_SAFE, ADD_OR_UPDATE_SAFE, @@ -60,7 +54,6 @@ const safeStorageMware = (store) => (next) => async (action) => { const state = store.getState() const { dispatch } = store const safes = safesMapSelector(state) - const addressBook = addressBookSelector(state) await saveSafes(safes.toJSON()) switch (action.type) { @@ -68,43 +61,6 @@ const safeStorageMware = (store) => (next) => async (action) => { recalculateActiveTokens(state) break } - case ADD_SAFE: { - const { safe, loadedFromStorage } = action.payload - const safeAlreadyLoaded = - loadedFromStorage || safes.find((safeIterator) => sameAddress(safeIterator.address, safe.address)) - - safe.owners.forEach((owner) => { - const checksumEntry = makeAddressBookEntry({ address: checksumAddress(owner.address), name: owner.name }) - - const ownerWasAlreadyInAddressBook = checkIfEntryWasDeletedFromAddressBook( - checksumEntry, - addressBook, - safeAlreadyLoaded, - ) - - if (!ownerWasAlreadyInAddressBook) { - dispatch(addAddressBookEntry(checksumEntry, { notifyEntryUpdate: false })) - } - const addressAlreadyExists = addressBook.find((entry) => sameAddress(entry.address, checksumEntry.address)) - if (isValidAddressBookName(checksumEntry.name) && addressAlreadyExists) { - dispatch(updateAddressBookEntry(checksumEntry)) - } - }) - const safeWasAlreadyInAddressBook = checkIfEntryWasDeletedFromAddressBook( - { address: safe.address, name: safe.name }, - addressBook, - safeAlreadyLoaded, - ) - - if (!safeWasAlreadyInAddressBook) { - dispatch( - addAddressBookEntry(makeAddressBookEntry({ address: safe.address, name: safe.name }), { - notifyEntryUpdate: true, - }), - ) - } - break - } case ADD_OR_UPDATE_SAFE: { const { safe } = action.payload safe.owners.forEach((owner) => { diff --git a/src/logic/safe/store/models/safe.ts b/src/logic/safe/store/models/safe.ts index 0311d3be..bd38c747 100644 --- a/src/logic/safe/store/models/safe.ts +++ b/src/logic/safe/store/models/safe.ts @@ -1,4 +1,5 @@ import { List, Map, Record, RecordOf, Set } from 'immutable' +import { FEATURES } from 'src/config/networks/network.d' export type SafeOwner = { name: string @@ -24,7 +25,7 @@ export type SafeRecordProps = { recurringUser?: boolean currentVersion: string needsUpdate: boolean - featuresEnabled: Array + featuresEnabled: Array } const makeSafe = Record({ diff --git a/src/logic/safe/store/models/transaction.ts b/src/logic/safe/store/models/transaction.ts index 3beaf658..a0d3e21c 100644 --- a/src/logic/safe/store/models/transaction.ts +++ b/src/logic/safe/store/models/transaction.ts @@ -31,6 +31,7 @@ export const makeTransaction = Record({ isCancellationTx: false, isCollectibleTransfer: false, isExecuted: false, + isPending: false, isSuccessful: true, isTokenTransfer: false, masterCopy: '', diff --git a/src/logic/safe/store/reducer/safe.ts b/src/logic/safe/store/reducer/safe.ts index 3d170f8e..d5abf59c 100644 --- a/src/logic/safe/store/reducer/safe.ts +++ b/src/logic/safe/store/reducer/safe.ts @@ -2,7 +2,6 @@ import { Map, Set, List } from 'immutable' import { handleActions } from 'redux-actions' import { ACTIVATE_TOKEN_FOR_ALL_SAFES } from 'src/logic/safe/store/actions/activateTokenForAllSafes' -import { ADD_SAFE, buildOwnersFrom } from 'src/logic/safe/store/actions/addSafe' import { ADD_SAFE_OWNER } from 'src/logic/safe/store/actions/addSafeOwner' import { EDIT_SAFE_OWNER } from 'src/logic/safe/store/actions/editSafeOwner' import { REMOVE_SAFE } from 'src/logic/safe/store/actions/removeSafe' @@ -17,7 +16,7 @@ import { makeOwner } from 'src/logic/safe/store/models/owner' import makeSafe, { SafeRecordProps } from 'src/logic/safe/store/models/safe' import { checksumAddress } from 'src/utils/checksumAddress' import { SafeReducerMap } from 'src/routes/safe/store/reducer/types/safe' -import { ADD_OR_UPDATE_SAFE } from 'src/logic/safe/store/actions/addOrUpdateSafe' +import { ADD_OR_UPDATE_SAFE, buildOwnersFrom } from 'src/logic/safe/store/actions/addOrUpdateSafe' import { sameAddress } from 'src/logic/wallets/ethAddresses' export const SAFE_REDUCER_ID = 'safes' @@ -99,19 +98,7 @@ export default handleActions( }) }) }, - [ADD_SAFE]: (state: SafeReducerMap, action) => { - const { safe } = action.payload - // if you add a new Safe it needs to be set as a record - // in case of update it shouldn't, because a record would be initialized - // with initial props and it would overwrite existing ones - - if (state.hasIn(['safes', safe.address])) { - return state - } - - return state.setIn(['safes', safe.address], makeSafe(safe)) - }, [ADD_OR_UPDATE_SAFE]: (state: SafeReducerMap, action) => { const { safe } = action.payload diff --git a/src/logic/safe/store/reducer/transactions.ts b/src/logic/safe/store/reducer/transactions.ts index fc42a6b6..3ce4df74 100644 --- a/src/logic/safe/store/reducer/transactions.ts +++ b/src/logic/safe/store/reducer/transactions.ts @@ -21,7 +21,7 @@ export default handleActions( if (stateTransactionsList) { const txsToStore = stateTransactionsList.withMutations((txsList) => { transactions.forEach((updateTx) => { - const storedTxIndex = txsList.findIndex((txIterator) => txIterator.nonce === updateTx.nonce) + const storedTxIndex = txsList.findIndex((txIterator) => txIterator.safeTxHash === updateTx.safeTxHash) if (storedTxIndex !== -1) { // Update diff --git a/src/logic/safe/transactions/incomingTxHistory.ts b/src/logic/safe/transactions/incomingTxHistory.ts index c7d78e54..8eec0f6f 100644 --- a/src/logic/safe/transactions/incomingTxHistory.ts +++ b/src/logic/safe/transactions/incomingTxHistory.ts @@ -1,10 +1,10 @@ -import { getIncomingTxServiceUriTo, getTxServiceHost } from 'src/config' +import { getIncomingTxServiceUriTo, getTxServiceUrl } from 'src/config' import { checksumAddress } from 'src/utils/checksumAddress' export const buildIncomingTxServiceUrl = (safeAddress: string): string => { - const host = getTxServiceHost() + const host = getTxServiceUrl() const address = checksumAddress(safeAddress) const base = getIncomingTxServiceUriTo(address) - return `${host}${base}` + return `${host}/${base}` } diff --git a/src/logic/safe/transactions/txHistory.ts b/src/logic/safe/transactions/txHistory.ts index eeb14436..ea787b17 100644 --- a/src/logic/safe/transactions/txHistory.ts +++ b/src/logic/safe/transactions/txHistory.ts @@ -1,7 +1,7 @@ import axios from 'axios' import { GnosisSafe } from 'src/types/contracts/GnosisSafe.d' -import { getTxServiceHost, getTxServiceUriFrom } from 'src/config' +import { getTxServiceUrl, getTxServiceUriFrom } from 'src/config' import { checksumAddress } from 'src/utils/checksumAddress' const calculateBodyFrom = async ( @@ -45,10 +45,10 @@ const calculateBodyFrom = async ( } export const buildTxServiceUrl = (safeAddress: string): string => { - const host = getTxServiceHost() + const host = getTxServiceUrl() const address = checksumAddress(safeAddress) const base = getTxServiceUriFrom(address) - return `${host}${base}?has_confirmations=True` + return `${host}/${base}?has_confirmations=True` } const SUCCESS_STATUS = 201 // CREATED status diff --git a/src/logic/safe/utils/buildSafeCreationTxUrl.ts b/src/logic/safe/utils/buildSafeCreationTxUrl.ts new file mode 100644 index 00000000..23284d58 --- /dev/null +++ b/src/logic/safe/utils/buildSafeCreationTxUrl.ts @@ -0,0 +1,10 @@ +import { getTxServiceUrl, getSafeCreationTxUri } from 'src/config' +import { checksumAddress } from 'src/utils/checksumAddress' + +export const buildSafeCreationTxUrl = (safeAddress: string): string => { + const host = getTxServiceUrl() + const address = checksumAddress(safeAddress) + const base = getSafeCreationTxUri(address) + + return `${host}/${base}` +} diff --git a/src/logic/safe/utils/safeVersion.ts b/src/logic/safe/utils/safeVersion.ts index 5fe5ee34..bdfa6080 100644 --- a/src/logic/safe/utils/safeVersion.ts +++ b/src/logic/safe/utils/safeVersion.ts @@ -3,15 +3,24 @@ import semverSatisfies from 'semver/functions/satisfies' import semverValid from 'semver/functions/valid' import { GnosisSafe } from 'src/types/contracts/GnosisSafe.d' -import { getSafeLastVersion } from 'src/config' import { getGnosisSafeInstanceAt, getSafeMasterContract } from 'src/logic/contracts/safeContracts' +import { LATEST_SAFE_VERSION } from 'src/utils/constants' +import { getNetworkConfigDisabledFeatures } from 'src/config' +import { FEATURES } from 'src/config/networks/network.d' -export const FEATURES = [ - { name: 'ERC721', validVersion: '>=1.1.1' }, - { name: 'ERC1155', validVersion: '>=1.1.1' }, +type FeatureConfigByVersion = { + name: FEATURES + validVersion?: string +} + +const FEATURES_BY_VERSION: FeatureConfigByVersion[] = [ + { name: FEATURES.ERC721, validVersion: '>=1.1.1' }, + { name: FEATURES.ERC1155, validVersion: '>=1.1.1' }, + { name: FEATURES.SAFE_APPS }, + { name: FEATURES.CONTRACT_INTERACTION }, ] -type Feature = typeof FEATURES[number] +type Feature = typeof FEATURES_BY_VERSION[number] export const safeNeedsUpdate = (currentVersion?: string, latestVersion?: string): boolean => { if (!currentVersion || !latestVersion) { @@ -27,13 +36,19 @@ export const safeNeedsUpdate = (currentVersion?: string, latestVersion?: string) export const getCurrentSafeVersion = (gnosisSafeInstance: GnosisSafe): Promise => gnosisSafeInstance.methods.VERSION().call() -export const enabledFeatures = (version: string): string[] => - FEATURES.reduce((acc: string[], feature: Feature) => { - if (semverSatisfies(version, feature.validVersion)) { +const checkFeatureEnabledByVersion = (featureConfig: FeatureConfigByVersion, version: string) => { + return featureConfig.validVersion ? semverSatisfies(version, featureConfig.validVersion) : true +} + +export const enabledFeatures = (version?: string): FEATURES[] => { + const disabledFeatures = getNetworkConfigDisabledFeatures() + return FEATURES_BY_VERSION.reduce((acc: FEATURES[], feature: Feature) => { + if (!disabledFeatures.includes(feature.name) && version && checkFeatureEnabledByVersion(feature, version)) { acc.push(feature.name) } return acc }, []) +} interface SafeVersionInfo { current: string @@ -60,11 +75,11 @@ export const getCurrentMasterContractLastVersion = async (): Promise => const safeMaster = await getSafeMasterContract() let safeMasterVersion try { - safeMasterVersion = await safeMaster.VERSION() + safeMasterVersion = await safeMaster.methods.VERSION().call() } catch (err) { // Default in case that it's not possible to obtain the version from the contract, returns a hardcoded value or an // env variable - safeMasterVersion = getSafeLastVersion() + safeMasterVersion = LATEST_SAFE_VERSION } return safeMasterVersion } diff --git a/src/logic/tokens/api/fetchErc20AndErc721AssetsList.ts b/src/logic/tokens/api/fetchErc20AndErc721AssetsList.ts new file mode 100644 index 00000000..c0e9a64c --- /dev/null +++ b/src/logic/tokens/api/fetchErc20AndErc721AssetsList.ts @@ -0,0 +1,24 @@ +import axios, { AxiosResponse } from 'axios' + +import { getTxServiceUrl } from 'src/config' + +export type TokenResult = { + address: string + decimals?: number + logoUri: string + name: string + symbol: string + type: string +} + +export const fetchErc20AndErc721AssetsList = async (): Promise> => { + const apiUrl = getTxServiceUrl() + + const url = `${apiUrl}/tokens/` + + return axios.get<{ results: TokenResult[] }>(url, { + params: { + limit: 3000, + }, + }) +} diff --git a/src/logic/tokens/api/fetchSafeCollectibles.ts b/src/logic/tokens/api/fetchSafeCollectibles.ts new file mode 100644 index 00000000..d1a66835 --- /dev/null +++ b/src/logic/tokens/api/fetchSafeCollectibles.ts @@ -0,0 +1,24 @@ +import axios, { AxiosResponse } from 'axios' + +import { getTxServiceUrl } from 'src/config' + +export type CollectibleResult = { + address: string + description: string | null + id: string + imageUri: string | null + logoUri: string + metadata: Record + name: string | null + tokenName: string + tokenSymbol: string + uri: string | null +} + +export const fetchSafeCollectibles = async (safeAddress: string): Promise> => { + const apiUrl = getTxServiceUrl() + + const url = `${apiUrl}/safes/${safeAddress}/collectibles/` + + return axios.get(url) +} diff --git a/src/logic/tokens/api/fetchToken.ts b/src/logic/tokens/api/fetchToken.ts deleted file mode 100644 index 7584521e..00000000 --- a/src/logic/tokens/api/fetchToken.ts +++ /dev/null @@ -1,16 +0,0 @@ -import axios from 'axios' - -import { getRelayUrl } from 'src/config/index' - -const fetchToken = (tokenAddress) => { - const apiUrl = getRelayUrl() - const url = `${apiUrl}/tokens/` - - return axios.get(url, { - params: { - address: tokenAddress, - }, - }) -} - -export default fetchToken diff --git a/src/logic/tokens/api/fetchTokenBalanceList.ts b/src/logic/tokens/api/fetchTokenBalanceList.ts index c446858c..9ee1d4f5 100644 --- a/src/logic/tokens/api/fetchTokenBalanceList.ts +++ b/src/logic/tokens/api/fetchTokenBalanceList.ts @@ -1,16 +1,17 @@ -import axios from 'axios' +import axios, { AxiosResponse } from 'axios' -import { getTxServiceHost } from 'src/config/index' +import { getTxServiceUrl } from 'src/config' +import { TokenProps } from 'src/logic/tokens/store/model/token' -const fetchTokenBalanceList = (safeAddress) => { - const apiUrl = getTxServiceHost() - const url = `${apiUrl}safes/${safeAddress}/balances/` - - return axios.get(url, { - params: { - limit: 3000, - }, - }) +type BalanceResult = { + tokenAddress: string + token: TokenProps + balance: string } -export default fetchTokenBalanceList +export const fetchTokenBalanceList = (safeAddress: string): Promise> => { + const apiUrl = getTxServiceUrl() + const url = `${apiUrl}/safes/${safeAddress}/balances/` + + return axios.get(url) +} diff --git a/src/logic/tokens/api/fetchTokenList.ts b/src/logic/tokens/api/fetchTokenList.ts deleted file mode 100644 index dd9054b0..00000000 --- a/src/logic/tokens/api/fetchTokenList.ts +++ /dev/null @@ -1,16 +0,0 @@ -import axios from 'axios' - -import { getRelayUrl } from 'src/config/index' - -const fetchTokenList = () => { - const apiUrl = getRelayUrl() - const url = `${apiUrl}tokens/` - - return axios.get(url, { - params: { - limit: 3000, - }, - }) -} - -export default fetchTokenList diff --git a/src/logic/tokens/api/index.ts b/src/logic/tokens/api/index.ts index 7ff3aa7d..9210ea14 100644 --- a/src/logic/tokens/api/index.ts +++ b/src/logic/tokens/api/index.ts @@ -1,2 +1,3 @@ -export { default as fetchTokenList } from './fetchTokenList' -export { default as fetchToken } from './fetchToken' +export { fetchErc20AndErc721AssetsList } from './fetchErc20AndErc721AssetsList' +export { fetchSafeCollectibles } from './fetchSafeCollectibles' +export { fetchTokenBalanceList } from './fetchTokenBalanceList' diff --git a/src/logic/tokens/store/actions/fetchSafeTokens.ts b/src/logic/tokens/store/actions/fetchSafeTokens.ts index 608256c6..55eca9e5 100644 --- a/src/logic/tokens/store/actions/fetchSafeTokens.ts +++ b/src/logic/tokens/store/actions/fetchSafeTokens.ts @@ -3,15 +3,12 @@ import { List, Map } from 'immutable' import { batch } from 'react-redux' import { Dispatch } from 'redux' -import fetchTokenCurrenciesBalances, { +import { + fetchTokenCurrenciesBalances, BalanceEndpoint, } from 'src/logic/currencyValues/api/fetchTokenCurrenciesBalances' import { setCurrencyBalances } from 'src/logic/currencyValues/store/actions/setCurrencyBalances' -import { - AVAILABLE_CURRENCIES, - CurrencyRateValueRecord, - makeBalanceCurrency, -} from 'src/logic/currencyValues/store/model/currencyValues' +import { CurrencyRateValueRecord, makeBalanceCurrency } from 'src/logic/currencyValues/store/model/currencyValues' import addTokens from 'src/logic/tokens/store/actions/saveTokens' import { makeToken, Token } from 'src/logic/tokens/store/model/token' import { TokenState } from 'src/logic/tokens/store/reducer/tokens' @@ -43,7 +40,7 @@ interface ExtractedData { const extractDataFromResult = (currentTokens: TokenState) => ( acc: ExtractedData, - { balance, balanceUsd, token, tokenAddress }: BalanceEndpoint, + { balance, fiatBalance, fiatCode, token, tokenAddress }: BalanceEndpoint, ): ExtractedData => { if (tokenAddress === null) { acc.ethBalance = humanReadableValue(balance, 18) @@ -57,10 +54,10 @@ const extractDataFromResult = (currentTokens: TokenState) => ( acc.currencyList = acc.currencyList.push( makeBalanceCurrency({ - currencyName: balanceUsd ? AVAILABLE_CURRENCIES.USD : undefined, + currencyName: fiatCode, tokenAddress, - balanceInBaseCurrency: balanceUsd, - balanceInSelectedCurrency: balanceUsd, + balanceInBaseCurrency: fiatBalance, + balanceInSelectedCurrency: fiatBalance, }), ) diff --git a/src/logic/tokens/store/actions/fetchTokens.ts b/src/logic/tokens/store/actions/fetchTokens.ts index 5c5bde49..20e170af 100644 --- a/src/logic/tokens/store/actions/fetchTokens.ts +++ b/src/logic/tokens/store/actions/fetchTokens.ts @@ -3,12 +3,13 @@ import HumanFriendlyToken from '@gnosis.pm/util-contracts/build/contracts/HumanF import ERC20Detailed from '@openzeppelin/contracts/build/contracts/ERC20Detailed.json' import ERC721 from '@openzeppelin/contracts/build/contracts/ERC721.json' import { List } from 'immutable' -import contract from 'truffle-contract' +import contract from '@truffle/contract/index.js' +import { AbiItem } from 'web3-utils' import saveTokens from './saveTokens' import generateBatchRequests from 'src/logic/contracts/generateBatchRequests' -import { fetchTokenList } from 'src/logic/tokens/api' +import { fetchErc20AndErc721AssetsList } from 'src/logic/tokens/api' import { makeToken, Token } from 'src/logic/tokens/store/model/token' import { tokensSelector } from 'src/logic/tokens/store/selectors' import { getWeb3 } from 'src/logic/wallets/getWeb3' @@ -53,8 +54,8 @@ export const containsMethodByHash = async (contractAddress: string, methodHash: } const getTokenValues = (tokenAddress) => - generateBatchRequests({ - abi: ERC20Detailed.abi, + generateBatchRequests<[undefined, string | undefined, string | undefined, string | undefined]>({ + abi: ERC20Detailed.abi as AbiItem[], address: tokenAddress, methods: ['decimals', 'name', 'symbol'], }) @@ -69,7 +70,7 @@ export const getTokenInfos = async (tokenAddress: string): Promise async ( const { data: { results: tokenList }, - } = await fetchTokenList() + } = await fetchErc20AndErc721AssetsList() - if (currentSavedTokens && currentSavedTokens.size === tokenList.length) { + const erc20Tokens = tokenList.filter((token) => token.type.toLowerCase() === 'erc20') + + if (currentSavedTokens?.size === erc20Tokens.length) { return } - const tokens = List(tokenList.map((token) => makeToken(token))) + const tokens = List(erc20Tokens.map((token) => makeToken(token))) dispatch(saveTokens(tokens)) } catch (err) { diff --git a/src/logic/tokens/utils/__tests__/tokenHelpers.test.ts b/src/logic/tokens/utils/__tests__/tokenHelpers.test.ts index 7afee4ed..0351af13 100644 --- a/src/logic/tokens/utils/__tests__/tokenHelpers.test.ts +++ b/src/logic/tokens/utils/__tests__/tokenHelpers.test.ts @@ -1,6 +1,7 @@ import { makeToken } from 'src/logic/tokens/store/model/token' import { getERC20DecimalsAndSymbol, isERC721Contract, isTokenTransfer } from 'src/logic/tokens/utils/tokenHelpers' import { getMockedTxServiceModel } from 'src/test/utils/safeHelper' +import { fromTokenUnit, toTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' describe('isTokenTransfer', () => { const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf' @@ -113,7 +114,11 @@ describe('getERC20DecimalsAndSymbol', () => { const generateBatchRequests = require('src/logic/contracts/generateBatchRequests') const spyTokenInfos = fetchTokens.getTokenInfos.mockImplementationOnce(() => null) - const spyGenerateBatchRequest = generateBatchRequests.default.mockImplementationOnce(() => [decimals, symbol]) + const spyGenerateBatchRequest = generateBatchRequests.default.mockImplementationOnce(() => [ + undefined, + decimals, + symbol, + ]) // when const result = await getERC20DecimalsAndSymbol(tokenAddress) @@ -171,4 +176,31 @@ describe('isERC721Contract', () => { expect(result).toEqual(expectedResult) expect(standardContractSpy).toHaveBeenCalled() }) + it('It should return the right conversion from unit to token', () => { + // given + const decimals = Number(18) + + const expectedResult = '0.000000003' + const ESTIMATED_GAS_COST = 3e9 // 3 Gwei + + // when + const gasCosts = fromTokenUnit(ESTIMATED_GAS_COST, decimals) + + // then + expect(gasCosts).toEqual(expectedResult) + }) + + it('It should return the right conversion from token to unit', () => { + // given + const decimals = Number(18) + + const expectedResult = '300000000000000000' + const VALUE = 0.3 + + // when + const txValue = toTokenUnit(VALUE, decimals) + + // then + expect(txValue).toEqual(expectedResult) + }) }) diff --git a/src/logic/tokens/utils/humanReadableValue.ts b/src/logic/tokens/utils/humanReadableValue.ts index 7716a1d5..2d2afc17 100644 --- a/src/logic/tokens/utils/humanReadableValue.ts +++ b/src/logic/tokens/utils/humanReadableValue.ts @@ -3,3 +3,17 @@ import { BigNumber } from 'bignumber.js' export const humanReadableValue = (value: number | string, decimals = 18): string => { return new BigNumber(value).times(`1e-${decimals}`).toFixed() } + +export const fromTokenUnit = (amount: number | string, decimals: string | number): string => + new BigNumber(amount).times(`1e-${decimals}`).toFixed() + +export const toTokenUnit = (amount: number | string, decimals: string | number): string => { + const amountBN = new BigNumber(amount).times(`1e${decimals}`) + const [, amountDecimalPlaces] = amount.toString().split('.') + + if (amountDecimalPlaces?.length >= +decimals) { + return amountBN.toFixed(+decimals, BigNumber.ROUND_DOWN) + } + + return amountBN.toFixed() +} diff --git a/src/logic/tokens/utils/tokenHelpers.ts b/src/logic/tokens/utils/tokenHelpers.ts index 48134bdb..b7bb4b2f 100644 --- a/src/logic/tokens/utils/tokenHelpers.ts +++ b/src/logic/tokens/utils/tokenHelpers.ts @@ -1,4 +1,6 @@ -import logo from 'src/assets/icons/icon_etherTokens.svg' +import { AbiItem } from 'web3-utils' + +import { getNetworkInfo } from 'src/config' import generateBatchRequests from 'src/logic/contracts/generateBatchRequests' import { getStandardTokenContract, @@ -6,22 +8,18 @@ import { getERC721TokenContract, } from 'src/logic/tokens/store/actions/fetchTokens' import { makeToken, Token } from 'src/logic/tokens/store/model/token' +import { TokenState } from 'src/logic/tokens/store/reducer/tokens' import { ALTERNATIVE_TOKEN_ABI } from 'src/logic/tokens/utils/alternativeAbi' import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3' import { isEmptyData } from 'src/logic/safe/store/actions/transactions/utils/transactionHelpers' import { TxServiceModel } from 'src/logic/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions' -import { Map } from 'immutable' -export const ETH_ADDRESS = '0x000' export const SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH = '42842e0e' export const getEthAsToken = (balance: string | number): Token => { + const { nativeCoin } = getNetworkInfo() return makeToken({ - address: ETH_ADDRESS, - name: 'Ether', - symbol: 'ETH', - decimals: 18, - logoUri: logo, + ...nativeCoin, balance, }) } @@ -44,18 +42,13 @@ export const isTokenTransfer = (tx: TxServiceModel): boolean => { return !isEmptyData(tx.data) && tx.data?.substring(0, 10) === '0xa9059cbb' && Number(tx.value) === 0 } -export const isSendERC721Transaction = ( - tx: TxServiceModel, - txCode: string | null, - knownTokens: Map, -): boolean => { +export const isSendERC721Transaction = (tx: TxServiceModel, txCode?: string, knownTokens?: TokenState): boolean => { // "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85" - ens token contract, includes safeTransferFrom // but no proper ERC721 standard implemented return ( - (txCode && - txCode.includes(SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH) && + (txCode?.includes(SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH) && tx.to !== '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85') || - (isTokenTransfer(tx) && !knownTokens.get(tx.to)) + (isTokenTransfer(tx) && !knownTokens?.get(tx.to)) ) } @@ -79,14 +72,16 @@ export const getERC20DecimalsAndSymbol = async ( const storedTokenInfo = await getTokenInfos(tokenAddress) if (!storedTokenInfo) { - const [tokenDecimals, tokenSymbol] = await generateBatchRequests({ - abi: ALTERNATIVE_TOKEN_ABI, + const [, tokenDecimals, tokenSymbol] = await generateBatchRequests< + [undefined, string | undefined, string | undefined] + >({ + abi: ALTERNATIVE_TOKEN_ABI as AbiItem[], address: tokenAddress, methods: ['decimals', 'symbol'], }) - return { decimals: Number(tokenDecimals), symbol: tokenSymbol } + return { decimals: Number(tokenDecimals), symbol: tokenSymbol ?? 'UNKNOWN' } } - return { decimals: storedTokenInfo.decimals as number, symbol: storedTokenInfo.symbol } + return { decimals: Number(storedTokenInfo.decimals), symbol: storedTokenInfo.symbol } } catch (err) { console.error(`Failed to retrieve token info for ERC20 token ${tokenAddress}`) } @@ -96,8 +91,8 @@ export const getERC20DecimalsAndSymbol = async ( export const isSendERC20Transaction = async ( tx: TxServiceModel, - txCode: string | null, - knownTokens: Map, + txCode?: string, + knownTokens?: TokenState, ): Promise => { let isSendTokenTx = !isSendERC721Transaction(tx, txCode, knownTokens) && isTokenTransfer(tx) diff --git a/src/logic/wallets/getWeb3.ts b/src/logic/wallets/getWeb3.ts index c3c7b0f4..ff1e26f7 100644 --- a/src/logic/wallets/getWeb3.ts +++ b/src/logic/wallets/getWeb3.ts @@ -1,24 +1,12 @@ import Web3 from 'web3' +import { provider as Provider } from 'web3-core' +import { ContentHash } from 'web3-eth-ens' import { sameAddress } from './ethAddresses' import { EMPTY_DATA } from './ethTransactions' - -import { getNetwork } from '../../config' -import { ContentHash } from 'web3-eth-ens' -import { provider as Provider } from 'web3-core' import { ProviderProps } from './store/model/provider' - -export const ETHEREUM_NETWORK = { - MAINNET: 'MAINNET' as const, - MORDEN: 'MORDEN' as const, - ROPSTEN: 'ROPSTEN' as const, - RINKEBY: 'RINKEBY' as const, - GOERLI: 'GOERLI' as const, - KOVAN: 'KOVAN' as const, - UNKNOWN: 'UNKNOWN' as const, -} - -export type EthereumNetworks = typeof ETHEREUM_NETWORK[keyof typeof ETHEREUM_NETWORK] +import { NODE_ENV } from 'src/utils/constants' +import { getRpcServiceUrl } from 'src/config' export const WALLET_PROVIDER = { SAFE: 'SAFE', @@ -38,34 +26,16 @@ export const WALLET_PROVIDER = { TREZOR: 'TREZOR', } -export const ETHEREUM_NETWORK_IDS = { - 1: ETHEREUM_NETWORK.MAINNET, - 2: ETHEREUM_NETWORK.MORDEN, - 3: ETHEREUM_NETWORK.ROPSTEN, - 4: ETHEREUM_NETWORK.RINKEBY, - 5: ETHEREUM_NETWORK.GOERLI, - 42: ETHEREUM_NETWORK.KOVAN, -} - -export const getEtherScanLink = (type: string, value: string): string => { - const network = getNetwork() - return `https://${ - network.toLowerCase() === 'mainnet' ? '' : `${network.toLowerCase()}.` - }etherscan.io/${type}/${value}` -} - -export const getInfuraUrl = (): string => { - const isMainnet = process.env.REACT_APP_NETWORK === 'mainnet' - - return `https://${isMainnet ? 'mainnet' : 'rinkeby'}.infura.io:443/v3/${process.env.REACT_APP_INFURA_TOKEN}` -} - // With some wallets from web3connect you have to use their provider instance only for signing // And our own one to fetch data -export const web3ReadOnly = +const httpProviderOptions = { + timeout: 10_000, +} +export const web3ReadOnly = new Web3( process.env.NODE_ENV !== 'test' - ? new Web3(new Web3.providers.HttpProvider(getInfuraUrl())) - : new Web3(window.web3?.currentProvider || 'ws://localhost:8545') + ? new Web3.providers.HttpProvider(getRpcServiceUrl(), httpProviderOptions) + : window.web3?.currentProvider || 'ws://localhost:8545', +) let web3 = web3ReadOnly export const getWeb3 = (): Web3 => web3 @@ -77,7 +47,7 @@ export const resetWeb3 = (): void => { export const getAccountFrom = async (web3Provider: Web3): Promise => { const accounts = await web3Provider.eth.getAccounts() - if (process.env.NODE_ENV === 'test' && window.testAccountIndex) { + if (NODE_ENV === 'test' && window.testAccountIndex) { return accounts[window.testAccountIndex] } diff --git a/src/logic/wallets/store/actions/fetchProvider.ts b/src/logic/wallets/store/actions/fetchProvider.ts index 3a7573fc..79ce4479 100644 --- a/src/logic/wallets/store/actions/fetchProvider.ts +++ b/src/logic/wallets/store/actions/fetchProvider.ts @@ -2,10 +2,10 @@ import ReactGA from 'react-ga' import addProvider from './addProvider' -import { getNetwork } from 'src/config' +import { getNetworkId, getNetworkInfo } from 'src/config' import { NOTIFICATIONS, enhanceSnackbarForAction } from 'src/logic/notifications' import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackbar' -import { ETHEREUM_NETWORK, ETHEREUM_NETWORK_IDS, getProviderInfo, getWeb3 } from 'src/logic/wallets/getWeb3' +import { getProviderInfo, getWeb3 } from 'src/logic/wallets/getWeb3' import { makeProvider } from 'src/logic/wallets/store/model/provider' import { updateStoredTransactionsStatus } from 'src/logic/safe/store/actions/transactions/utils/transactionHelpers' import { Dispatch } from 'redux' @@ -24,12 +24,13 @@ const handleProviderNotification = (provider, dispatch) => { return } - if (ETHEREUM_NETWORK_IDS[network] !== getNetwork()) { + if (network !== getNetworkId()) { dispatch(enqueueSnackbar(NOTIFICATIONS.WRONG_NETWORK_MSG)) return } - if (ETHEREUM_NETWORK.RINKEBY === getNetwork()) { - dispatch(enqueueSnackbar(enhanceSnackbarForAction(NOTIFICATIONS.RINKEBY_VERSION_MSG))) + + if (getNetworkInfo().isTestNet) { + dispatch(enqueueSnackbar(enhanceSnackbarForAction(NOTIFICATIONS.TESTNET_VERSION_MSG))) } if (available) { diff --git a/src/logic/wallets/store/model/provider.ts b/src/logic/wallets/store/model/provider.ts index 1e8ad09a..146b2ddb 100644 --- a/src/logic/wallets/store/model/provider.ts +++ b/src/logic/wallets/store/model/provider.ts @@ -1,11 +1,13 @@ import { Record, RecordOf } from 'immutable' +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' + export type ProviderProps = { name: string loaded: boolean available: boolean account: string - network: number + network: ETHEREUM_NETWORK smartContractWallet: boolean hardwareWallet: boolean } @@ -15,7 +17,7 @@ export const makeProvider = Record({ loaded: false, available: false, account: '', - network: 0, + network: ETHEREUM_NETWORK.UNKNOWN, smartContractWallet: false, hardwareWallet: false, }) diff --git a/src/logic/wallets/store/selectors/index.ts b/src/logic/wallets/store/selectors/index.ts index ed147c38..56e41f67 100644 --- a/src/logic/wallets/store/selectors/index.ts +++ b/src/logic/wallets/store/selectors/index.ts @@ -1,6 +1,6 @@ import { createSelector } from 'reselect' -import { ETHEREUM_NETWORK, ETHEREUM_NETWORK_IDS, EthereumNetworks } from 'src/logic/wallets/getWeb3' +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' import { PROVIDER_REDUCER_ID, ProviderState } from 'src/logic/wallets/store/reducer/provider' import { AppReduxState } from 'src/store' @@ -18,9 +18,10 @@ export const providerNameSelector = createSelector(providerSelector, (provider: export const networkSelector = createSelector( providerSelector, - (provider: ProviderState): EthereumNetworks => { + (provider: ProviderState): ETHEREUM_NETWORK => { const networkId = provider.get('network') - return ETHEREUM_NETWORK_IDS[networkId] || ETHEREUM_NETWORK.UNKNOWN + + return networkId ?? ETHEREUM_NETWORK.UNKNOWN }, ) diff --git a/src/logic/wallets/utils/walletList.ts b/src/logic/wallets/utils/walletList.ts index 70452f18..1939a1c3 100644 --- a/src/logic/wallets/utils/walletList.ts +++ b/src/logic/wallets/utils/walletList.ts @@ -1,19 +1,25 @@ -import { getInfuraUrl } from '../getWeb3' +import { WalletInitOptions } from 'bnc-onboard/dist/src/interfaces' -const isMainnet = process.env.REACT_APP_NETWORK === 'mainnet' +import { getNetworkId, getRpcServiceUrl } from 'src/config' +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' +import { FORTMATIC_KEY, PORTIS_ID } from 'src/utils/constants' -const PORTIS_DAPP_ID = isMainnet ? process.env.REACT_APP_PORTIS_ID : '852b763d-f28b-4463-80cb-846d7ec5806b' -// const SQUARELINK_CLIENT_ID = isMainnet ? process.env.REACT_APP_SQUARELINK_ID : '46ce08fe50913cfa1b78' -const FORTMATIC_API_KEY = isMainnet ? process.env.REACT_APP_FORTMATIC_KEY : 'pk_test_CAD437AA29BE0A40' +const networkId = getNetworkId() +const PORTIS_DAPP_ID = PORTIS_ID[networkId] ?? PORTIS_ID[ETHEREUM_NETWORK.RINKEBY] +const FORTMATIC_API_KEY = FORTMATIC_KEY[networkId] ?? FORTMATIC_KEY[ETHEREUM_NETWORK.RINKEBY] -const infuraUrl = getInfuraUrl() +type Wallet = WalletInitOptions & { + desktop: boolean +} -const wallets = [ +const rpcUrl = getRpcServiceUrl() +const wallets: Wallet[] = [ { walletName: 'metamask', preferred: true, desktop: false }, { walletName: 'walletConnect', preferred: true, - infuraKey: process.env.REACT_APP_INFURA_TOKEN, + // as stated in the documentation, `infuraKey` is not mandatory if rpc is provided + rpc: { [networkId]: rpcUrl }, desktop: true, bridge: 'https://safe-walletconnect.gnosis.io/', }, @@ -23,13 +29,13 @@ const wallets = [ preferred: true, email: 'safe@gnosis.io', desktop: true, - rpcUrl: infuraUrl, + rpcUrl, }, { walletName: 'ledger', desktop: true, preferred: true, - rpcUrl: infuraUrl, + rpcUrl, LedgerTransport: (window as any).TransportNodeHid, }, { walletName: 'trust', preferred: true, desktop: false }, @@ -48,16 +54,18 @@ const wallets = [ { walletName: 'torus', desktop: true }, { walletName: 'unilogin', desktop: true }, { walletName: 'coinbase', desktop: false }, - { walletName: 'walletLink', rpcUrl: infuraUrl, desktop: false }, + { walletName: 'walletLink', rpcUrl, desktop: false }, { walletName: 'opera', desktop: false }, { walletName: 'operaTouch', desktop: false }, ] -export const getSupportedWallets = () => { +export const getSupportedWallets = (): WalletInitOptions[] => { const { isDesktop } = window as any /* eslint-disable no-unused-vars */ - if (isDesktop) return wallets.filter((wallet) => wallet.desktop).map(({ desktop, ...rest }) => rest) + if (isDesktop) { + return wallets.filter((wallet) => wallet.desktop).map(({ desktop, ...rest }) => rest) + } return wallets.map(({ desktop, ...rest }) => rest) } diff --git a/src/routes/load/components/DetailsForm/index.tsx b/src/routes/load/components/DetailsForm/index.tsx index acd1cd94..1524f9e1 100644 --- a/src/routes/load/components/DetailsForm/index.tsx +++ b/src/routes/load/components/DetailsForm/index.tsx @@ -43,7 +43,7 @@ const useStyles = makeStyles({ }) export const SAFE_INSTANCE_ERROR = 'Address given is not a Safe instance' -export const SAFE_MASTERCOPY_ERROR = 'Mastercopy used by this Safe is not the same' +export const SAFE_MASTERCOPY_ERROR = 'Address is not a Safe or mastercopy is not supported' // In case of an error here, it will be swallowed by final-form // So if you're experiencing any strang behaviours like freeze or hanging @@ -73,7 +73,7 @@ export const safeFieldsValidation = async (values): Promise { @@ -112,7 +112,7 @@ const OwnerListComponent = (props) => { {address} - + diff --git a/src/routes/load/components/ReviewInformation/index.tsx b/src/routes/load/components/ReviewInformation/index.tsx index 49a7588f..2c5ba075 100644 --- a/src/routes/load/components/ReviewInformation/index.tsx +++ b/src/routes/load/components/ReviewInformation/index.tsx @@ -5,12 +5,12 @@ import React from 'react' import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' import Identicon from 'src/components/Identicon' -import OpenPaper from 'src/components/Stepper/OpenPaper' import Block from 'src/components/layout/Block' import Col from 'src/components/layout/Col' import Hairline from 'src/components/layout/Hairline' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' +import OpenPaper from 'src/components/Stepper/OpenPaper' import { shortVersionOf } from 'src/logic/wallets/ethAddresses' import { FIELD_LOAD_ADDRESS, FIELD_LOAD_NAME, THRESHOLD } from 'src/routes/load/components/fields' import { getNumOwnersFrom, getOwnerAddressBy, getOwnerNameBy } from 'src/routes/open/components/fields' @@ -76,7 +76,7 @@ const ReviewComponent = ({ userAddress, values }: Props): React.ReactElement => {shortVersionOf(safeAddress, 4)} - +
@@ -121,7 +121,7 @@ const ReviewComponent = ({ userAddress, values }: Props): React.ReactElement => {address} - + diff --git a/src/routes/load/container/Load.tsx b/src/routes/load/container/Load.tsx index a3452bfd..6e4b5305 100644 --- a/src/routes/load/container/Load.tsx +++ b/src/routes/load/container/Load.tsx @@ -1,5 +1,6 @@ import * as React from 'react' import { useDispatch, useSelector } from 'react-redux' +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' import Layout from 'src/routes/load/components/Layout' import { FIELD_LOAD_ADDRESS, FIELD_LOAD_NAME } from '../components/fields' @@ -77,7 +78,12 @@ const Load = (): React.ReactElement => { return ( - + ) } diff --git a/src/routes/open/components/ReviewInformation/index.tsx b/src/routes/open/components/ReviewInformation/index.tsx index c5d6c4c1..4c901b40 100644 --- a/src/routes/open/components/ReviewInformation/index.tsx +++ b/src/routes/open/components/ReviewInformation/index.tsx @@ -1,22 +1,22 @@ import TableContainer from '@material-ui/core/TableContainer' import classNames from 'classnames' import React, { useEffect, useState } from 'react' - -import { FIELD_CONFIRMATIONS, FIELD_NAME, getNumOwnersFrom } from '../fields' - +import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' +import { getNetworkInfo } from 'src/config' import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' import Identicon from 'src/components/Identicon' -import OpenPaper from 'src/components/Stepper/OpenPaper' import Block from 'src/components/layout/Block' import Col from 'src/components/layout/Col' import Hairline from 'src/components/layout/Hairline' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' +import OpenPaper from 'src/components/Stepper/OpenPaper' import { estimateGasForDeployingSafe } from 'src/logic/contracts/safeContracts' import { formatAmount } from 'src/logic/tokens/utils/formatAmount' -import { getWeb3 } from 'src/logic/wallets/getWeb3' import { getAccountsFrom, getNamesFrom } from 'src/routes/open/utils/safeDataExtractor' + +import { FIELD_CONFIRMATIONS, FIELD_NAME, getNumOwnersFrom } from '../fields' import { useStyles } from './styles' type ReviewComponentProps = { @@ -24,6 +24,8 @@ type ReviewComponentProps = { values: any } +const { nativeCoin } = getNetworkInfo() + const ReviewComponent = ({ userAccount, values }: ReviewComponentProps) => { const classes = useStyles() @@ -37,11 +39,9 @@ const ReviewComponent = ({ userAccount, values }: ReviewComponentProps) => { if (!addresses.length || !numOwners || !userAccount) { return } - const web3 = getWeb3() - const { fromWei, toBN } = web3.utils - const estimatedGasCosts = await estimateGasForDeployingSafe(addresses, numOwners, userAccount) - const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether') - const formattedGasCosts = formatAmount(gasCostsAsEth) + const estimatedGasCosts = (await estimateGasForDeployingSafe(addresses, numOwners, userAccount)).toString() + const gasCosts = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals) + const formattedGasCosts = formatAmount(gasCosts) setGasCosts(formattedGasCosts) } @@ -118,7 +118,7 @@ const ReviewComponent = ({ userAccount, values }: ReviewComponentProps) => { {addresses[index]} - + @@ -132,8 +132,8 @@ const ReviewComponent = ({ userAccount, values }: ReviewComponentProps) => { You're about to create a new Safe and will have to confirm a transaction with your currently connected - wallet. The creation will cost approximately {gasCosts} ETH. The exact amount will be determined by your - wallet. + wallet. The creation will cost approximately {gasCosts} {nativeCoin.name}. The exact amount will be determined + by your wallet. diff --git a/src/routes/open/container/Open.tsx b/src/routes/open/container/Open.tsx index 360d3884..9b98eac3 100644 --- a/src/routes/open/container/Open.tsx +++ b/src/routes/open/container/Open.tsx @@ -22,6 +22,7 @@ import { loadFromStorage, removeFromStorage, saveToStorage } from 'src/utils/sto import { userAccountSelector } from 'src/logic/wallets/store/selectors' import { SafeRecordProps } from 'src/logic/safe/store/models/safe' import { addOrUpdateSafe } from 'src/logic/safe/store/actions/addOrUpdateSafe' +import { PromiEvent, TransactionReceipt } from 'web3-core' const SAFE_PENDING_CREATION_STORAGE_KEY = 'SAFE_PENDING_CREATION_STORAGE_KEY' @@ -60,7 +61,7 @@ export const createSafe = (values, userAccount) => { const deploymentTx = getSafeDeploymentTransaction(ownerAddresses, confirmations) - const promiEvent = deploymentTx.send({ from: userAccount, value: 0 }) + const promiEvent = deploymentTx.send({ from: userAccount }) promiEvent .once('transactionHash', (txHash) => { @@ -68,7 +69,7 @@ export const createSafe = (values, userAccount) => { }) .then(async (receipt) => { await checkReceiptStatus(receipt.transactionHash) - const safeAddress = receipt.events.ProxyCreation.returnValues.proxy + const safeAddress = receipt.events?.ProxyCreation.returnValues.proxy const safeProps = await getSafeProps(safeAddress, name, ownersNames, ownerAddresses) // returning info for testing purposes, in app is fully async return { safeAddress: safeProps.address, safeTx: receipt } @@ -83,7 +84,7 @@ export const createSafe = (values, userAccount) => { const Open = (): React.ReactElement => { const [loading, setLoading] = useState(false) const [showProgress, setShowProgress] = useState(false) - const [creationTxPromise, setCreationTxPromise] = useState() + const [creationTxPromise, setCreationTxPromise] = useState>() const [safeCreationPendingInfo, setSafeCreationPendingInfo] = useState() const [safePropsFromUrl, setSafePropsFromUrl] = useState() const userAccount = useSelector(userAccountSelector) diff --git a/src/routes/opening/components/Footer.tsx b/src/routes/opening/components/Footer.tsx index 292000e3..8f213484 100644 --- a/src/routes/opening/components/Footer.tsx +++ b/src/routes/opening/components/Footer.tsx @@ -2,10 +2,10 @@ import React, { SyntheticEvent } from 'react' import styled from 'styled-components' import Button from 'src/components/layout/Button' -import { getEtherScanLink } from 'src/logic/wallets/getWeb3' import { connected } from 'src/theme/variables' +import { getExplorerInfo } from 'src/config' -const EtherScanLink = styled.a` +const ExplorerLink = styled.a` color: ${connected}; ` @@ -13,24 +13,31 @@ const ButtonWithMargin = styled(Button)` margin-right: 16px; ` -export const GenericFooter = ({ safeCreationTxHash }: { safeCreationTxHash: string }) => ( - -

This process should take a couple of minutes.

-

- Follow the progress on{' '} - - Etherscan.io - - . -

-
-) +export const GenericFooter = ({ safeCreationTxHash }: { safeCreationTxHash: string }) => { + const explorerInfo = getExplorerInfo(safeCreationTxHash) + const { url, alt } = explorerInfo() + const match = /(http|https):\/\/(\w+\.\w+)\/.*/i.exec(url) + const explorerDomain = match !== null ? match[2] : 'Network Explorer' + + return ( + +

This process should take a couple of minutes.

+

+ Follow the progress on{' '} + + {explorerDomain} + + . +

+
+ ) +} export const ContinueFooter = ({ continueButtonDisabled, diff --git a/src/routes/safe/components/AddressBook/index.tsx b/src/routes/safe/components/AddressBook/index.tsx index 42597e64..b10cc4c4 100644 --- a/src/routes/safe/components/AddressBook/index.tsx +++ b/src/routes/safe/components/AddressBook/index.tsx @@ -40,7 +40,6 @@ import { addressBookQueryParamsSelector, safesListSelector } from 'src/logic/saf import { checksumAddress } from 'src/utils/checksumAddress' import { grantedSelector } from 'src/routes/safe/container/selector' import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics' -import { getValidAddressBookName } from 'src/logic/addressBook/utils' const useStyles = makeStyles(styles) @@ -167,7 +166,7 @@ const AddressBookTable = (): React.ReactElement => { {column.id === AB_ADDRESS_ID ? ( ) : ( - getValidAddressBookName(row[column.id]) + row[column.id] )} ) diff --git a/src/routes/safe/components/Apps/components/ConfirmTransactionModal.tsx b/src/routes/safe/components/Apps/components/ConfirmTransactionModal.tsx index 01353288..379a66c6 100644 --- a/src/routes/safe/components/Apps/components/ConfirmTransactionModal.tsx +++ b/src/routes/safe/components/Apps/components/ConfirmTransactionModal.tsx @@ -14,12 +14,13 @@ import Heading from 'src/components/layout/Heading' import Img from 'src/components/layout/Img' import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers' import { SafeApp } from 'src/routes/safe/components/Apps/types.d' -import { humanReadableValue } from 'src/logic/tokens/utils/humanReadableValue' +import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' import { useDispatch } from 'react-redux' import createTransaction from 'src/logic/safe/store/actions/createTransaction' import { MULTI_SEND_ADDRESS } from 'src/logic/contracts/safeContracts' import { DELEGATE_CALL, TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import { encodeMultiSendCall } from 'src/logic/safe/transactions/multisend' +import { getNetworkInfo } from 'src/config' const isTxValid = (t: Transaction): boolean => { if (!['string', 'number'].includes(typeof t.value)) { @@ -74,6 +75,8 @@ type OwnProps = { onClose: () => void } +const { nativeCoin } = getNetworkInfo() + const ConfirmTransactionModal = ({ isOpen, app, @@ -146,7 +149,9 @@ const ConfirmTransactionModal = ({ Value
Ether - {humanReadableValue(tx.value, 18)} ETH + + {fromTokenUnit(tx.value, nativeCoin.decimals)} {nativeCoin.name} +
diff --git a/src/routes/safe/components/Apps/hooks/useAppList.ts b/src/routes/safe/components/Apps/hooks/useAppList.ts index fc48a716..c2f13c13 100644 --- a/src/routes/safe/components/Apps/hooks/useAppList.ts +++ b/src/routes/safe/components/Apps/hooks/useAppList.ts @@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react' import { loadFromStorage, saveToStorage } from 'src/utils/storage' import { getAppInfoFromUrl, staticAppsList } from '../utils' import { SafeApp, StoredSafeApp } from '../types' +import { getNetworkId } from 'src/config' const APPS_STORAGE_KEY = 'APPS_STORAGE_KEY' @@ -28,20 +29,31 @@ const useAppList = (): UseAppListReturnType => { // * third-party apps added by the user // * disabled status for both static and third-party apps const persistedAppList = (await loadFromStorage(APPS_STORAGE_KEY)) || [] - const list: (StoredSafeApp & { isDeletable?: boolean })[] = persistedAppList.map((a) => ({ + let list: (StoredSafeApp & { isDeletable: boolean; networks?: number[] })[] = persistedAppList.map((a) => ({ ...a, isDeletable: true, })) + // merge stored apps with static apps (apps added manually can be deleted by the user) staticAppsList.forEach((staticApp) => { const app = list.find((persistedApp) => persistedApp.url === staticApp.url) - if (!app) { - list.push({ ...staticApp, isDeletable: false }) - } else { + if (app) { app.isDeletable = false + app.networks = staticApp.networks + } else { + list.push({ ...staticApp, isDeletable: false }) } }) + // filter app by network + list = list.filter((app) => { + // if the app does not expose supported networks, include them. (backward compatible) + if (!app.networks) { + return true + } + return app.networks.includes(getNetworkId()) + }) + let apps: SafeApp[] = [] // using the appURL to recover app info for (let index = 0; index < list.length; index++) { diff --git a/src/routes/safe/components/Apps/hooks/useIframeMessageHandler.ts b/src/routes/safe/components/Apps/hooks/useIframeMessageHandler.ts index a1a0e695..d38a4399 100644 --- a/src/routes/safe/components/Apps/hooks/useIframeMessageHandler.ts +++ b/src/routes/safe/components/Apps/hooks/useIframeMessageHandler.ts @@ -12,13 +12,12 @@ import { } from '@gnosis.pm/safe-apps-sdk' import { useDispatch, useSelector } from 'react-redux' import { useEffect, useCallback, MutableRefObject } from 'react' -import { getTxServiceHost } from 'src/config/' +import { getNetworkName, getTxServiceUrl } from 'src/config/' import { safeEthBalanceSelector, safeNameSelector, safeParamAddressFromStateSelector, } from 'src/logic/safe/store/selectors' -import { networkSelector } from 'src/logic/wallets/store/selectors' import { SafeApp } from 'src/routes/safe/components/Apps/types.d' type InterfaceMessageProps = { @@ -42,6 +41,8 @@ interface InterfaceMessageRequest extends InterfaceMessageProps void, @@ -52,7 +53,6 @@ const useIframeMessageHandler = ( const safeName = useSelector(safeNameSelector) const safeAddress = useSelector(safeParamAddressFromStateSelector) const ethBalance = useSelector(safeEthBalanceSelector) - const network = useSelector(networkSelector) const dispatch = useDispatch() const sendMessageToIframe = useCallback( @@ -90,14 +90,14 @@ const useIframeMessageHandler = ( messageId: INTERFACE_MESSAGES.ON_SAFE_INFO, data: { safeAddress: safeAddress as string, - network: network.toLowerCase() as LowercaseNetworks, + network: NETWORK_NAME.toLowerCase() as LowercaseNetworks, ethBalance: ethBalance as string, }, } const envInfoMessage = { messageId: INTERFACE_MESSAGES.ENV_INFO, data: { - txServiceUrl: getTxServiceHost(), + txServiceUrl: getTxServiceUrl(), }, } @@ -132,7 +132,6 @@ const useIframeMessageHandler = ( dispatch, enqueueSnackbar, ethBalance, - network, openConfirmationModal, safeAddress, safeName, diff --git a/src/routes/safe/components/Apps/index.tsx b/src/routes/safe/components/Apps/index.tsx index d34d8dd4..324d7473 100644 --- a/src/routes/safe/components/Apps/index.tsx +++ b/src/routes/safe/components/Apps/index.tsx @@ -10,7 +10,6 @@ import { useAppList } from './hooks/useAppList' import { SafeApp } from './types.d' import LCL from 'src/components/ListContentLayout' -import { networkSelector } from 'src/logic/wallets/store/selectors' import { grantedSelector } from 'src/routes/safe/container/selector' import { safeEthBalanceSelector, @@ -21,6 +20,7 @@ import { isSameURL } from 'src/utils/url' import { useIframeMessageHandler } from './hooks/useIframeMessageHandler' import ConfirmTransactionModal from './components/ConfirmTransactionModal' import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics' +import { getNetworkName } from 'src/config' const centerCSS = css` display: flex; @@ -41,7 +41,7 @@ const StyledCard = styled(Card)` const CenteredMT = styled.div` ${centerCSS}; - margin-top: 5px; + margin-top: 16px; ` type ConfirmTransactionModalState = { @@ -56,6 +56,8 @@ const INITIAL_CONFIRM_TX_MODAL_STATE: ConfirmTransactionModalState = { requestId: undefined, } +const NETWORK_NAME = getNetworkName() + const Apps = (): React.ReactElement => { const { appList, loadingAppList, onAppToggle, onAppAdded, onAppRemoved } = useAppList() @@ -70,7 +72,6 @@ const Apps = (): React.ReactElement => { const granted = useSelector(grantedSelector) const safeAddress = useSelector(safeParamAddressFromStateSelector) const safeName = useSelector(safeNameSelector) - const network = useSelector(networkSelector) const ethBalance = useSelector(safeEthBalanceSelector) const openConfirmationModal = useCallback( @@ -155,11 +156,11 @@ const Apps = (): React.ReactElement => { messageId: INTERFACE_MESSAGES.ON_SAFE_INFO, data: { safeAddress: safeAddress as string, - network: network.toLowerCase() as LowercaseNetworks, + network: NETWORK_NAME.toLowerCase() as LowercaseNetworks, ethBalance: ethBalance as string, }, }) - }, [ethBalance, network, safeAddress, selectedApp, sendMessageToIframe]) + }, [ethBalance, safeAddress, selectedApp, sendMessageToIframe]) if (loadingAppList || !appList.length || !safeAddress) { return ( @@ -185,7 +186,7 @@ const Apps = (): React.ReactElement => { granted={granted} selectedApp={selectedApp} safeAddress={safeAddress} - network={network} + network={NETWORK_NAME} appIsLoading={appIsLoading} onIframeLoad={handleIframeLoad} /> diff --git a/src/routes/safe/components/Apps/utils.ts b/src/routes/safe/components/Apps/utils.ts index 2cc10e30..da77d424 100644 --- a/src/routes/safe/components/Apps/utils.ts +++ b/src/routes/safe/components/Apps/utils.ts @@ -3,9 +3,10 @@ import memoize from 'lodash.memoize' import { SafeApp } from './types.d' -import { getGnosisSafeAppsUrl } from 'src/config/index' +import { getGnosisSafeAppsUrl } from 'src/config' import { getContentFromENS } from 'src/logic/wallets/getWeb3' import appsIconSvg from 'src/routes/safe/components/Transactions/TxsTable/TxType/assets/appsIcon.svg' +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' const removeLastTrailingSlash = (url) => { if (url.substr(-1) === '/') { @@ -15,33 +16,99 @@ const removeLastTrailingSlash = (url) => { } const gnosisAppsUrl = removeLastTrailingSlash(getGnosisSafeAppsUrl()) -export const staticAppsList: Array<{ url: string; disabled: boolean }> = [ +export const staticAppsList: Array<{ url: string; disabled: boolean; networks: number[] }> = [ // 1inch - { url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmUDTSghr154kCCGguyA3cbG5HRVd2tQgNR7yD69bcsjm5`, disabled: false }, + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmUDTSghr154kCCGguyA3cbG5HRVd2tQgNR7yD69bcsjm5`, + disabled: false, + networks: [ETHEREUM_NETWORK.MAINNET], + }, // Aave - { url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmY1MUZo44UkT8EokYHs7xDvWEziYSn7n3c4ojVB6qo3SM`, disabled: false }, + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmY1MUZo44UkT8EokYHs7xDvWEziYSn7n3c4ojVB6qo3SM`, + disabled: false, + networks: [ETHEREUM_NETWORK.MAINNET], + }, //Balancer Exchange - { url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmfPLXne1UrY399RQAcjD1dmBhQrPGZWgp311CDLLW3VTn`, disabled: false }, + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmfPLXne1UrY399RQAcjD1dmBhQrPGZWgp311CDLLW3VTn`, + disabled: false, + networks: [ETHEREUM_NETWORK.MAINNET], + }, //Balancer Pool - { url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmaTucdZYLKTqaewwJduVMM8qfCDhyaEqjd8tBNae26K1J`, disabled: false }, + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmaTucdZYLKTqaewwJduVMM8qfCDhyaEqjd8tBNae26K1J`, + disabled: false, + networks: [ETHEREUM_NETWORK.MAINNET], + }, // Compound - { url: `${gnosisAppsUrl}/compound`, disabled: false }, + { url: `${gnosisAppsUrl}/compound`, disabled: false, networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY] }, // Idle - { url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmZ3oug89a3BaVqdJrJEA8CKmLF4M8snuAnphR6z1yq8V8`, disabled: false }, + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmZ3oug89a3BaVqdJrJEA8CKmLF4M8snuAnphR6z1yq8V8`, + disabled: false, + networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY], + }, // request - { url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmTBBaiDQyGa17DJ7DdviyHbc51fTVgf6Z5PW5w2YUTkgR`, disabled: false }, + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmTBBaiDQyGa17DJ7DdviyHbc51fTVgf6Z5PW5w2YUTkgR`, + disabled: false, + networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY], + }, // Sablier - { url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmVmBWNY6fPrt6SwYynJoCrU7ZWydo3Zr9rDNQR5bcKsFb`, disabled: false }, + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmVmBWNY6fPrt6SwYynJoCrU7ZWydo3Zr9rDNQR5bcKsFb`, + disabled: false, + networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY], + }, // Synthetix - { url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmXLxxczMH4MBEYDeeN9zoiHDzVkeBmB5rBjA3UniPEFcA`, disabled: false }, + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmXLxxczMH4MBEYDeeN9zoiHDzVkeBmB5rBjA3UniPEFcA`, + disabled: false, + networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY], + }, // OpenZeppelin - { url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmQovvfYYMUXjZfNbysQDUEXR8nr55iJRwcYgJQGJR7KEA`, disabled: false }, + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmQovvfYYMUXjZfNbysQDUEXR8nr55iJRwcYgJQGJR7KEA`, + disabled: false, + networks: [ + ETHEREUM_NETWORK.MAINNET, + ETHEREUM_NETWORK.RINKEBY, + ETHEREUM_NETWORK.ENERGY_WEB_CHAIN, + ETHEREUM_NETWORK.VOLTA, + // ETHEREUM_NETWORK.XDAI, + ], + }, // TX-Builder - { url: `${gnosisAppsUrl}/tx-builder`, disabled: false }, + { + url: `${gnosisAppsUrl}/tx-builder`, + disabled: false, + networks: [ + ETHEREUM_NETWORK.MAINNET, + ETHEREUM_NETWORK.RINKEBY, + ETHEREUM_NETWORK.ENERGY_WEB_CHAIN, + ETHEREUM_NETWORK.VOLTA, + ETHEREUM_NETWORK.XDAI, + ], + }, // Wallet-Connect - { url: `${gnosisAppsUrl}/walletConnect`, disabled: false }, + { + url: `${gnosisAppsUrl}/walletConnect`, + disabled: false, + networks: [ + ETHEREUM_NETWORK.MAINNET, + ETHEREUM_NETWORK.RINKEBY, + ETHEREUM_NETWORK.ENERGY_WEB_CHAIN, + ETHEREUM_NETWORK.VOLTA, + ETHEREUM_NETWORK.XDAI, + ], + }, // Yearn Vaults - { url: `${process.env.REACT_APP_IPFS_GATEWAY}/Qme9HuPPhgCtgfj1CktvaDKhTesMueGCV2Kui1Sqna3Xs9`, disabled: false }, + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/Qme9HuPPhgCtgfj1CktvaDKhTesMueGCV2Kui1Sqna3Xs9`, + disabled: false, + networks: [ETHEREUM_NETWORK.MAINNET], + }, ] export const getAppInfoFromOrigin = (origin: string): Record | null => { diff --git a/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx index 371dfb7a..ebbdc9fe 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx @@ -17,6 +17,7 @@ import ContractInteractionIcon from 'src/routes/safe/components/Transactions/Txs import Collectible from '../assets/collectibles.svg' import Token from '../assets/token.svg' +import { FEATURES } from 'src/config/networks/network.d' type ActiveScreen = 'sendFunds' | 'sendCollectible' | 'contractInteraction' @@ -29,7 +30,8 @@ interface ChooseTxTypeProps { const ChooseTxType = ({ onClose, recipientAddress, setActiveScreen }: ChooseTxTypeProps): React.ReactElement => { const classes = useStyles() const featuresEnabled = useSelector(safeFeaturesEnabledSelector) - const erc721Enabled = featuresEnabled?.includes('ERC721') + const erc721Enabled = featuresEnabled?.includes(FEATURES.ERC721) + const contractInteractionEnabled = featuresEnabled?.includes(FEATURES.CONTRACT_INTERACTION) const [disableContractInteraction, setDisableContractInteraction] = React.useState(!!recipientAddress) React.useEffect(() => { @@ -99,22 +101,24 @@ const ChooseTxType = ({ onClose, recipientAddress, setActiveScreen }: ChooseTxTy Send collectible )} - + {contractInteractionEnabled && ( + + )} diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx index fe47ee3f..0df470c8 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx @@ -1,13 +1,16 @@ import React from 'react' +import { useField, useForm } from 'react-final-form' import TextareaField from 'src/components/forms/TextareaField' +import { mustBeEthereumAddress, mustBeEthereumContractAddress } from 'src/components/forms/validator' import Col from 'src/components/layout/Col' import Row from 'src/components/layout/Row' +import { getContractABI } from 'src/config' import { extractUsefulMethods } from 'src/logic/contractInteraction/sources/ABIService' export const NO_DATA = 'no data' -const mustBeValidABI = (abi: string): undefined | string => { +const hasUsefulMethods = (abi: string): undefined | string => { try { const parsedABI = extractUsefulMethods(JSON.parse(abi)) @@ -19,12 +22,42 @@ const mustBeValidABI = (abi: string): undefined | string => { } } -const ContractABI = () => ( - - - - - -) +const ContractABI = (): React.ReactElement => { + const { + input: { value: contractAddress }, + } = useField('contractAddress', { subscription: { value: true } }) + const { mutators } = useForm() + const setAbiValue = React.useRef(mutators.setAbiValue) + + React.useEffect(() => { + const validateAndSetAbi = async () => { + const isEthereumAddress = mustBeEthereumAddress(contractAddress) === undefined + const isEthereumContractAddress = (await mustBeEthereumContractAddress(contractAddress)) === undefined + + if (isEthereumAddress && isEthereumContractAddress) { + const abi = await getContractABI(contractAddress) + const isValidABI = hasUsefulMethods(abi) === undefined + + // this check may help in scenarios where the user first pastes the ABI, + // and then sets a Proxy contract that has no useful methods + if (isValidABI) { + setAbiValue.current(abi) + } + } + } + + if (contractAddress) { + validateAndSetAbi() + } + }, [contractAddress]) + + return ( + + + + + + ) +} export default ContractABI diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/NativeCoinValue/index.tsx similarity index 85% rename from src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx rename to src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/NativeCoinValue/index.tsx index afda6213..e75258a6 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/EthValue/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/NativeCoinValue/index.tsx @@ -14,15 +14,20 @@ import Row from 'src/components/layout/Row' import { isPayable } from 'src/logic/contractInteraction/sources/ABIService' import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' import { safeSelector } from 'src/logic/safe/store/selectors' +import { getNetworkInfo } from 'src/config' const useStyles = makeStyles(styles) -interface EthValueProps { - onSetMax: (ethBalance: string) => void +interface NativeCoinValueProps { + onSetMax: (nativeCoinBalance: string) => void } -const EthValue = ({ onSetMax }: EthValueProps): React.ReactElement | null => { + +const { nativeCoin } = getNetworkInfo() + +export const NativeCoinValue = ({ onSetMax }: NativeCoinValueProps): React.ReactElement | null => { const classes = useStyles() const { ethBalance } = useSelector(safeSelector) || {} + const { input: { value: method }, } = useField('selectedMethod', { subscription: { value: true } }) @@ -52,7 +57,7 @@ const EthValue = ({ onSetMax }: EthValueProps): React.ReactElement | null => { component={TextField} disabled={disabled} inputAdornment={{ - endAdornment: ETH, + endAdornment: {nativeCoin.name}, disabled, }} name="value" @@ -66,5 +71,3 @@ const EthValue = ({ onSetMax }: EthValueProps): React.ReactElement | null => { ) } - -export default EthValue diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx index 5d2d1c10..350d30d7 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx @@ -2,7 +2,8 @@ import { makeStyles } from '@material-ui/core/styles' import { useSnackbar } from 'notistack' import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' - +import { fromTokenUnit, toTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' +import { getNetworkInfo } from 'src/config' import AddressInfo from 'src/components/AddressInfo' import Block from 'src/components/layout/Block' import Button from 'src/components/layout/Button' @@ -16,7 +17,6 @@ import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew' import { formatAmount } from 'src/logic/tokens/utils/formatAmount' import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers' -import { getWeb3 } from 'src/logic/wallets/getWeb3' import { styles } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/style' import Header from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Header' import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' @@ -40,23 +40,23 @@ type Props = { tx: TransactionReviewType } +const { nativeCoin } = getNetworkInfo() + const ContractInteractionReview = ({ onClose, onPrev, tx }: Props): React.ReactElement => { const { enqueueSnackbar, closeSnackbar } = useSnackbar() const classes = useStyles() const dispatch = useDispatch() const { address: safeAddress } = useSelector(safeSelector) || {} const [gasCosts, setGasCosts] = useState('< 0.001') - useEffect(() => { let isCurrent = true const estimateGas = async (): Promise => { - const { fromWei, toBN } = getWeb3().utils const txData = tx.data ? tx.data.trim() : '' const estimatedGasCosts = await estimateTxGasCosts(safeAddress as string, tx.contractAddress as string, txData) - const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether') - const formattedGasCosts = formatAmount(gasCostsAsEth) + const gasCosts = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals) + const formattedGasCosts = formatAmount(gasCosts) if (isCurrent) { setGasCosts(formattedGasCosts) @@ -71,11 +71,9 @@ const ContractInteractionReview = ({ onClose, onPrev, tx }: Props): React.ReactE }, [safeAddress, tx.contractAddress, tx.data]) const submitTx = async () => { - const web3 = getWeb3() const txRecipient = tx.contractAddress const txData = tx.data ? tx.data.trim() : '' - const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : '0' - + const txValue = tx.value ? toTokenUnit(tx.value, nativeCoin.decimals) : '0' dispatch( createTransaction({ safeAddress, @@ -117,7 +115,7 @@ const ContractInteractionReview = ({ onClose, onPrev, tx }: Props): React.ReactE {tx.value || 0} - {' ETH'} + {' ' + nativeCoin.name} @@ -165,7 +163,7 @@ const ContractInteractionReview = ({ onClose, onPrev, tx }: Props): React.ReactE - {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`} + {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/index.tsx index b47b8f2f..95273241 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/index.tsx @@ -3,10 +3,8 @@ import { makeStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' - -import ArrowDown from '../../assets/arrow-down.svg' - -import { styles } from './style' +import { fromTokenUnit, toTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' +import { getNetworkInfo } from 'src/config' import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' @@ -18,17 +16,20 @@ import Hairline from 'src/components/layout/Hairline' import Img from 'src/components/layout/Img' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' +import createTransaction from 'src/logic/safe/store/actions/createTransaction' +import { safeSelector } from 'src/logic/safe/store/selectors' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew' import { formatAmount } from 'src/logic/tokens/utils/formatAmount' import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers' -import { getWeb3 } from 'src/logic/wallets/getWeb3' import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo' import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' -import createTransaction from 'src/logic/safe/store/actions/createTransaction' -import { safeSelector } from 'src/logic/safe/store/selectors' import { sm } from 'src/theme/variables' +import ArrowDown from '../../assets/arrow-down.svg' + +import { styles } from './style' + type Props = { onClose: () => void onPrev: () => void @@ -37,22 +38,22 @@ type Props = { const useStyles = makeStyles(styles) +const { nativeCoin } = getNetworkInfo() + const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => { const classes = useStyles() const dispatch = useDispatch() const { address: safeAddress } = useSelector(safeSelector) || {} const [gasCosts, setGasCosts] = useState('< 0.001') - useEffect(() => { let isCurrent = true const estimateGas = async () => { - const { fromWei, toBN } = getWeb3().utils const txData = tx.data ? tx.data.trim() : '' const estimatedGasCosts = await estimateTxGasCosts(safeAddress as string, tx.contractAddress as string, txData) - const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether') - const formattedGasCosts = formatAmount(gasCostsAsEth) + const gasCosts = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals) + const formattedGasCosts = formatAmount(gasCosts) if (isCurrent) { setGasCosts(formattedGasCosts) @@ -67,10 +68,9 @@ const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => { }, [safeAddress, tx.data, tx.contractAddress]) const submitTx = async (): Promise => { - const web3 = getWeb3() const txRecipient = tx.contractAddress const txData = tx.data ? tx.data.trim() : '' - const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : '0' + const txValue = tx.value ? toTokenUnit(tx.value, nativeCoin.decimals) : '0' dispatch( createTransaction({ @@ -122,7 +122,7 @@ const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => { {tx.contractAddress} - + @@ -135,7 +135,7 @@ const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => { Ether {tx.value || 0} - {' ETH'} + {' ' + nativeCoin.name} @@ -152,7 +152,7 @@ const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => { - {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`} + {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/SendCustomTx/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/SendCustomTx/index.tsx index 99bbd100..e99edda8 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/SendCustomTx/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/SendCustomTx/index.tsx @@ -1,25 +1,20 @@ import IconButton from '@material-ui/core/IconButton' import InputAdornment from '@material-ui/core/InputAdornment' -import Switch from '@material-ui/core/Switch' import { makeStyles } from '@material-ui/core/styles' +import Switch from '@material-ui/core/Switch' import Close from '@material-ui/icons/Close' import React, { useState } from 'react' import { useSelector } from 'react-redux' -import ArrowDown from '../../assets/arrow-down.svg' - -import { styles } from './style' - import QRIcon from 'src/assets/icons/qrcode.svg' import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' -import Identicon from 'src/components/Identicon' -import ScanQRModal from 'src/components/ScanQRModal' import Field from 'src/components/forms/Field' import GnoForm from 'src/components/forms/GnoForm' -import TextField from 'src/components/forms/TextField' import TextareaField from 'src/components/forms/TextareaField' -import { composeValidators, maxValue, mustBeFloat, minValue } from 'src/components/forms/validator' +import TextField from 'src/components/forms/TextField' +import { composeValidators, maxValue, minValue, mustBeFloat } from 'src/components/forms/validator' +import Identicon from 'src/components/Identicon' import Block from 'src/components/layout/Block' import Button from 'src/components/layout/Button' import ButtonLink from 'src/components/layout/ButtonLink' @@ -28,11 +23,17 @@ import Hairline from 'src/components/layout/Hairline' import Img from 'src/components/layout/Img' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' +import ScanQRModal from 'src/components/ScanQRModal' +import { safeSelector } from 'src/logic/safe/store/selectors' import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo' import AddressBookInput from 'src/routes/safe/components/Balances/SendModal/screens/AddressBookInput' -import { safeSelector } from 'src/logic/safe/store/selectors' import { sm } from 'src/theme/variables' +import ArrowDown from '../../assets/arrow-down.svg' + +import { styles } from './style' +import { getNetworkInfo } from 'src/config' + export interface CreatedTx { contractAddress: string data: string @@ -50,6 +51,8 @@ type Props = { const useStyles = makeStyles(styles) +const { nativeCoin } = getNetworkInfo() + const SendCustomTx: React.FC = ({ initialValues, onClose, onNext, contractAddress, switchMethod, isABI }) => { const classes = useStyles() const { ethBalance } = useSelector(safeSelector) || {} @@ -177,7 +180,7 @@ const SendCustomTx: React.FC = ({ initialValues, onClose, onNext, contrac - + @@ -224,7 +227,7 @@ const SendCustomTx: React.FC = ({ initialValues, onClose, onNext, contrac ETH, + endAdornment: {nativeCoin.name}, }} name="value" placeholder="Value*" diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx index 31d28b3f..e1c4d55e 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/index.tsx @@ -12,15 +12,15 @@ import Paragraph from 'src/components/layout/Paragraph' import Buttons from './Buttons' import ContractABI from './ContractABI' import EthAddressInput from './EthAddressInput' -import EthValue from './EthValue' import FormDivisor from './FormDivisor' import FormErrorMessage from './FormErrorMessage' import Header from './Header' import MethodsDropdown from './MethodsDropdown' import RenderInputParams from './RenderInputParams' import RenderOutputParams from './RenderOutputParams' -import { abiExtractor, createTxObject, formMutators, handleSubmitError, isReadMethod, ensResolver } from './utils' +import { createTxObject, formMutators, handleSubmitError, isReadMethod, ensResolver } from './utils' import { TransactionReviewType } from './Review' +import { NativeCoinValue } from './NativeCoinValue' const useStyles = makeStyles(styles) @@ -92,7 +92,7 @@ const ContractInteraction: React.FC = ({
= ({ /> - + diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts index 52922fd0..c30e1eaa 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils/index.ts @@ -2,9 +2,6 @@ import { FORM_ERROR, Mutator, SubmissionErrors } from 'final-form' import createDecorator from 'final-form-calculate' import { ContractSendMethod } from 'web3-eth-contract' -import { mustBeEthereumAddress, mustBeEthereumContractAddress } from 'src/components/forms/validator' -import { getNetwork } from 'src/config' -import { getConfiguredSource } from 'src/logic/contractInteraction/sources' import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService' import { getAddressFromENS, getWeb3 } from 'src/logic/wallets/getWeb3' import { TransactionReviewType } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review' @@ -12,24 +9,6 @@ import { isValidEnsName } from 'src/logic/wallets/ethAddresses' export const NO_CONTRACT = 'no contract' -export const abiExtractor = createDecorator({ - field: 'contractAddress', - updates: { - abi: async (contractAddress) => { - if ( - !contractAddress || - mustBeEthereumAddress(contractAddress) || - (await mustBeEthereumContractAddress(contractAddress)) - ) { - return - } - const network = getNetwork() - const source = getConfiguredSource() - return source.getContractABI(contractAddress, network) - }, - }, -}) - export const ensResolver = createDecorator({ field: 'contractAddress', updates: { @@ -40,12 +19,12 @@ export const ensResolver = createDecorator({ if (resolvedAddress) { return resolvedAddress } + + return contractAddress } catch (e) { console.error(e.message) return contractAddress } - - return contractAddress }, }, }) @@ -71,6 +50,9 @@ export const formMutators: Record { utils.changeValue(state, 'callResults', () => args[0]) }, + setAbiValue: (args, state, utils) => { + utils.changeValue(state, 'abi', () => args[0]) + }, } export const isAddress = (type: string): boolean => type.indexOf('address') === 0 diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/index.tsx index 1b7cb68d..96a393bf 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/index.tsx @@ -4,11 +4,8 @@ import Close from '@material-ui/icons/Close' import { withSnackbar } from 'notistack' import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' - -import ArrowDown from '../assets/arrow-down.svg' - -import { styles } from './style' - +import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' +import { getNetworkInfo } from 'src/config' import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' import Identicon from 'src/components/Identicon' @@ -20,6 +17,8 @@ import Img from 'src/components/layout/Img' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' import { nftTokensSelector } from 'src/logic/collectibles/store/selectors' +import createTransaction from 'src/logic/safe/store/actions/createTransaction' +import { safeSelector } from 'src/logic/safe/store/selectors' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew' import { @@ -29,14 +28,17 @@ import { } from 'src/logic/tokens/store/actions/fetchTokens' import { formatAmount } from 'src/logic/tokens/utils/formatAmount' import { SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH } from 'src/logic/tokens/utils/tokenHelpers' -import { getWeb3 } from 'src/logic/wallets/getWeb3' import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo' import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' -import createTransaction from 'src/logic/safe/store/actions/createTransaction' -import { safeSelector } from 'src/logic/safe/store/selectors' import { sm } from 'src/theme/variables' import { textShortener } from 'src/utils/strings' +import ArrowDown from '../assets/arrow-down.svg' + +import { styles } from './style' + +const { nativeCoin } = getNetworkInfo() + const useStyles = makeStyles(styles as any) const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { @@ -55,8 +57,6 @@ const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx let isCurrent = true const estimateGas = async () => { - const { fromWei, toBN } = getWeb3().utils - const supportsSafeTransfer = await containsMethodByHash(tx.assetAddress, SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH) const methodToCall = supportsSafeTransfer ? `0x${SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH}` : 'transfer' const transferParams = [tx.recipientAddress, tx.nftTokenId] @@ -67,8 +67,8 @@ const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx const txData = tokenInstance.contract.methods[methodToCall](...params).encodeABI() const estimatedGasCosts = await estimateTxGasCosts(safeAddress as string, tx.recipientAddress, txData) - const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether') - const formattedGasCosts = formatAmount(gasCostsAsEth) + const gasCosts = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals) + const formattedGasCosts = formatAmount(gasCosts) if (isCurrent) { setGasCosts(formattedGasCosts) @@ -135,7 +135,7 @@ const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx {tx.recipientAddress} - + @@ -154,7 +154,7 @@ const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx )} - {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`} + {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx index 0ee764bb..1328cdcf 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx @@ -3,12 +3,10 @@ import { makeStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' import { BigNumber } from 'bignumber.js' import { withSnackbar } from 'notistack' -import React, { useEffect, useState, useMemo } from 'react' +import React, { useEffect, useMemo, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' - -import ArrowDown from '../assets/arrow-down.svg' - -import { styles } from './style' +import { toTokenUnit, fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' +import { getNetworkInfo } from 'src/config' import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' @@ -20,22 +18,26 @@ import Hairline from 'src/components/layout/Hairline' import Img from 'src/components/layout/Img' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' +import createTransaction from 'src/logic/safe/store/actions/createTransaction' +import { safeSelector } from 'src/logic/safe/store/selectors' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew' import { getHumanFriendlyToken } from 'src/logic/tokens/store/actions/fetchTokens' import { formatAmount } from 'src/logic/tokens/utils/formatAmount' -import { ETH_ADDRESS } from 'src/logic/tokens/utils/tokenHelpers' import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' -import { getWeb3 } from 'src/logic/wallets/getWeb3' import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo' import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' import { extendedSafeTokensSelector } from 'src/routes/safe/container/selector' -import createTransaction from 'src/logic/safe/store/actions/createTransaction' -import { safeSelector } from 'src/logic/safe/store/selectors' import { sm } from 'src/theme/variables' +import ArrowDown from '../assets/arrow-down.svg' + +import { styles } from './style' + const useStyles = makeStyles(styles as any) +const { nativeCoin } = getNetworkInfo() + const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { const classes = useStyles() const dispatch = useDispatch() @@ -45,15 +47,13 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { const [data, setData] = useState('') const txToken = useMemo(() => tokens.find((token) => token.address === tx.token), [tokens, tx.token]) - const isSendingETH = txToken?.address === ETH_ADDRESS + const isSendingETH = txToken?.address === nativeCoin.address const txRecipient = isSendingETH ? tx.recipientAddress : txToken?.address useEffect(() => { let isCurrent = true const estimateGas = async () => { - const { fromWei, toBN } = getWeb3().utils - if (!txToken) { return } @@ -70,8 +70,8 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { } const estimatedGasCosts = await estimateTxGasCosts(safeAddress as string, txRecipient, txData) - const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether') - const formattedGasCosts = formatAmount(gasCostsAsEth) + const gasCosts = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals) + const formattedGasCosts = formatAmount(gasCosts) if (isCurrent) { setGasCosts(formattedGasCosts) @@ -87,11 +87,10 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { }, [isSendingETH, safeAddress, tx.amount, tx.recipientAddress, txRecipient, txToken]) const submitTx = async () => { - const web3 = getWeb3() // txAmount should be 0 if we send tokens // the real value is encoded in txData and will be used by the contract // if txAmount > 0 it would send ETH from the Safe - const txAmount = isSendingETH ? web3.utils.toWei(tx.amount, 'ether') : '0' + const txAmount = isSendingETH ? toTokenUnit(tx.amount, nativeCoin.decimals) : '0' dispatch( createTransaction({ @@ -149,7 +148,7 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { {tx.recipientAddress} - + @@ -171,7 +170,7 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { - {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`} + {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/index.tsx index 14c54f06..833f4e99 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/index.tsx @@ -4,22 +4,18 @@ import Close from '@material-ui/icons/Close' import React, { useState } from 'react' import { useSelector } from 'react-redux' -import ArrowDown from '../assets/arrow-down.svg' - -import { styles } from './style' - import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' -import Identicon from 'src/components/Identicon' -import { ScanQRWrapper } from 'src/components/ScanQRModal/ScanQRWrapper' -import WhenFieldChanges from 'src/components/WhenFieldChanges' import GnoForm from 'src/components/forms/GnoForm' +import Identicon from 'src/components/Identicon' import Block from 'src/components/layout/Block' import Button from 'src/components/layout/Button' import Col from 'src/components/layout/Col' import Hairline from 'src/components/layout/Hairline' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' +import { ScanQRWrapper } from 'src/components/ScanQRModal/ScanQRWrapper' +import WhenFieldChanges from 'src/components/WhenFieldChanges' import { addressBookSelector } from 'src/logic/addressBook/store/selectors' import { getNameFromAddressBook } from 'src/logic/addressBook/utils' import { nftTokensSelector, safeActiveSelectorMap } from 'src/logic/collectibles/store/selectors' @@ -29,6 +25,10 @@ import CollectibleSelectField from 'src/routes/safe/components/Balances/SendModa import TokenSelectField from 'src/routes/safe/components/Balances/SendModal/screens/SendCollectible/TokenSelectField' import { sm } from 'src/theme/variables' +import ArrowDown from '../assets/arrow-down.svg' + +import { styles } from './style' + const formMutators = { setMax: (args, state, utils) => { utils.changeValue(state, 'amount', () => args[0]) @@ -171,7 +171,7 @@ const SendCollectible = ({ - + diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx index 27db52e9..14bdc61f 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx @@ -2,22 +2,18 @@ import IconButton from '@material-ui/core/IconButton' import InputAdornment from '@material-ui/core/InputAdornment' import { makeStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' +import { getNetworkInfo } from 'src/config' import React, { useState } from 'react' import { OnChange } from 'react-final-form-listeners' import { useSelector } from 'react-redux' -import ArrowDown from '../assets/arrow-down.svg' - -import { styles } from './style' - import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' -import Identicon from 'src/components/Identicon' -import { ScanQRWrapper } from 'src/components/ScanQRModal/ScanQRWrapper' import Field from 'src/components/forms/Field' import GnoForm from 'src/components/forms/GnoForm' import TextField from 'src/components/forms/TextField' -import { composeValidators, minValue, maxValue, mustBeFloat, required } from 'src/components/forms/validator' +import { composeValidators, maxValue, minValue, mustBeFloat, required } from 'src/components/forms/validator' +import Identicon from 'src/components/Identicon' import Block from 'src/components/layout/Block' import Button from 'src/components/layout/Button' import ButtonLink from 'src/components/layout/ButtonLink' @@ -25,6 +21,7 @@ import Col from 'src/components/layout/Col' import Hairline from 'src/components/layout/Hairline' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' +import { ScanQRWrapper } from 'src/components/ScanQRModal/ScanQRWrapper' import { addressBookSelector } from 'src/logic/addressBook/store/selectors' import { getNameFromAddressBook } from 'src/logic/addressBook/utils' @@ -34,6 +31,10 @@ import TokenSelectField from 'src/routes/safe/components/Balances/SendModal/scre import { extendedSafeTokensSelector } from 'src/routes/safe/container/selector' import { sm } from 'src/theme/variables' +import ArrowDown from '../assets/arrow-down.svg' + +import { styles } from './style' + const formMutators = { setMax: (args, state, utils) => { utils.changeValue(state, 'amount', () => args[0]) @@ -60,6 +61,8 @@ type SendFundsProps = { selectedToken: string } +const { nativeCoin } = getNetworkInfo() + const SendFunds = ({ initialValues, onClose, @@ -184,7 +187,7 @@ const SendFunds = ({ - + @@ -211,7 +214,7 @@ const SendFunds = ({ diff --git a/src/routes/safe/components/Balances/Tokens/screens/AssetsList/AssetRow.tsx b/src/routes/safe/components/Balances/Tokens/screens/AssetsList/AssetRow.tsx index 4d0af38c..9a927ad2 100644 --- a/src/routes/safe/components/Balances/Tokens/screens/AssetsList/AssetRow.tsx +++ b/src/routes/safe/components/Balances/Tokens/screens/AssetsList/AssetRow.tsx @@ -3,32 +3,32 @@ import ListItemIcon from '@material-ui/core/ListItemIcon' import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction' import ListItemText from '@material-ui/core/ListItemText' import Switch from '@material-ui/core/Switch' -import { withStyles } from '@material-ui/core/styles' import React, { memo } from 'react' -import { styles } from './style' - +import { useStyles } from 'src/routes/safe/components/Balances/Tokens/screens/TokenList/style' import Img from 'src/components/layout/Img' -import { ETH_ADDRESS } from 'src/logic/tokens/utils/tokenHelpers' -import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' +import { getNetworkInfo } from 'src/config' +import { setCollectibleImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' export const TOGGLE_ASSET_TEST_ID = 'toggle-asset-btn' -// eslint-disable-next-line react/display-name -const AssetRow = memo(({ classes, data, index, style }: any) => { +const { nativeCoin } = getNetworkInfo() + +const AssetRow = memo(({ data, index, style }: any) => { + const classes = useStyles() const { activeAssetsAddresses, assets, onSwitch } = data - const asset = assets.get(index) + const asset = assets[index] const { address, image, name, symbol } = asset - const isActive = activeAssetsAddresses.has(asset.address) + const isActive = activeAssetsAddresses.includes(asset.address) return (
- {name} + {name} - {address !== ETH_ADDRESS && ( + {address !== nativeCoin.address && ( { ) }) -export default withStyles(styles as any)(AssetRow) +AssetRow.displayName = 'AssetRow' + +export default AssetRow diff --git a/src/routes/safe/components/Balances/Tokens/screens/AssetsList/index.tsx b/src/routes/safe/components/Balances/Tokens/screens/AssetsList/index.tsx index 1e87da0a..fbe2f64b 100644 --- a/src/routes/safe/components/Balances/Tokens/screens/AssetsList/index.tsx +++ b/src/routes/safe/components/Balances/Tokens/screens/AssetsList/index.tsx @@ -1,14 +1,13 @@ -import CircularProgress from '@material-ui/core/CircularProgress' import MuiList from '@material-ui/core/List' -import { makeStyles } from '@material-ui/core/styles' import Search from '@material-ui/icons/Search' import cn from 'classnames' import SearchBar from 'material-ui-search-bar' import React, { useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { FixedSizeList } from 'react-window' +import Paragraph from 'src/components/layout/Paragraph' -import { styles } from './style' +import { useStyles } from './style' import Spacer from 'src/components/Spacer' import Block from 'src/components/layout/Block' @@ -25,7 +24,6 @@ import { safeBlacklistedAssetsSelector, safeParamAddressFromStateSelector, } from 'src/logic/safe/store/selectors' -const useStyles = makeStyles(styles as any) export const ADD_CUSTOM_ASSET_BUTTON_TEST_ID = 'add-custom-asset-btn' @@ -130,14 +128,14 @@ const AssetsList = (props) => { {!nftAssetsList.length && ( - + No collectibles available )} {nftAssetsList.length > 0 && ( ({ - root: { - minHeight: '52px', - }, - search: { - color: secondaryText, - paddingLeft: sm, - }, - padding: { - padding: `0 ${md}`, - }, - add: { - fontSize: '11px', - fontWeight: 'normal', - paddingRight: md, - paddingLeft: md, - }, - addBtnLabel: { - fontSize: mediumFontSize, - }, - actions: { - height: '50px', - }, - list: { - overflow: 'hidden', - overflowY: 'scroll', - padding: 0, - height: '100%', - }, - tokenIcon: { - marginRight: sm, - height: '28px', - width: '28px', - }, - searchInput: { - backgroundColor: 'transparent', - lineHeight: 'initial', - fontSize: '13px', - padding: 0, - '& > input::placeholder': { - letterSpacing: '-0.5px', +export const useStyles = makeStyles( + createStyles({ + root: { + minHeight: '52px', + }, + search: { + color: secondaryText, + paddingLeft: sm, + }, + padding: { + padding: `0 ${md}`, + }, + add: { + fontSize: '11px', + fontWeight: 'normal', + paddingRight: md, + paddingLeft: md, + }, + addBtnLabel: { fontSize: mediumFontSize, - color: 'black', }, - '& > input': { + actions: { + height: '50px', + }, + list: { + overflow: 'hidden', + overflowY: 'scroll', + padding: 0, + height: '100%', + }, + tokenIcon: { + marginRight: sm, + height: '28px', + width: '28px', + }, + searchInput: { + backgroundColor: 'transparent', + lineHeight: 'initial', + fontSize: '13px', + padding: 0, + '& > input::placeholder': { + letterSpacing: '-0.5px', + fontSize: mediumFontSize, + color: 'black', + }, + '& > input': { + letterSpacing: '-0.5px', + }, + }, + progressContainer: { + width: '100%', + height: '100%', + alignItems: 'center', + }, + searchContainer: { + width: '180px', + marginLeft: xs, + marginRight: xs, + }, + searchRoot: { letterSpacing: '-0.5px', + fontSize: '13px', + border: 'none', + boxShadow: 'none', + '& > button': { + display: 'none', + }, }, - }, - progressContainer: { - width: '100%', - height: '100%', - alignItems: 'center', - }, - searchContainer: { - width: '180px', - marginLeft: xs, - marginRight: xs, - }, - searchRoot: { - letterSpacing: '-0.5px', - fontSize: '13px', - border: 'none', - boxShadow: 'none', - '& > button': { - display: 'none', + searchIcon: { + '&:hover': { + backgroundColor: 'transparent !important', + }, }, - }, - searchIcon: { - '&:hover': { - backgroundColor: 'transparent !important', - }, - }, -}) + }), +) diff --git a/src/routes/safe/components/Balances/Tokens/screens/TokenList/TokenRow.tsx b/src/routes/safe/components/Balances/Tokens/screens/TokenList/TokenRow.tsx index 01b2501c..9b73f079 100644 --- a/src/routes/safe/components/Balances/Tokens/screens/TokenList/TokenRow.tsx +++ b/src/routes/safe/components/Balances/Tokens/screens/TokenList/TokenRow.tsx @@ -3,22 +3,33 @@ import ListItemIcon from '@material-ui/core/ListItemIcon' import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction' import ListItemText from '@material-ui/core/ListItemText' import Switch from '@material-ui/core/Switch' -import { withStyles } from '@material-ui/core/styles' -import React, { memo } from 'react' - -import { styles } from './style' +import React, { CSSProperties, memo, ReactElement } from 'react' +import { useStyles } from './style' import Img from 'src/components/layout/Img' - -import { ETH_ADDRESS } from 'src/logic/tokens/utils/tokenHelpers' +import { getNetworkInfo } from 'src/config' import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' +import { ItemData } from 'src/routes/safe/components/Balances/Tokens/screens/TokenList/index' export const TOGGLE_TOKEN_TEST_ID = 'toggle-token-btn' -// eslint-disable-next-line react/display-name -const TokenRow = memo(({ classes, data, index, style }: any) => { +interface TokenRowProps { + data: ItemData + index: number + style: CSSProperties +} + +const { nativeCoin } = getNetworkInfo() + +const TokenRow = memo(({ data, index, style }: TokenRowProps): ReactElement | null => { + const classes = useStyles() const { activeTokensAddresses, onSwitch, tokens } = data const token = tokens.get(index) + + if (!token) { + return null + } + const isActive = activeTokensAddresses.has(token.address) return ( @@ -28,13 +39,9 @@ const TokenRow = memo(({ classes, data, index, style }: any) => { {token.name} - {token.address !== ETH_ADDRESS && ( - - + {token.address !== nativeCoin.address && ( + + )} @@ -42,4 +49,6 @@ const TokenRow = memo(({ classes, data, index, style }: any) => { ) }) -export default withStyles(styles as any)(TokenRow) +TokenRow.displayName = 'TokenRow' + +export default TokenRow diff --git a/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.tsx b/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.tsx index 999dd941..a8099cfe 100644 --- a/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.tsx +++ b/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.tsx @@ -1,6 +1,5 @@ import CircularProgress from '@material-ui/core/CircularProgress' import MuiList from '@material-ui/core/List' -import { makeStyles } from '@material-ui/core/styles' import Search from '@material-ui/icons/Search' import cn from 'classnames' import { List, Set } from 'immutable' @@ -9,7 +8,7 @@ import React, { useState } from 'react' import { FixedSizeList } from 'react-window' import TokenRow from './TokenRow' -import { styles } from './style' +import { useStyles } from './style' import Spacer from 'src/components/Spacer' import Block from 'src/components/layout/Block' @@ -32,8 +31,6 @@ const filterBy = (filter: string, tokens: List): List => token.name.toLowerCase().includes(filter.toLowerCase()), ) -const useStyles = makeStyles(styles) - type Props = { setActiveScreen: (newScreen: string) => void tokens: List @@ -42,6 +39,12 @@ type Props = { safeAddress: string } +export type ItemData = { + tokens: List + activeTokensAddresses: Set + onSwitch: (token: Token) => () => void +} + export const TokenList = (props: Props): React.ReactElement => { const classes = useStyles() const { setActiveScreen, tokens, activeTokens, blacklistedTokens, safeAddress } = props @@ -85,16 +88,11 @@ export const TokenList = (props: Props): React.ReactElement => { dispatch(updateBlacklistedTokens(safeAddress, newBlacklistedTokensAddresses)) } - const createItemData = ( - tokens: List, - activeTokensAddresses: Set, - ): { tokens: List; activeTokensAddresses: Set; onSwitch: (token: Token) => void } => { - return { - tokens, - activeTokensAddresses, - onSwitch: onSwitch, - } - } + const createItemData = (tokens: List, activeTokensAddresses: Set): ItemData => ({ + tokens, + activeTokensAddresses, + onSwitch, + }) const switchToAddCustomTokenScreen = () => setActiveScreen('addCustomToken') diff --git a/src/routes/safe/components/Balances/Tokens/screens/TokenList/style.ts b/src/routes/safe/components/Balances/Tokens/screens/TokenList/style.ts index ac7f8c34..18631a78 100644 --- a/src/routes/safe/components/Balances/Tokens/screens/TokenList/style.ts +++ b/src/routes/safe/components/Balances/Tokens/screens/TokenList/style.ts @@ -1,84 +1,87 @@ -import { border, md, mediumFontSize, secondaryText, sm, xs } from 'src/theme/variables' -import { createStyles } from '@material-ui/core' +import { createStyles, makeStyles } from '@material-ui/core' -export const styles = createStyles({ - root: { - minHeight: '52px', - }, - search: { - color: secondaryText, - paddingLeft: sm, - }, - padding: { - padding: `0 ${md}`, - }, - add: { - fontSize: '11px', - fontWeight: 'normal', - paddingRight: md, - paddingLeft: md, - }, - addBtnLabel: { - fontSize: mediumFontSize, - }, - actions: { - height: '50px', - }, - list: { - overflow: 'hidden', - overflowY: 'scroll', - padding: 0, - height: '100%', - }, - token: { - minHeight: '50px', - borderBottom: `1px solid ${border}`, - }, - tokenRoot: { - paddingTop: 0, - paddingBottom: 0, - }, - searchInput: { - backgroundColor: 'transparent', - lineHeight: 'initial', - fontSize: '13px', - padding: 0, - '& > input::placeholder': { - letterSpacing: '-0.5px', +import { border, md, mediumFontSize, secondaryText, sm, xs } from 'src/theme/variables' + +export const useStyles = makeStyles( + createStyles({ + root: { + minHeight: '52px', + }, + search: { + color: secondaryText, + paddingLeft: sm, + }, + padding: { + padding: `0 ${md}`, + }, + add: { + fontSize: '11px', + fontWeight: 'normal', + paddingRight: md, + paddingLeft: md, + }, + addBtnLabel: { fontSize: mediumFontSize, - color: 'black', }, - '& > input': { + actions: { + height: '50px', + }, + list: { + overflow: 'hidden', + overflowY: 'scroll', + padding: 0, + height: '100%', + }, + token: { + minHeight: '50px', + borderBottom: `1px solid ${border}`, + }, + tokenRoot: { + paddingTop: 0, + paddingBottom: 0, + }, + searchInput: { + backgroundColor: 'transparent', + lineHeight: 'initial', + fontSize: '13px', + padding: 0, + '& > input::placeholder': { + letterSpacing: '-0.5px', + fontSize: mediumFontSize, + color: 'black', + }, + '& > input': { + letterSpacing: '-0.5px', + }, + }, + tokenIcon: { + marginRight: md, + height: '28px', + width: '28px', + }, + progressContainer: { + width: '100%', + height: '100%', + alignItems: 'center', + }, + searchContainer: { + width: '180px', + marginLeft: xs, + marginRight: xs, + }, + searchRoot: { letterSpacing: '-0.5px', + fontSize: '13px', + border: 'none', + boxShadow: 'none', + '& > button': { + display: 'none', + }, }, - }, - tokenIcon: { - marginRight: md, - height: '28px', - width: '28px', - }, - progressContainer: { - width: '100%', - height: '100%', - alignItems: 'center', - }, - searchContainer: { - width: '180px', - marginLeft: xs, - marginRight: xs, - }, - searchRoot: { - letterSpacing: '-0.5px', - fontSize: '13px', - border: 'none', - boxShadow: 'none', - '& > button': { - display: 'none', + searchIcon: { + '&:hover': { + backgroundColor: 'transparent !important', + }, }, - }, - searchIcon: { - '&:hover': { - backgroundColor: 'transparent !important', - }, - }, -}) + }), +) diff --git a/src/routes/safe/components/Balances/dataFetcher.ts b/src/routes/safe/components/Balances/dataFetcher.ts index e19c215a..0308f30f 100644 --- a/src/routes/safe/components/Balances/dataFetcher.ts +++ b/src/routes/safe/components/Balances/dataFetcher.ts @@ -1,9 +1,8 @@ import { BigNumber } from 'bignumber.js' import { List } from 'immutable' - +import { getNetworkInfo } from 'src/config' import { FIXED } from 'src/components/Table/sorting' import { formatAmountInUsFormat } from 'src/logic/tokens/utils/formatAmount' -import { ETH_ADDRESS } from 'src/logic/tokens/utils/tokenHelpers' import { TableColumn } from 'src/components/Table/types.d' import { AVAILABLE_CURRENCIES, BalanceCurrencyList } from 'src/logic/currencyValues/store/model/currencyValues' import { Token } from 'src/logic/tokens/store/model/token' @@ -23,7 +22,8 @@ const getTokenPriceInCurrency = ( } const currencyValue = currencyValues?.find(({ tokenAddress }) => { - if (token.address === ETH_ADDRESS && !tokenAddress) { + const { nativeCoin } = getNetworkInfo() + if (token.address === nativeCoin.address && !tokenAddress) { return true } @@ -54,8 +54,9 @@ export const getBalanceData = ( currencySelected?: AVAILABLE_CURRENCIES, currencyValues?: BalanceCurrencyList, currencyRate?: number, -): List => - activeTokens.map((token) => ({ +): List => { + const { nativeCoin } = getNetworkInfo() + return activeTokens.map((token) => ({ [BALANCE_TABLE_ASSET_ID]: { name: token.name, logoUri: token.logoUri, @@ -65,9 +66,10 @@ export const getBalanceData = ( assetOrder: token.name, [BALANCE_TABLE_BALANCE_ID]: `${formatAmountInUsFormat(token.balance?.toString() || '0')} ${token.symbol}`, balanceOrder: Number(token.balance), - [FIXED]: token.symbol === 'ETH', + [FIXED]: token.symbol === nativeCoin.symbol, [BALANCE_TABLE_VALUE_ID]: getTokenPriceInCurrency(token, currencySelected, currencyValues, currencyRate), })) +} export const generateColumns = (): List => { const assetColumn: TableColumn = { diff --git a/src/routes/safe/components/Balances/index.tsx b/src/routes/safe/components/Balances/index.tsx index 1f5489c6..49bd4d8b 100644 --- a/src/routes/safe/components/Balances/index.tsx +++ b/src/routes/safe/components/Balances/index.tsx @@ -2,7 +2,7 @@ import { makeStyles } from '@material-ui/core/styles' import React, { useEffect, useState } from 'react' import { useSelector } from 'react-redux' -import Receive from 'src/components/App/ReceiveModal' +import ReceiveModal from 'src/components/App/ReceiveModal' import Tokens from './Tokens' import { styles } from './style' @@ -17,13 +17,14 @@ import SendModal from 'src/routes/safe/components/Balances/SendModal' import CurrencyDropdown from 'src/routes/safe/components/CurrencyDropdown' import { safeFeaturesEnabledSelector, - safeParamAddressFromStateSelector, safeNameSelector, + safeParamAddressFromStateSelector, } from 'src/logic/safe/store/selectors' import { wrapInSuspense } from 'src/utils/wrapInSuspense' import { useFetchTokens } from 'src/logic/safe/hooks/useFetchTokens' -import { Route, Switch, NavLink, Redirect } from 'react-router-dom' +import { NavLink, Redirect, Route, Switch } from 'react-router-dom' +import { FEATURES } from 'src/config/networks/network.d' const Collectibles = React.lazy(() => import('src/routes/safe/components/Balances/Collectibles')) const Coins = React.lazy(() => import('src/routes/safe/components/Balances/Coins')) @@ -53,12 +54,12 @@ const Balances = (): React.ReactElement => { const address = useSelector(safeParamAddressFromStateSelector) const featuresEnabled = useSelector(safeFeaturesEnabledSelector) - const safeName = useSelector(safeNameSelector) + const safeName = useSelector(safeNameSelector) ?? '' useFetchTokens(address as string) useEffect(() => { - const erc721Enabled = Boolean(featuresEnabled?.includes('ERC721')) + const erc721Enabled = Boolean(featuresEnabled?.includes(FEATURES.ERC721)) setState((prevState) => ({ ...prevState, @@ -229,7 +230,7 @@ const Balances = (): React.ReactElement => { paperClassName={receiveModal} title="Receive Tokens" > - onHide('Receive')} /> + onHide('Receive')} /> ) diff --git a/src/routes/safe/components/Balances/utils/index.ts b/src/routes/safe/components/Balances/utils/index.ts index 2fb9682f..f91f4984 100644 --- a/src/routes/safe/components/Balances/utils/index.ts +++ b/src/routes/safe/components/Balances/utils/index.ts @@ -1 +1,2 @@ -export * from './setTokenImgToPlaceholder' +export { setImageToPlaceholder } from './setTokenImgToPlaceholder' +export { setCollectibleImageToPlaceholder } from './setCollectibleImageToPlaceholder' diff --git a/src/routes/safe/components/Balances/utils/setCollectibleImageToPlaceholder.ts b/src/routes/safe/components/Balances/utils/setCollectibleImageToPlaceholder.ts new file mode 100644 index 00000000..31081dee --- /dev/null +++ b/src/routes/safe/components/Balances/utils/setCollectibleImageToPlaceholder.ts @@ -0,0 +1,8 @@ +import { SyntheticEvent } from 'react' + +import NFTIcon from 'src/routes/safe/components/Balances/assets/nft_icon.png' + +export const setCollectibleImageToPlaceholder = (error: SyntheticEvent): void => { + error.currentTarget.onerror = null + error.currentTarget.src = NFTIcon +} diff --git a/src/routes/safe/components/Balances/utils/setTokenImgToPlaceholder.ts b/src/routes/safe/components/Balances/utils/setTokenImgToPlaceholder.ts index 9d8b6519..eaa64a39 100644 --- a/src/routes/safe/components/Balances/utils/setTokenImgToPlaceholder.ts +++ b/src/routes/safe/components/Balances/utils/setTokenImgToPlaceholder.ts @@ -1,6 +1,8 @@ +import { SyntheticEvent } from 'react' + import TokenPlaceholder from 'src/routes/safe/components/Balances/assets/token_placeholder.svg' -export const setImageToPlaceholder = (e) => { - e.target.onerror = null - e.target.src = TokenPlaceholder +export const setImageToPlaceholder = (error: SyntheticEvent): void => { + error.currentTarget.onerror = null + error.currentTarget.src = TokenPlaceholder } diff --git a/src/routes/safe/components/Settings/Advanced/RemoveModuleModal.tsx b/src/routes/safe/components/Settings/Advanced/RemoveModuleModal.tsx index e630c45d..cfe23ac0 100644 --- a/src/routes/safe/components/Settings/Advanced/RemoveModuleModal.tsx +++ b/src/routes/safe/components/Settings/Advanced/RemoveModuleModal.tsx @@ -6,25 +6,25 @@ import OpenInNew from '@material-ui/icons/OpenInNew' import cn from 'classnames' import React from 'react' import { useDispatch, useSelector } from 'react-redux' -import styled from 'styled-components' - -import { styles } from './style' +import Identicon from 'src/components/Identicon' +import Block from 'src/components/layout/Block' +import Col from 'src/components/layout/Col' +import Hairline from 'src/components/layout/Hairline' +import Link from 'src/components/layout/Link' +import Paragraph from 'src/components/layout/Paragraph' +import Row from 'src/components/layout/Row' +import Modal from 'src/components/Modal' +import { getExplorerInfo } from 'src/config' +import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts' +import createTransaction from 'src/logic/safe/store/actions/createTransaction' import { ModulePair } from 'src/logic/safe/store/models/safe' import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' -import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts' -import createTransaction from 'src/logic/safe/store/actions/createTransaction' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' -import Modal from 'src/components/Modal' -import Row from 'src/components/layout/Row' -import Paragraph from 'src/components/layout/Paragraph' -import Hairline from 'src/components/layout/Hairline' -import Block from 'src/components/layout/Block' -import Col from 'src/components/layout/Col' -import Identicon from 'src/components/Identicon' -import Link from 'src/components/layout/Link' -import { getEtherScanLink } from 'src/logic/wallets/getWeb3' import { md, secondary } from 'src/theme/variables' +import styled from 'styled-components' + +import { styles } from './style' const useStyles = makeStyles(styles) @@ -49,6 +49,9 @@ const RemoveModuleModal = ({ onClose, selectedModule }: RemoveModuleModal): Reac const safeAddress = useSelector(safeParamAddressFromStateSelector) as string const dispatch = useDispatch() + const explorerInfo = getExplorerInfo(selectedModule[0]) + const { url } = explorerInfo() + const removeSelectedModule = async (): Promise => { try { const safeInstance = await getGnosisSafeInstanceAt(safeAddress) @@ -101,11 +104,7 @@ const RemoveModuleModal = ({ onClose, selectedModule }: RemoveModuleModal): Reac {selectedModule[0]} - + diff --git a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/Review/index.tsx b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/Review/index.tsx index dde8e118..b6670220 100644 --- a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/Review/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/Review/index.tsx @@ -4,9 +4,8 @@ import Close from '@material-ui/icons/Close' import classNames from 'classnames' import React, { useEffect, useState } from 'react' import { useSelector } from 'react-redux' - -import { styles } from './style' - +import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' +import { getNetworkInfo } from 'src/config' import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' import Identicon from 'src/components/Identicon' @@ -17,13 +16,16 @@ import Hairline from 'src/components/layout/Hairline' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts' +import { safeNameSelector, safeOwnersSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew' import { formatAmount } from 'src/logic/tokens/utils/formatAmount' -import { getWeb3 } from 'src/logic/wallets/getWeb3' -import { safeNameSelector, safeOwnersSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' + +import { styles } from './style' export const ADD_OWNER_SUBMIT_BTN_TEST_ID = 'add-owner-submit-btn' +const { nativeCoin } = getNetworkInfo() + const ReviewAddOwner = ({ classes, onClickBack, onClose, onSubmit, values }) => { const [gasCosts, setGasCosts] = useState('< 0.001') const safeAddress = useSelector(safeParamAddressFromStateSelector) as string @@ -32,15 +34,13 @@ const ReviewAddOwner = ({ classes, onClickBack, onClose, onSubmit, values }) => useEffect(() => { let isCurrent = true const estimateGas = async () => { - const web3 = getWeb3() - const { fromWei, toBN } = web3.utils const safeInstance = await getGnosisSafeInstanceAt(safeAddress) const txData = safeInstance.methods.addOwnerWithThreshold(values.ownerAddress, values.threshold).encodeABI() const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, txData) - const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether') - const formattedGasCosts = formatAmount(gasCostsAsEth) + const gasCosts = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals) + const formattedGasCosts = formatAmount(gasCosts) if (isCurrent) { setGasCosts(formattedGasCosts) } @@ -118,7 +118,7 @@ const ReviewAddOwner = ({ classes, onClickBack, onClose, onSubmit, values }) => {owner.address} - + @@ -146,7 +146,7 @@ const ReviewAddOwner = ({ classes, onClickBack, onClose, onSubmit, values }) => {values.ownerAddress} - + @@ -160,7 +160,7 @@ const ReviewAddOwner = ({ classes, onClickBack, onClose, onSubmit, values }) => You're about to create a transaction and will have to confirm it with your currently connected wallet.
- {`Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`} + {`Make sure you have ${gasCosts} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`}
diff --git a/src/routes/safe/components/Settings/ManageOwners/EditOwnerModal/index.tsx b/src/routes/safe/components/Settings/ManageOwners/EditOwnerModal/index.tsx index 7f6fbec5..6d750aed 100644 --- a/src/routes/safe/components/Settings/ManageOwners/EditOwnerModal/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/EditOwnerModal/index.tsx @@ -4,28 +4,28 @@ import Close from '@material-ui/icons/Close' import React from 'react' import { useDispatch, useSelector } from 'react-redux' -import { styles } from './style' - import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' -import Identicon from 'src/components/Identicon' -import Modal from 'src/components/Modal' import Field from 'src/components/forms/Field' import GnoForm from 'src/components/forms/GnoForm' import TextField from 'src/components/forms/TextField' import { composeValidators, minMaxLength, required } from 'src/components/forms/validator' +import Identicon from 'src/components/Identicon' import Block from 'src/components/layout/Block' import Button from 'src/components/layout/Button' import Hairline from 'src/components/layout/Hairline' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' +import Modal from 'src/components/Modal' import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook' +import { addOrUpdateAddressBookEntry } from 'src/logic/addressBook/store/actions/addOrUpdateAddressBookEntry' import { NOTIFICATIONS } from 'src/logic/notifications' +import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackbar' import editSafeOwner from 'src/logic/safe/store/actions/editSafeOwner' import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' import { sm } from 'src/theme/variables' -import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackbar' -import { addOrUpdateAddressBookEntry } from 'src/logic/addressBook/store/actions/addOrUpdateAddressBookEntry' + +import { styles } from './style' export const RENAME_OWNER_INPUT_TEST_ID = 'rename-owner-input' export const SAVE_OWNER_CHANGES_BTN_TEST_ID = 'save-owner-changes-btn' @@ -93,7 +93,7 @@ const EditOwnerComponent = ({ isOpen, onClose, ownerAddress, selectedOwnerName } {ownerAddress} - + diff --git a/src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell/index.tsx b/src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell/index.tsx index 13313a54..0caf445c 100644 --- a/src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell/index.tsx @@ -1,12 +1,12 @@ import * as React from 'react' +import { useEffect, useState } from 'react' import EtherScanLink from 'src/components/EtherscanLink' import Identicon from 'src/components/Identicon' import Block from 'src/components/layout/Block' import Paragraph from 'src/components/layout/Paragraph' -import { useWindowDimensions } from 'src/logic/hooks/useWindowDimensions' -import { useEffect, useState } from 'react' import { getValidAddressBookName } from 'src/logic/addressBook/utils' +import { useWindowDimensions } from 'src/logic/hooks/useWindowDimensions' type OwnerAddressTableCellProps = { address: string @@ -36,7 +36,7 @@ const OwnerAddressTableCell = (props: OwnerAddressTableCellProps): React.ReactEl {showLinks ? (
{userName && getValidAddressBookName(userName)} - +
) : ( {address} diff --git a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/CheckOwner/index.tsx b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/CheckOwner/index.tsx index eb2853ab..3ab15c42 100644 --- a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/CheckOwner/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/CheckOwner/index.tsx @@ -4,8 +4,6 @@ import Close from '@material-ui/icons/Close' import classNames from 'classnames/bind' import React from 'react' -import { styles } from './style' - import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' import Identicon from 'src/components/Identicon' @@ -16,6 +14,8 @@ import Hairline from 'src/components/layout/Hairline' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' +import { styles } from './style' + export const REMOVE_OWNER_MODAL_NEXT_BTN_TEST_ID = 'remove-owner-next-btn' const CheckOwner = ({ classes, onClose, onSubmit, ownerAddress, ownerName }) => { @@ -53,7 +53,7 @@ const CheckOwner = ({ classes, onClose, onSubmit, ownerAddress, ownerName }) => {ownerAddress} - + diff --git a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/Review/index.tsx b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/Review/index.tsx index 8328f939..e3df6f75 100644 --- a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/Review/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/Review/index.tsx @@ -4,9 +4,8 @@ import Close from '@material-ui/icons/Close' import classNames from 'classnames' import React, { useEffect, useState } from 'react' import { useSelector } from 'react-redux' - -import { styles } from './style' - +import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' +import { getNetworkInfo } from 'src/config' import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' import Identicon from 'src/components/Identicon' @@ -16,34 +15,34 @@ import Col from 'src/components/layout/Col' import Hairline from 'src/components/layout/Hairline' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' -import { SENTINEL_ADDRESS, getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts' +import { getGnosisSafeInstanceAt, SENTINEL_ADDRESS } from 'src/logic/contracts/safeContracts' +import { safeNameSelector, safeOwnersSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew' import { formatAmount } from 'src/logic/tokens/utils/formatAmount' -import { getWeb3 } from 'src/logic/wallets/getWeb3' -import { safeNameSelector, safeOwnersSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' + +import { styles } from './style' export const REMOVE_OWNER_REVIEW_BTN_TEST_ID = 'remove-owner-review-btn' +const { nativeCoin } = getNetworkInfo() + const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddress, ownerName, values }) => { const [gasCosts, setGasCosts] = useState('< 0.001') const safeAddress = useSelector(safeParamAddressFromStateSelector) as string const safeName = useSelector(safeNameSelector) const owners = useSelector(safeOwnersSelector) - useEffect(() => { let isCurrent = true const estimateGas = async () => { - const web3 = getWeb3() - const { fromWei, toBN } = web3.utils const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress) const safeOwners = await gnosisSafe.methods.getOwners().call() const index = safeOwners.findIndex((owner) => owner.toLowerCase() === ownerAddress.toLowerCase()) const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1] const txData = gnosisSafe.methods.removeOwner(prevAddress, ownerAddress, values.threshold).encodeABI() const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, txData) - const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether') - const formattedGasCosts = formatAmount(gasCostsAsEth) + const gasCosts = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals) + const formattedGasCosts = formatAmount(gasCosts) if (isCurrent) { setGasCosts(formattedGasCosts) @@ -120,7 +119,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre {owner.address} - + @@ -149,7 +148,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre {ownerAddress} - + @@ -163,7 +162,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre You're about to create a transaction and will have to confirm it with your currently connected wallet.
- {`Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`} + {`Make sure you have ${gasCosts} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`}
diff --git a/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/OwnerForm/index.tsx b/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/OwnerForm/index.tsx index c8ce72bf..c808f411 100644 --- a/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/OwnerForm/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/OwnerForm/index.tsx @@ -5,25 +5,25 @@ import classNames from 'classnames/bind' import React from 'react' import { useSelector } from 'react-redux' -import { styles } from './style' - import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' -import Identicon from 'src/components/Identicon' -import { ScanQRWrapper } from 'src/components/ScanQRModal/ScanQRWrapper' import AddressInput from 'src/components/forms/AddressInput' import Field from 'src/components/forms/Field' import GnoForm from 'src/components/forms/GnoForm' import TextField from 'src/components/forms/TextField' import { composeValidators, minMaxLength, required, uniqueAddress } from 'src/components/forms/validator' +import Identicon from 'src/components/Identicon' import Block from 'src/components/layout/Block' import Button from 'src/components/layout/Button' import Col from 'src/components/layout/Col' import Hairline from 'src/components/layout/Hairline' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' +import { ScanQRWrapper } from 'src/components/ScanQRModal/ScanQRWrapper' import { safeOwnersSelector } from 'src/logic/safe/store/selectors' +import { styles } from './style' + export const REPLACE_OWNER_NAME_INPUT_TEST_ID = 'replace-owner-name-input' export const REPLACE_OWNER_ADDRESS_INPUT_TEST_ID = 'replace-owner-address-testid' export const REPLACE_OWNER_NEXT_BTN_TEST_ID = 'replace-owner-next-btn' @@ -94,7 +94,7 @@ const OwnerForm = ({ classes, onClose, onSubmit, ownerAddress, ownerName }) => { {ownerAddress} - + diff --git a/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/Review/index.tsx b/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/Review/index.tsx index 80939f21..156065bc 100644 --- a/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/Review/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/Review/index.tsx @@ -4,9 +4,8 @@ import Close from '@material-ui/icons/Close' import classNames from 'classnames' import React, { useEffect, useState } from 'react' import { useSelector } from 'react-redux' - -import { styles } from './style' - +import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' +import { getNetworkInfo } from 'src/config' import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' import Identicon from 'src/components/Identicon' @@ -16,19 +15,22 @@ import Col from 'src/components/layout/Col' import Hairline from 'src/components/layout/Hairline' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' -import { SENTINEL_ADDRESS, getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts' -import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew' -import { formatAmount } from 'src/logic/tokens/utils/formatAmount' -import { getWeb3 } from 'src/logic/wallets/getWeb3' +import { getGnosisSafeInstanceAt, SENTINEL_ADDRESS } from 'src/logic/contracts/safeContracts' import { safeNameSelector, safeOwnersSelector, safeParamAddressFromStateSelector, safeThresholdSelector, } from 'src/logic/safe/store/selectors' +import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew' +import { formatAmount } from 'src/logic/tokens/utils/formatAmount' + +import { styles } from './style' export const REPLACE_OWNER_SUBMIT_BTN_TEST_ID = 'replace-owner-submit-btn' +const { nativeCoin } = getNetworkInfo() + const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddress, ownerName, values }) => { const [gasCosts, setGasCosts] = useState('< 0.001') const safeAddress = useSelector(safeParamAddressFromStateSelector) as string @@ -39,16 +41,14 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre useEffect(() => { let isCurrent = true const estimateGas = async () => { - const web3 = getWeb3() - const { fromWei, toBN } = web3.utils const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress) const safeOwners = await gnosisSafe.methods.getOwners().call() const index = safeOwners.findIndex((owner) => owner.toLowerCase() === ownerAddress.toLowerCase()) const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1] const txData = gnosisSafe.methods.swapOwner(prevAddress, ownerAddress, values.ownerAddress).encodeABI() const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, txData) - const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether') - const formattedGasCosts = formatAmount(gasCostsAsEth) + const gasCosts = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals) + const formattedGasCosts = formatAmount(gasCosts) if (isCurrent) { setGasCosts(formattedGasCosts) } @@ -124,7 +124,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre {owner.address} - + @@ -153,7 +153,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre {ownerAddress} - + @@ -178,7 +178,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre {values.ownerAddress} - + @@ -192,7 +192,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre You're about to create a transaction and will have to confirm it with your currently connected wallet.
- {`Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`} + {`Make sure you have ${gasCosts} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`}
diff --git a/src/routes/safe/components/Settings/RemoveSafeModal/index.tsx b/src/routes/safe/components/Settings/RemoveSafeModal/index.tsx index e301c1b0..5524ecd3 100644 --- a/src/routes/safe/components/Settings/RemoveSafeModal/index.tsx +++ b/src/routes/safe/components/Settings/RemoveSafeModal/index.tsx @@ -5,6 +5,7 @@ import OpenInNew from '@material-ui/icons/OpenInNew' import classNames from 'classnames' import React from 'react' import { useDispatch, useSelector } from 'react-redux' +import { getExplorerInfo } from 'src/config' import { styles } from './style' @@ -17,7 +18,6 @@ import Hairline from 'src/components/layout/Hairline' import Link from 'src/components/layout/Link' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' -import { getEtherScanLink } from 'src/logic/wallets/getWeb3' import removeSafe from 'src/logic/safe/store/actions/removeSafe' import { safeNameSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' import { md, secondary } from 'src/theme/variables' @@ -34,7 +34,8 @@ const RemoveSafeComponent = ({ isOpen, onClose }) => { const safeAddress = useSelector(safeParamAddressFromStateSelector) as string const safeName = useSelector(safeNameSelector) const dispatch = useDispatch() - const etherScanLink = getEtherScanLink('address', safeAddress) + const explorerInfo = getExplorerInfo(safeAddress) + const { url } = explorerInfo() return ( { {safeAddress} - + diff --git a/src/routes/safe/components/Settings/ThresholdSettings/ChangeThreshold/index.tsx b/src/routes/safe/components/Settings/ThresholdSettings/ChangeThreshold/index.tsx index e821d4d3..d941eb4f 100644 --- a/src/routes/safe/components/Settings/ThresholdSettings/ChangeThreshold/index.tsx +++ b/src/routes/safe/components/Settings/ThresholdSettings/ChangeThreshold/index.tsx @@ -3,7 +3,8 @@ import MenuItem from '@material-ui/core/MenuItem' import { withStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' import React, { useEffect, useState } from 'react' - +import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' +import { getNetworkInfo } from 'src/config' import { styles } from './style' import Field from 'src/components/forms/Field' @@ -19,22 +20,21 @@ import Row from 'src/components/layout/Row' import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts' import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew' import { formatAmount } from 'src/logic/tokens/utils/formatAmount' -import { getWeb3 } from 'src/logic/wallets/getWeb3' const THRESHOLD_FIELD_NAME = 'threshold' +const { nativeCoin } = getNetworkInfo() + const ChangeThreshold = ({ classes, onChangeThreshold, onClose, owners, safeAddress, threshold }) => { const [gasCosts, setGasCosts] = useState('< 0.001') useEffect(() => { let isCurrent = true const estimateGasCosts = async () => { - const web3 = getWeb3() - const { fromWei, toBN } = web3.utils const safeInstance = await getGnosisSafeInstanceAt(safeAddress) const txData = safeInstance.methods.changeThreshold('1').encodeABI() const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, txData) - const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether') + const gasCostsAsEth = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals) const formattedGasCosts = formatAmount(gasCostsAsEth) if (isCurrent) { setGasCosts(formattedGasCosts) @@ -105,7 +105,7 @@ const ChangeThreshold = ({ classes, onChangeThreshold, onClose, owners, safeAddr - {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`} + {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.tsx index e6eb22ee..5a04dd07 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.tsx @@ -5,6 +5,8 @@ import { makeStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' +import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' +import { getNetworkInfo } from 'src/config' import { styles } from './style' @@ -18,7 +20,6 @@ import Row from 'src/components/layout/Row' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew' import { formatAmount } from 'src/logic/tokens/utils/formatAmount' -import { getWeb3 } from 'src/logic/wallets/getWeb3' import { userAccountSelector } from 'src/logic/wallets/store/selectors' import processTransaction from 'src/logic/safe/store/actions/processTransaction' @@ -61,6 +62,7 @@ type Props = { thresholdReached: boolean tx: Transaction } +const { nativeCoin } = getNetworkInfo() const ApproveTxModal = ({ canExecute, @@ -85,9 +87,6 @@ const ApproveTxModal = ({ let isCurrent = true const estimateGas = async () => { - const web3 = getWeb3() - const { fromWei, toBN } = web3.utils - const estimatedGasCosts = await estimateTxGasCosts( safeAddress, tx.recipient, @@ -95,8 +94,8 @@ const ApproveTxModal = ({ tx, approveAndExecute ? userAddress : undefined, ) - const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether') - const formattedGasCosts = formatAmount(gasCostsAsEth) + const gasCosts = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals) + const formattedGasCosts = formatAmount(gasCosts) if (isCurrent) { setGasCosts(formattedGasCosts) } @@ -164,7 +163,9 @@ const ApproveTxModal = ({ {`You're about to ${ approveAndExecute ? 'execute' : 'approve' - } a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`} + } a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ${ + nativeCoin.name + } in this wallet to fund this confirmation.`} diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/CreationTx/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/CreationTx/index.tsx index d4e609b7..bae4e469 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/CreationTx/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/CreationTx/index.tsx @@ -1,14 +1,15 @@ import { makeStyles } from '@material-ui/core/styles' import React from 'react' import { EthHashInfo } from '@gnosis.pm/safe-react-components' -import { getNetwork } from 'src/config' import { Transaction } from 'src/logic/safe/store/models/types/transaction' + import { formatDate } from 'src/routes/safe/components/Transactions/TxsTable/columns' import Bold from 'src/components/layout/Bold' import Paragraph from 'src/components/layout/Paragraph' import Block from 'src/components/layout/Block' import { TransactionTypes } from 'src/logic/safe/store/models/types/transaction' +import { getExplorerInfo } from 'src/config' const useStyles = makeStyles({ address: { @@ -25,17 +26,19 @@ const useStyles = makeStyles({ }) type Props = { - tx: Transaction + tx?: Transaction } export const CreationTx = ({ tx }: Props): React.ReactElement | null => { const classes = useStyles() + const isCreationTx = tx?.type === TransactionTypes.CREATION - if (!tx) { + if (!tx || !isCreationTx) { return null } - - const isCreationTx = tx.type === TransactionTypes.CREATION + const explorerUrl = getExplorerInfo(tx.creator) + const scanBlockFactoryAddressUrl = getExplorerInfo(tx.factoryAddress) + const scanBlockMasterCopyUrl = getExplorerInfo(tx.masterCopy) return isCreationTx ? ( <> @@ -45,16 +48,12 @@ export const CreationTx = ({ tx }: Props): React.ReactElement | null => { Creator: - {tx.creator ? ( - - ) : ( - 'n/a' - )} + {tx.creator ? : 'n/a'} Factory: {tx.factoryAddress ? ( - + ) : ( 'n/a' )} @@ -62,7 +61,7 @@ export const CreationTx = ({ tx }: Props): React.ReactElement | null => { Mastercopy: {tx.masterCopy ? ( - + ) : ( 'n/a' )} diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription/index.tsx index bab38466..532ce9ac 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription/index.tsx @@ -28,7 +28,7 @@ const TransferDescription = ({ from, txFromName, value = '' }) => ( {txFromName ? ( ) : ( - + )} ) diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/OwnerComponent.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/OwnerComponent.tsx index 8ba79a83..06bbaec6 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/OwnerComponent.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/OwnerComponent.tsx @@ -3,7 +3,6 @@ import cn from 'classnames' import React from 'react' import { useSelector } from 'react-redux' import { EthHashInfo } from '@gnosis.pm/safe-react-components' -import { getNetwork } from 'src/config' import CancelSmallFilledCircle from './assets/cancel-small-filled.svg' import ConfirmSmallFilledCircle from './assets/confirm-small-filled.svg' @@ -18,6 +17,7 @@ import Button from 'src/components/layout/Button' import Img from 'src/components/layout/Img' import { getNameFromAddressBookSelector } from 'src/logic/addressBook/store/selectors' import { OwnersWithoutConfirmations } from './index' +import { getExplorerInfo } from 'src/config' export const CONFIRM_TX_BTN_TEST_ID = 'confirm-btn' export const EXECUTE_TX_BTN_TEST_ID = 'execute-btn' @@ -163,7 +163,7 @@ const OwnerComponent = (props: OwnerComponentProps): React.ReactElement => { ) } - + const explorerUrl = getExplorerInfo(owner) return (
{ shortenHash={4} showIdenticon showCopyBtn - showEtherscanBtn - network={getNetwork()} + explorerUrl={explorerUrl} /> {owner === userAddress && {isCancelTx ? rejectButton() : confirmButton()}} diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/index.tsx index ee580586..5fbf1262 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/index.tsx @@ -114,7 +114,6 @@ const OwnersColumn = ({ }: ownersColumnProps): React.ReactElement => { const classes = useStyles() let showOlderTxAnnotation - if (tx.isExecuted || cancelTx.isExecuted) { showOlderTxAnnotation = false } else { diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/RejectTxModal/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/RejectTxModal/index.tsx index e5372d4c..013b2444 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/RejectTxModal/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/RejectTxModal/index.tsx @@ -3,6 +3,8 @@ import { makeStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' +import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' +import { getNetworkInfo } from 'src/config' import { styles } from './style' @@ -17,7 +19,6 @@ import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import { estimateTxGasCosts } from 'src/logic/safe/transactions/gasNew' import { formatAmount } from 'src/logic/tokens/utils/formatAmount' import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' -import { getWeb3 } from 'src/logic/wallets/getWeb3' import createTransaction from 'src/logic/safe/store/actions/createTransaction' import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' @@ -31,6 +32,8 @@ type Props = { tx: Transaction } +const { nativeCoin } = getNetworkInfo() + const RejectTxModal = ({ isOpen, onClose, tx }: Props): React.ReactElement => { const [gasCosts, setGasCosts] = useState('< 0.001') const dispatch = useDispatch() @@ -40,12 +43,9 @@ const RejectTxModal = ({ isOpen, onClose, tx }: Props): React.ReactElement => { useEffect(() => { let isCurrent = true const estimateGasCosts = async () => { - const web3 = getWeb3() - const { fromWei, toBN } = web3.utils - const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, EMPTY_DATA) - const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether') - const formattedGasCosts = formatAmount(gasCostsAsEth) + const gasCosts = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals) + const formattedGasCosts = formatAmount(gasCosts) if (isCurrent) { setGasCosts(formattedGasCosts) } @@ -96,7 +96,7 @@ const RejectTxModal = ({ isOpen, onClose, tx }: Props): React.ReactElement => { - {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`} + {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx index 0ed87bec..cf73ed8f 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx @@ -1,6 +1,7 @@ import { IconText, Text, EthHashInfo } from '@gnosis.pm/safe-react-components' import { makeStyles } from '@material-ui/core/styles' import React from 'react' + import styled from 'styled-components' import { styles } from './styles' @@ -12,7 +13,7 @@ import { MultiSendDetails, } from 'src/routes/safe/store/actions/transactions/utils/multiSendDecodedDetails' import Bold from 'src/components/layout/Bold' -import { humanReadableValue } from 'src/logic/tokens/utils/humanReadableValue' +import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' import Collapse from 'src/components/Collapse' import { useSelector } from 'react-redux' import { getNameFromAddressBookSelector } from 'src/logic/addressBook/store/selectors' @@ -23,7 +24,8 @@ import { Transaction } from 'src/logic/safe/store/models/types/transaction' import { DataDecoded } from 'src/routes/safe/store/models/types/transactions.d' import DividerLine from 'src/components/DividerLine' import { isArrayParameter } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils' -import { getNetwork } from 'src/config' + +import { getExplorerInfo, getNetworkInfo } from 'src/config' export const TRANSACTIONS_DESC_CUSTOM_VALUE_TEST_ID = 'tx-description-custom-value' export const TRANSACTIONS_DESC_CUSTOM_DATA_TEST_ID = 'tx-description-custom-data' @@ -55,6 +57,8 @@ const StyledMethodName = styled(Text)` white-space: nowrap; ` +const { nativeCoin } = getNetworkInfo() + const TxInfoDetails = ({ data }: { data: DataDecoded }): React.ReactElement => ( @@ -75,7 +79,7 @@ const TxInfoDetails = ({ data }: { data: DataDecoded }): React.ReactElement => ( const MultiSendCustomDataAction = ({ tx, order }: { tx: MultiSendDetails; order: number }): React.ReactElement => { const classes = useStyles() const methodName = tx.data?.method ? ` (${tx.data.method})` : '' - + const explorerUrl = getExplorerInfo(tx.to) return ( - Send {humanReadableValue(tx.value)} ETH to: - + + Send {fromTokenUnit(tx.value, nativeCoin.decimals)} {nativeCoin.name} to: + + {!!tx.data && } @@ -177,7 +183,7 @@ interface GenericCustomDataProps { const GenericCustomData = ({ amount = '0', data, recipient, storedTx }: GenericCustomDataProps): React.ReactElement => { const classes = useStyles() const recipientName = useSelector((state) => getNameFromAddressBookSelector(state, recipient)) - + const explorerUrl = getExplorerInfo(recipient) return ( @@ -188,8 +194,7 @@ const GenericCustomData = ({ amount = '0', data, recipient, storedTx }: GenericC name={recipientName === 'UNKNOWN' ? undefined : recipientName} showIdenticon showCopyBtn - showEtherscanBtn - network={getNetwork()} + explorerUrl={explorerUrl} /> diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription.tsx index 596f3224..ccaf06fe 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription.tsx @@ -1,12 +1,12 @@ -import { useSelector } from 'react-redux' import React from 'react' - -import { getNameFromAddressBookSelector } from 'src/logic/addressBook/store/selectors' +import { useSelector } from 'react-redux' +import EtherscanLink from 'src/components/EtherscanLink' import Block from 'src/components/layout/Block' import Bold from 'src/components/layout/Bold' -import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell' -import EtherscanLink from 'src/components/EtherscanLink' import Paragraph from 'src/components/layout/Paragraph' + +import { getNameFromAddressBookSelector } from 'src/logic/addressBook/store/selectors' +import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell' import { SAFE_METHODS_NAMES, SafeMethods } from 'src/routes/safe/store/models/types/transactions.d' export const TRANSACTIONS_DESC_ADD_OWNER_TEST_ID = 'tx-description-add-owner' @@ -29,7 +29,7 @@ const RemovedOwner = ({ removedOwner }: RemovedOwnerProps): React.ReactElement = {ownerChangedName ? ( ) : ( - + )} ) @@ -48,7 +48,7 @@ const AddedOwner = ({ addedOwner }: AddedOwnerProps): React.ReactElement => { {ownerChangedName ? ( ) : ( - + )} ) @@ -74,7 +74,7 @@ interface AddModuleProps { const AddModule = ({ module }: AddModuleProps): React.ReactElement => ( Add module: - + ) @@ -85,7 +85,7 @@ interface RemoveModuleProps { const RemoveModule = ({ module }: RemoveModuleProps): React.ReactElement => ( Remove module: - + ) diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/TransferDescription.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/TransferDescription.tsx index 4fc0be97..afc5f2c1 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/TransferDescription.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/TransferDescription.tsx @@ -1,12 +1,12 @@ import React from 'react' import { useSelector } from 'react-redux' - -import { TRANSACTIONS_DESC_SEND_TEST_ID } from './index' -import { getNameFromAddressBookSelector } from 'src/logic/addressBook/store/selectors' +import EtherscanLink from 'src/components/EtherscanLink' import Block from 'src/components/layout/Block' import Bold from 'src/components/layout/Bold' +import { getNameFromAddressBookSelector } from 'src/logic/addressBook/store/selectors' import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell' -import EtherscanLink from 'src/components/EtherscanLink' + +import { TRANSACTIONS_DESC_SEND_TEST_ID } from './index' interface TransferDescriptionProps { amount: string @@ -21,7 +21,7 @@ const TransferDescription = ({ amount = '', recipient }: TransferDescriptionProp {recipientName ? ( ) : ( - + )} ) diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/Value.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/Value.tsx index 7a0108e2..9bc97b09 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/Value.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/Value.tsx @@ -2,11 +2,11 @@ import { Text, EthHashInfo } from '@gnosis.pm/safe-react-components' import React from 'react' import styled from 'styled-components' -import { getNetwork } from 'src/config' import { isAddress, isArrayParameter, } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils' +import { getExplorerInfo } from 'src/config' const NestedWrapper = styled.div` padding-left: 4px; @@ -50,10 +50,9 @@ const GenericValue = ({ method, type, value }: RenderValueProps): React.ReactEle } const Value = ({ type, ...props }: RenderValueProps): React.ReactElement => { + const explorerUrl = getExplorerInfo(props.value as string) if (isAddress(type)) { - return ( - - ) + return } return diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx index 2bc2c2bb..8ef6da59 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx @@ -13,7 +13,6 @@ import { CreationTx } from './CreationTx' import { OutgoingTx } from './OutgoingTx' import { styles } from './style' -import { getNetwork } from 'src/config' import Block from 'src/components/layout/Block' import Bold from 'src/components/layout/Bold' import Col from 'src/components/layout/Col' @@ -26,6 +25,7 @@ import { INCOMING_TX_TYPES } from 'src/logic/safe/store/models/incomingTransacti import { safeNonceSelector, safeThresholdSelector } from 'src/logic/safe/store/selectors' import { Transaction, TransactionTypes } from 'src/logic/safe/store/models/types/transaction' import IncomingTxDescription from './IncomingTxDescription' +import { getExplorerInfo, getNetworkInfo } from 'src/config' const useStyles = makeStyles(styles as any) @@ -34,6 +34,8 @@ interface ExpandedTxProps { tx: Transaction } +const { nativeCoin } = getNetworkInfo() + const ExpandedTx = ({ cancelTx, tx }: ExpandedTxProps): React.ReactElement => { const { fromWei, toBN } = getWeb3().utils @@ -59,6 +61,8 @@ const ExpandedTx = ({ cancelTx, tx }: ExpandedTxProps): React.ReactElement => { } } + const explorerUrl = tx.executionTxHash ? getExplorerInfo(tx.executionTxHash) : null + return ( <> @@ -68,13 +72,7 @@ const ExpandedTx = ({ cancelTx, tx }: ExpandedTxProps): React.ReactElement => {
Hash: {tx.executionTxHash ? ( - + ) : ( 'n/a' )} @@ -88,7 +86,7 @@ const ExpandedTx = ({ cancelTx, tx }: ExpandedTxProps): React.ReactElement => { {!isCreationTx ? ( Fee: - {tx.fee ? fromWei(toBN(tx.fee)) + ' ETH' : 'n/a'} + {tx.fee ? fromWei(toBN(tx.fee)) + ` ${nativeCoin.name}` : 'n/a'} ) : null} diff --git a/src/routes/safe/container/index.tsx b/src/routes/safe/container/index.tsx index 2ca98cbf..d90de961 100644 --- a/src/routes/safe/container/index.tsx +++ b/src/routes/safe/container/index.tsx @@ -1,13 +1,15 @@ +import { GenericModal } from '@gnosis.pm/safe-react-components' import React, { useState } from 'react' import { useSelector } from 'react-redux' import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom' -import { GenericModal } from '@gnosis.pm/safe-react-components' import NoSafe from 'src/components/NoSafe' import { providerNameSelector } from 'src/logic/wallets/store/selectors' -import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' +import { safeFeaturesEnabledSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' +import { AppReduxState } from 'src/store' import { wrapInSuspense } from 'src/utils/wrapInSuspense' import { SAFELIST_ADDRESS } from 'src/routes/routes' +import { FEATURES } from 'src/config/networks/network.d' export const BALANCES_TAB_BTN_TEST_ID = 'balances-tab-btn' export const SETTINGS_TAB_BTN_TEST_ID = 'settings-tab-btn' @@ -34,7 +36,18 @@ const Container = (): React.ReactElement => { const safeAddress = useSelector(safeParamAddressFromStateSelector) const provider = useSelector(providerNameSelector) + const featuresEnabled = useSelector( + safeFeaturesEnabledSelector, + (left, right) => { + if (Array.isArray(left) && Array.isArray(right)) { + return JSON.stringify(left) === JSON.stringify(right) + } + + return left === right + }, + ) const matchSafeWithAddress = useRouteMatch<{ safeAddress: string }>({ path: `${SAFELIST_ADDRESS}/:safeAddress` }) + const safeAppsEnabled = Boolean(featuresEnabled?.includes(FEATURES.SAFE_APPS)) if (!safeAddress) { return @@ -67,7 +80,17 @@ const Container = (): React.ReactElement => { path={`${matchSafeWithAddress?.path}/transactions`} render={() => wrapInSuspense(, null)} /> - wrapInSuspense(, null)} /> + { + if (!safeAppsEnabled) { + history.push(`${matchSafeWithAddress?.url}/balances`) + } + return wrapInSuspense(, null) + }} + /> + receipt.events?.ProxyCreation.returnValues.proxy) } export default aSafe diff --git a/src/utils/constants.ts b/src/utils/constants.ts index a21a6b6c..cf6fbe2e 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,16 +1,40 @@ -import { ETHEREUM_NETWORK } from 'src/logic/wallets/getWeb3' +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' -export const NETWORK = process.env.REACT_APP_NETWORK || ETHEREUM_NETWORK.RINKEBY -export const GOOGLE_ANALYTICS_ID_RINKEBY = process.env.REACT_APP_GOOGLE_ANALYTICS_ID_RINKEBY -export const GOOGLE_ANALYTICS_ID_MAINNET = process.env.REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET -export const INTERCOM_ID = process.env.REACT_APP_INTERCOM_ID -export const PORTIS_ID = process.env.REACT_APP_PORTIS_ID -export const SQUARELINK_ID = process.env.REACT_APP_SQUARELINK_ID -export const FORTMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY +export const APP_ENV = process.env.REACT_APP_ENV +export const NODE_ENV = process.env.NODE_ENV +export const NETWORK = process.env.REACT_APP_NETWORK?.toUpperCase() || 'RINKEBY' +export const INTERCOM_ID = APP_ENV === 'production' ? process.env.REACT_APP_INTERCOM_ID : 'plssl1fl' +export const GOOGLE_ANALYTICS_ID = process.env.REACT_APP_GOOGLE_ANALYTICS || '' +export const PORTIS_ID = { + [ETHEREUM_NETWORK.RINKEBY]: '852b763d-f28b-4463-80cb-846d7ec5806b', + [ETHEREUM_NETWORK.MAINNET]: process.env.REACT_APP_PORTIS_ID, + [ETHEREUM_NETWORK.XDAI]: process.env.REACT_APP_PORTIS_ID, +} +export const FORTMATIC_KEY = { + [ETHEREUM_NETWORK.RINKEBY]: 'pk_test_CAD437AA29BE0A40', + [ETHEREUM_NETWORK.MAINNET]: process.env.REACT_APP_FORTMATIC_KEY, + [ETHEREUM_NETWORK.XDAI]: process.env.REACT_APP_FORTMATIC_KEY, +} +export const BLOCKNATIVE_KEY = { + [ETHEREUM_NETWORK.RINKEBY]: '7fbb9cee-7e97-4436-8770-8b29a9a8814c', + [ETHEREUM_NETWORK.MAINNET]: process.env.REACT_APP_BLOCKNATIVE_KEY, + [ETHEREUM_NETWORK.XDAI]: process.env.REACT_APP_BLOCKNATIVE_KEY, +} +/* + * Not being used +export const SQUARELINK_ID = { + [ETHEREUM_NETWORK.RINKEBY]: '46ce08fe50913cfa1b78', + [ETHEREUM_NETWORK.MAINNET]: process.env.REACT_APP_SQUARELINK_ID, + [ETHEREUM_NETWORK.XDAI]: process.env.REACT_APP_SQUARELINK_ID, +} + */ export const INFURA_TOKEN = process.env.REACT_APP_INFURA_TOKEN || '' -export const LATEST_SAFE_VERSION = process.env.REACT_APP_LATEST_SAFE_VERSION || 'not-defined' +export const LATEST_SAFE_VERSION = process.env.REACT_APP_LATEST_SAFE_VERSION || '1.1.1' export const APP_VERSION = process.env.REACT_APP_APP_VERSION || 'not-defined' export const OPENSEA_API_KEY = process.env.REACT_APP_OPENSEA_API_KEY || '' -export const COLLECTIBLES_SOURCE = process.env.REACT_APP_COLLECTIBLES_SOURCE || 'OpenSea' +export const COLLECTIBLES_SOURCE = process.env.REACT_APP_COLLECTIBLES_SOURCE || 'Gnosis' export const TIMEOUT = process.env.NODE_ENV === 'test' ? 1500 : 5000 export const ETHERSCAN_API_KEY = process.env.REACT_APP_ETHERSCAN_API_KEY +export const EXCHANGE_RATE_URL = 'https://api.exchangeratesapi.io/latest' +export const EXCHANGE_RATE_URL_FALLBACK = 'https://api.coinbase.com/v2/exchange-rates' +export const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY diff --git a/src/utils/intercom.ts b/src/utils/intercom.ts index 6fb98cbf..68035e7d 100644 --- a/src/utils/intercom.ts +++ b/src/utils/intercom.ts @@ -1,8 +1,8 @@ -import { getIntercomId } from 'src/config' +import { INTERCOM_ID } from 'src/utils/constants' // eslint-disable-next-line consistent-return export const loadIntercom = () => { - const APP_ID = getIntercomId() + const APP_ID = INTERCOM_ID if (!APP_ID) { console.error('[Intercom] - In order to use Intercom you need to add an appID') return null diff --git a/src/utils/storage/index.ts b/src/utils/storage/index.ts index d2dff5b7..d9f7a133 100644 --- a/src/utils/storage/index.ts +++ b/src/utils/storage/index.ts @@ -1,6 +1,6 @@ import { ImmortalStorage, IndexedDbStore, LocalStorageStore } from 'immortal-db' -import { getNetwork } from 'src/config' +import { getNetworkName } from 'src/config' // Don't use sessionStorage and cookieStorage // https://github.com/gruns/ImmortalDB/issues/22 @@ -8,7 +8,7 @@ import { getNetwork } from 'src/config' const stores = [IndexedDbStore, LocalStorageStore] export const storage = new ImmortalStorage(stores) -const PREFIX = `v2_${getNetwork()}` +const PREFIX = `v2_${getNetworkName()}` export const loadFromStorage = async (key: string): Promise => { try { diff --git a/yarn.lock b/yarn.lock index 98d3de3f..2a2958a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1484,10 +1484,10 @@ "@ethersproject/rlp" "^5.0.0" "@ethersproject/signing-key" "^5.0.0" -"@gnosis.pm/safe-apps-sdk@0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-apps-sdk/-/safe-apps-sdk-0.4.0.tgz#26c821513c995b9dc023ebbdfe103a832e731521" - integrity sha512-hUt/Siz5kSu9jgvMZXejQsxQiUo/NIow67KNAQGfMt7D0S1YoyvpCGAgSliNelY/bP7EanBhhStOnItnu7DwUA== +"@gnosis.pm/safe-apps-sdk@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-apps-sdk/-/safe-apps-sdk-0.4.2.tgz#ae87b2164931c006cb0efdede3d82ff210df1648" + integrity sha512-BwA2dyCebPMdi4JhhTkp6EjkhEM6vAIviKdhqHiHnSmL+sDfxtP1jdOuE8ME2/4+5TiLSS8k8qscYjLSlf1LLw== "@gnosis.pm/safe-contracts@1.1.1-dev.2": version "1.1.1-dev.2" @@ -1501,9 +1501,9 @@ solc "0.5.14" truffle "^5.1.21" -"@gnosis.pm/safe-react-components@https://github.com/gnosis/safe-react-components.git#1bf397f": - version "0.2.0" - resolved "https://github.com/gnosis/safe-react-components.git#1bf397f2bc48ba48906824137943f0bb5804c99c" +"@gnosis.pm/safe-react-components@https://github.com/gnosis/safe-react-components.git#70e57bdd1e0fd5dfdf5768076577c1e000b5fe28": + version "0.4.0" + resolved "https://github.com/gnosis/safe-react-components.git#70e57bdd1e0fd5dfdf5768076577c1e000b5fe28" dependencies: classnames "^2.2.6" polished "3.6.5" @@ -1757,11 +1757,25 @@ "@ledgerhq/logs" "^5.22.0" rxjs "^6.6.2" +"@ledgerhq/devices@^5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.26.0.tgz#6c25ee48d0d2f49a8fa1abc11f3efd888f3fea68" + integrity sha512-atD6al6E6j2tT7vefnW5r0bbZVURsOFbvyqy4Cknv659xVO/+i4pftgRaATecGD+evprRMcI+Rt1G1WtP3z66Q== + dependencies: + "@ledgerhq/errors" "^5.26.0" + "@ledgerhq/logs" "^5.26.0" + rxjs "^6.6.3" + "@ledgerhq/errors@^5.22.0": version "5.22.0" resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.22.0.tgz#7327fc152d4896ddc26aada0943065db21c14880" integrity sha512-XDT0meBn39+q+JWzUFXmiFbVYLTy+uHRFMb9napcxyZ0Q/MdKkle9/vkgtvRHjPIkGobklXpyefsgH3BZQHukA== +"@ledgerhq/errors@^5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.26.0.tgz#a2d3da356e8053817b6517a94eff876ca1d0c972" + integrity sha512-oMEf6ONyLqaZYkunDZWSc6Qo9uBzim5R8XDXOOyrz7zrj4hixsvxiAh8y1Jbrr1MQLG5HJ+aehee4Ot/A5iXtw== + "@ledgerhq/hw-app-eth@^5.21.0": version "5.22.0" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-eth/-/hw-app-eth-5.22.0.tgz#571e7d97629a9be63c5a1e5d7324cde0a7a8ffd5" @@ -1772,29 +1786,29 @@ bignumber.js "^9.0.0" rlp "^2.2.6" -"@ledgerhq/hw-transport-node-hid-noevents@^5.22.0": - version "5.22.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-5.22.0.tgz#b5a42a71664fe69bf5fae579854d10e4815a794e" - integrity sha512-6sxrqTcBEGvhVDOS5Vy3mKZgaVOKbHplxm4o/3PmtugJcvpEBvDNGXNh3PMWPtHXXYQ5E5C/qWh7Y+gYshMmTg== +"@ledgerhq/hw-transport-node-hid-noevents@^5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-5.26.0.tgz#84155794e5095e4db0fbc39f1442c6c2e8f482e6" + integrity sha512-XszH42vlzp+BwO7Ej2H91aCrH1TVsc5/uc/Bpciz1ac34FIp412kUMT9zAcY7H1ey2lgqTiBddSyj4uEPUB6Nw== dependencies: - "@ledgerhq/devices" "^5.22.0" - "@ledgerhq/errors" "^5.22.0" - "@ledgerhq/hw-transport" "^5.22.0" - "@ledgerhq/logs" "^5.22.0" - node-hid "^1.3.0" + "@ledgerhq/devices" "^5.26.0" + "@ledgerhq/errors" "^5.26.0" + "@ledgerhq/hw-transport" "^5.26.0" + "@ledgerhq/logs" "^5.26.0" + node-hid "1.3.0" -"@ledgerhq/hw-transport-node-hid@5.22.0": - version "5.22.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-5.22.0.tgz#00f573bd9163b9c553071af3f2a52aa19d4674c0" - integrity sha512-TrSQEGiYXBW8FQS2QEAmk/g+vwcKQ2MZVjPiIaqSCGnwVgmKOXfMetPjwgwr8k6XiQ7YMRdpsXa0GpIvTowqRA== +"@ledgerhq/hw-transport-node-hid@5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-5.26.0.tgz#69bc4f8067cdd9c09ef4aed0e0b3c58328936e4b" + integrity sha512-qhaefZVZatJ6UuK8Wb6WSFNOLWc2mxcv/xgsfKi5HJCIr4bPF/ecIeN+7fRcEaycxj4XykY6Z4A7zDVulfFH4w== dependencies: - "@ledgerhq/devices" "^5.22.0" - "@ledgerhq/errors" "^5.22.0" - "@ledgerhq/hw-transport" "^5.22.0" - "@ledgerhq/hw-transport-node-hid-noevents" "^5.22.0" - "@ledgerhq/logs" "^5.22.0" - lodash "^4.17.19" - node-hid "^1.3.0" + "@ledgerhq/devices" "^5.26.0" + "@ledgerhq/errors" "^5.26.0" + "@ledgerhq/hw-transport" "^5.26.0" + "@ledgerhq/hw-transport-node-hid-noevents" "^5.26.0" + "@ledgerhq/logs" "^5.26.0" + lodash "^4.17.20" + node-hid "1.3.0" usb "^1.6.3" "@ledgerhq/hw-transport-u2f@^5.21.0": @@ -1816,11 +1830,25 @@ "@ledgerhq/errors" "^5.22.0" events "^3.2.0" +"@ledgerhq/hw-transport@^5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-5.26.0.tgz#bfedc3d48400ad2fe48278d9444344b72aa9d0fe" + integrity sha512-NFeJOJmyEfAX8uuIBTpocWHcz630sqPcXbu864Q+OCBm4EK5UOKV1h/pX7e0xgNIKY8zhJ/O4p4cIZp9tnXLHQ== + dependencies: + "@ledgerhq/devices" "^5.26.0" + "@ledgerhq/errors" "^5.26.0" + events "^3.2.0" + "@ledgerhq/logs@^5.22.0": version "5.22.0" resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.22.0.tgz#a54d6b5b391cdb4c2eacc9500feb04b90475c361" integrity sha512-jV4mJxD1aieORm+sK9bYakQd9GMLd7KAxgt2IaxhrTU+QD5Ne47mxQOTys9p7f5w25ujs3R+Px2t3KiMRASHtg== +"@ledgerhq/logs@^5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.26.0.tgz#171bd471265259663520abb02a0eed80e949e3a1" + integrity sha512-/3EKvS9eHzKl9Om+t//SPnzJaahIoVIUlozLMSarINyO7SSh174kxl3jTNHBl7dLxvkQyDDs5imLvP04ohlQaw== + "@material-ui/core@4.11.0": version "4.11.0" resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.11.0.tgz#b69b26e4553c9e53f2bfaf1053e216a0af9be15a" @@ -2575,24 +2603,76 @@ memory-cache "^0.2.0" web3-utils "^1.2.11" -"@truffle/blockchain-utils@^0.0.11": - version "0.0.11" - resolved "https://registry.yarnpkg.com/@truffle/blockchain-utils/-/blockchain-utils-0.0.11.tgz#9886f4cb7a9f20deded4451ac78f8567ae5c0d75" - integrity sha512-9MyQ/20M96clhIcC7fVFIckGSB8qMsmcdU6iYt98HXJ9GOLNKsCaJFz1OVsJncVreYwTUhoEXTrVBc8zrmPDJQ== +"@truffle/blockchain-utils@^0.0.25": + version "0.0.25" + resolved "https://registry.yarnpkg.com/@truffle/blockchain-utils/-/blockchain-utils-0.0.25.tgz#f4b320890113d282f25f1a1ecd65b94a8b763ac1" + integrity sha512-XA5m0BfAWtysy5ChHyiAf1fXbJxJXphKk+eZ9Rb9Twi6fn3Jg4gnHNwYXJacYFEydqT5vr2s4Ou812JHlautpw== + dependencies: + source-map-support "^0.5.19" -"@truffle/contract-schema@^3.0.14": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@truffle/contract-schema/-/contract-schema-3.2.3.tgz#f7d940c0187918083fe5a605ee81ac17efe287a8" - integrity sha512-dnR5wtqCBKVfJDX5g+sCUiDF1WDucpxoWsr6ZOwq9JqgyS4Gz7iJi1wMegMmcDctOykoKjsju6iAOi+HObrkfg== +"@truffle/codec@^0.6.4": + version "0.6.4" + resolved "https://registry.yarnpkg.com/@truffle/codec/-/codec-0.6.4.tgz#674074553f9baec1a1a1f37e89b59fabbf865e57" + integrity sha512-inTYczuEnml9OjqQyYmg6EG8M2WdwgQn3X3lhzmwfg19JNO1Z3rwjRRu3Hx0vD3OR6moaAjMXy6VBoPhyuoJLQ== + dependencies: + big.js "^5.2.2" + bn.js "^4.11.8" + borc "^2.1.2" + debug "^4.1.0" + lodash.clonedeep "^4.5.0" + lodash.escaperegexp "^4.1.2" + lodash.partition "^4.6.0" + lodash.sum "^4.0.2" + semver "^6.3.0" + source-map-support "^0.5.19" + utf8 "^3.0.0" + web3-utils "1.2.1" + +"@truffle/contract-schema@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@truffle/contract-schema/-/contract-schema-3.3.0.tgz#3cf6bcc18fe34cc21e35889b40e9afc1647f3c3c" + integrity sha512-HjD2tByID6KvR1jfy4z+oXKZ0pQVWrBDynGL7T3mNqApzbYXNWlwk2oEBIGgugWH17B1WxVu5CC00Lq67jFQIw== dependencies: ajv "^6.10.0" crypto-js "^3.1.9-1" debug "^4.1.0" -"@truffle/error@^0.0.6": - version "0.0.6" - resolved "https://registry.yarnpkg.com/@truffle/error/-/error-0.0.6.tgz#75d499845b4b3a40537889e7d04c663afcaee85d" - integrity sha512-QUM9ZWiwlXGixFGpV18g5I6vua6/r+ZV9W/5DQA5go9A3eZUNPHPaTKMIQPJLYn6+ZV5jg5H28zCHq56LHF3yA== +"@truffle/contract@4.2.25": + version "4.2.25" + resolved "https://registry.yarnpkg.com/@truffle/contract/-/contract-4.2.25.tgz#600575864d8ad18c5c8cab25ba0be39d84ec6252" + integrity sha512-VPeMY5ofJuoS9LFO/9OkLMZPyMTi4ErC2D8f9L+6vwbHFEbIJdL+qdRA6KYK1l+/SBCmrhfFUK7sdIg3+9E48w== + dependencies: + "@truffle/blockchain-utils" "^0.0.25" + "@truffle/contract-schema" "^3.3.0" + "@truffle/debug-utils" "^4.2.11" + "@truffle/error" "^0.0.11" + "@truffle/interface-adapter" "^0.4.16" + bignumber.js "^7.2.1" + ethereum-ens "^0.8.0" + ethers "^4.0.0-beta.1" + source-map-support "^0.5.19" + web3 "1.2.1" + web3-core-helpers "1.2.1" + web3-core-promievent "1.2.1" + web3-eth-abi "1.2.1" + web3-utils "1.2.1" + +"@truffle/debug-utils@^4.2.11": + version "4.2.11" + resolved "https://registry.yarnpkg.com/@truffle/debug-utils/-/debug-utils-4.2.11.tgz#8bb47a9efd020a9dcd4e12e24221af2cef1bf82b" + integrity sha512-mcu5vhVLRUoOXm6yKn+X7Z7gTtNNk9k7czNGRQyS6dD0K4cTSwap0UjTBR9a4P9eAo7aTcr1dChLS5Ups6cdXg== + dependencies: + "@truffle/codec" "^0.6.4" + "@trufflesuite/chromafi" "^2.2.0" + chalk "^2.4.2" + debug "^4.1.0" + highlight.js "^9.15.8" + highlightjs-solidity "^1.0.18" + +"@truffle/error@^0.0.11": + version "0.0.11" + resolved "https://registry.yarnpkg.com/@truffle/error/-/error-0.0.11.tgz#2789c0042d7e796dcbb840c7a9b5d2bcd8e0e2d8" + integrity sha512-ju6TucjlJkfYMmdraYY/IBJaFb+Sa+huhYtOoyOJ+G29KcgytUVnDzKGwC7Kgk6IsxQMm62Mc1E0GZzFbGGipw== "@truffle/hdwallet-provider@^1.0.0", "@truffle/hdwallet-provider@^1.0.27": version "1.0.42" @@ -2610,6 +2690,37 @@ ethereumjs-wallet "^0.6.3" source-map-support "^0.5.19" +"@truffle/interface-adapter@^0.4.16": + version "0.4.16" + resolved "https://registry.yarnpkg.com/@truffle/interface-adapter/-/interface-adapter-0.4.16.tgz#6bd65d9d17b4a2a51f39d05dd8b467daa8855792" + integrity sha512-lsxk26Lz/h0n8fe37K1ZxowxokXj0AZeNR10QHltDvkHukuTIC4L6fXvrUi74mCwI9hShl4CSBas1Q8kAyJyOA== + dependencies: + bn.js "^4.11.8" + ethers "^4.0.32" + source-map-support "^0.5.19" + web3 "1.2.1" + +"@trufflesuite/chromafi@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@trufflesuite/chromafi/-/chromafi-2.2.0.tgz#18cceacbb44f1e22ec956dd7ad21a2ed414b09c7" + integrity sha512-km4Px34wZ015PDjAK0wfYBx+zoCE4qR3AY9NWLUvtjnnzhCUkaRFCpZdvwDEyB75EzFBoLwV9iiqboz+mMXwBA== + dependencies: + ansi-mark "^1.0.0" + ansi-regex "^3.0.0" + array-uniq "^1.0.3" + camelcase "^4.1.0" + chalk "^2.3.2" + cheerio "^1.0.0-rc.2" + detect-indent "^5.0.0" + he "^1.1.1" + highlight.js "^9.12.0" + husky "^0.14.3" + lodash.merge "^4.6.2" + min-indent "^1.0.0" + strip-ansi "^4.0.0" + strip-indent "^2.0.0" + super-split "^1.1.0" + "@trufflesuite/eth-json-rpc-filters@^4.1.2-1": version "4.1.2-1" resolved "https://registry.yarnpkg.com/@trufflesuite/eth-json-rpc-filters/-/eth-json-rpc-filters-4.1.2-1.tgz#61ab78c52e98a883e5cf086925b34a30297b1824" @@ -2879,11 +2990,6 @@ "@types/node" "*" "@types/node@*": - version "14.0.27" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.27.tgz#a151873af5a5e851b51b3b065c9e63390a9e0eb1" - integrity sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g== - -"@types/node@14.11.2": version "14.11.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.2.tgz#2de1ed6670439387da1c9f549a2ade2b0a799256" integrity sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA== @@ -2898,6 +3004,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.54.tgz#a4b58d8df3a4677b6c08bfbc94b7ad7a7a5f82d1" integrity sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w== +"@types/node@^14.11.8": + version "14.11.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.8.tgz#fe2012f2355e4ce08bca44aeb3abbb21cf88d33f" + integrity sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw== + "@types/npmlog@^4.1.2": version "4.1.2" resolved "https://registry.yarnpkg.com/@types/npmlog/-/npmlog-4.1.2.tgz#d070fe6a6b78755d1092a3dc492d34c3d8f871c4" @@ -2962,10 +3073,10 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" -"@types/react-router-dom@^5.1.5": - version "5.1.5" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.5.tgz#7c334a2ea785dbad2b2dcdd83d2cf3d9973da090" - integrity sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw== +"@types/react-router-dom@^5.1.6": + version "5.1.6" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.6.tgz#07b14e7ab1893a837c8565634960dc398564b1fb" + integrity sha512-gjrxYqxz37zWEdMVvQtWPFMFj1dRDb4TGOcgyOfSXTrEXdF92L00WE3C471O3TV/RF1oskcStkXsOU0Ete4s/g== dependencies: "@types/history" "*" "@types/react" "*" @@ -3008,10 +3119,10 @@ "@types/prop-types" "*" csstype "^3.0.2" -"@types/react@^16.9.49": - version "16.9.49" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.49.tgz#09db021cf8089aba0cdb12a49f8021a69cce4872" - integrity sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g== +"@types/react@^16.9.52": + version "16.9.52" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.52.tgz#c46c72d1a1d8d9d666f4dd2066c0e22600ccfde1" + integrity sha512-EHRjmnxiNivwhGdMh9sz1Yw9AUxTSZFxKqdBWAAzyZx3sufWwx6ogqHYh/WB1m/I4ZpjkoZLExF5QTy2ekVi/Q== dependencies: "@types/prop-types" "*" csstype "^3.0.2" @@ -3045,10 +3156,10 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== -"@types/styled-components@^5.1.3": - version "5.1.3" - resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.3.tgz#6fab3d9c8f7d9a15cbb89d379d850c985002f363" - integrity sha512-HGpirof3WOhiX17lb61Q/tpgqn48jxO8EfZkdJ8ueYqwLbK2AHQe/G08DasdA2IdKnmwOIP1s9X2bopxKXgjRw== +"@types/styled-components@^5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.4.tgz#11f167dbde268635c66adc89b5a5db2e69d75384" + integrity sha512-78f5Zuy0v/LTQNOYfpH+CINHpchzMMmAt9amY2YNtSgsk1TmlKm8L2Wijss/mtTrsUAVTm2CdGB8VOM65vA8xg== dependencies: "@types/hoist-non-react-statics" "*" "@types/react" "*" @@ -3824,6 +3935,17 @@ ansi-html@0.0.7: resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= +ansi-mark@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/ansi-mark/-/ansi-mark-1.0.4.tgz#1cd4ba8d57f15f109d6aaf6ec9ca9786c8a4ee6c" + integrity sha1-HNS6jVfxXxCdaq9uycqXhsik7mw= + dependencies: + ansi-regex "^3.0.0" + array-uniq "^1.0.3" + chalk "^2.3.2" + strip-ansi "^4.0.0" + super-split "^1.1.0" + ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -4041,7 +4163,7 @@ array-union@^1.0.1: dependencies: array-uniq "^1.0.1" -array-uniq@^1.0.1: +array-uniq@^1.0.1, array-uniq@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= @@ -5255,16 +5377,21 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -bignumber.js@9.0.0, bignumber.js@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" - integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A== +bignumber.js@9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" + integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== bignumber.js@^7.2.1: version "7.2.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== +bignumber.js@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" + integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A== + "bignumber.js@git+https://github.com/frozeman/bignumber.js-nolookahead.git": version "2.0.7" resolved "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934" @@ -5327,7 +5454,7 @@ bluebird-lst@^1.0.9: dependencies: bluebird "^3.5.5" -bluebird@^3.3.5, bluebird@^3.5.0, bluebird@^3.5.5: +bluebird@^3.3.5, bluebird@^3.4.7, bluebird@^3.5.0, bluebird@^3.5.5: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -5357,10 +5484,10 @@ bn.js@^5.1.1, bn.js@^5.1.2: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b" integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ== -bnc-onboard@1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/bnc-onboard/-/bnc-onboard-1.13.1.tgz#281629e15c01ab47425f9494b33c4ce28a9db01e" - integrity sha512-Mv06oWNjkjDNU3vR8l/aJFKTBya5lj6vPfYcl9ONpyyaoY/8neZS4icQrFeRv9nDvENVz6esxcYAyX62f0+rwA== +bnc-onboard@1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/bnc-onboard/-/bnc-onboard-1.13.2.tgz#031b8ec87582d686b3493be97c410e09efda3557" + integrity sha512-r5vkY0GkD/gKULyjCSlgVkRFBEtFrqN+EVncm1e8i1sUdeQLiXjr0YNQ/xOcsyfH2MxYVEIcGoaQSH/NzeX0+g== dependencies: "@ledgerhq/hw-app-eth" "^5.21.0" "@ledgerhq/hw-transport-u2f" "^5.21.0" @@ -5428,6 +5555,19 @@ boolean@^3.0.0, boolean@^3.0.1: resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.1.tgz#35ecf2b4a2ee191b0b44986f14eb5f052a5cbb4f" integrity sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA== +borc@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/borc/-/borc-2.1.2.tgz#6ce75e7da5ce711b963755117dd1b187f6f8cf19" + integrity sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w== + dependencies: + bignumber.js "^9.0.0" + buffer "^5.5.0" + commander "^2.15.0" + ieee754 "^1.1.13" + iso-url "~0.4.7" + json-text-sequence "~0.1.0" + readable-stream "^3.6.0" + bowser@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.10.0.tgz#be3736f161c4bb8b10958027ab99465d2a811198" @@ -5694,6 +5834,13 @@ buffer@^5.0.5, buffer@^5.2.1, buffer@^5.4.3, buffer@^5.5.0, buffer@^5.6.0: base64-js "^1.0.2" ieee754 "^1.1.4" +bufferutil@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7" + integrity sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA== + dependencies: + node-gyp-build "~3.7.0" + builder-util-runtime@8.7.2: version "8.7.2" resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.7.2.tgz#d93afc71428a12789b437e13850e1fa7da956d72" @@ -5999,6 +6146,18 @@ checkpoint-store@^1.1.0: dependencies: functional-red-black-tree "^1.0.1" +cheerio@^1.0.0-rc.2: + version "1.0.0-rc.3" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6" + integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA== + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.1" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash "^4.15.0" + parse5 "^3.0.1" + chokidar@3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" @@ -6065,6 +6224,11 @@ chromium-pickle-js@^0.2.0: resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205" integrity sha1-BKEGZywYsIWrd02YPfo+oTjyIgU= +ci-info@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" + integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -6340,7 +6504,7 @@ commander@3.0.2: resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== -commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@^2.8.1: +commander@^2.11.0, commander@^2.15.0, commander@^2.19.0, commander@^2.20.0, commander@^2.8.1: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -6858,7 +7022,7 @@ css-select-base-adapter@^0.1.1: resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== -css-select@^1.1.0: +css-select@^1.1.0, css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= @@ -7103,7 +7267,12 @@ data-urls@^1.0.0, data-urls@^1.1.0: whatwg-mimetype "^2.2.0" whatwg-url "^7.0.0" -date-fns@2.15.0, date-fns@^2.0.1: +date-fns@2.16.1: + version "2.16.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b" + integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ== + +date-fns@^2.0.1: version "2.15.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.15.0.tgz#424de6b3778e4e69d3ff27046ec136af58ae5d5f" integrity sha512-ZCPzAMJZn3rNUvvQIMlXhDr4A+Ar07eLeGsGREoWU19a3Pqf5oYa+ccd+B3F6XVtQY6HANMFdOQ8A+ipFnvJdQ== @@ -7347,6 +7516,11 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= +delimit-stream@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/delimit-stream/-/delimit-stream-0.1.0.tgz#9b8319477c0e5f8aeb3ce357ae305fc25ea1cd2b" + integrity sha1-m4MZR3wOX4rrPONXrjBfwl6hzSs= + depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -7377,6 +7551,11 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" +detect-indent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= + detect-libc@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" @@ -7532,6 +7711,14 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" +dom-serializer@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" + integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== + dependencies: + domelementtype "^1.3.0" + entities "^1.1.1" + dom-walk@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" @@ -7542,7 +7729,7 @@ domain-browser@^1.1.1: resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== -domelementtype@1, domelementtype@^1.3.1: +domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== @@ -7773,10 +7960,10 @@ electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.47, electron-to-chromiu resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.533.tgz#d7e5ca4d57e9bc99af87efbe13e7be5dde729b0f" integrity sha512-YqAL+NXOzjBnpY+dcOKDlZybJDCOzgsq4koW3fvyty/ldTmsb4QazZpOWmVvZ2m0t5jbBf7L0lIGU3BUipwG+A== -electron-updater@4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.3.4.tgz#6003f88be9004d7834e4dd757167033d0fc2d29a" - integrity sha512-ekpgxDrYl+Wi24ktO4qfj2CtCABxrmK1C/oekp0tai6q4VR4ZdPkit4CX8+GenvKMme7uMmfPFnLp/vwhP/ThQ== +electron-updater@4.3.5: + version "4.3.5" + resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.3.5.tgz#4fb36f593a031c87ea07ee141c9f064d5deffb15" + integrity sha512-5jjN7ebvfj1cLI0VZMdCnJk6aC4bP+dy7ryBf21vArR0JzpRVk0OZHA2QBD+H5rm6ZSeDYHOY6+8PrMEqJ4wlQ== dependencies: "@types/semver" "^7.3.1" builder-util-runtime "8.7.2" @@ -7925,7 +8112,7 @@ enquirer@^2.3.6: dependencies: ansi-colors "^4.1.1" -entities@^1.1.1, entities@^1.1.2: +entities@^1.1.1, entities@^1.1.2, entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== @@ -8104,10 +8291,10 @@ escodegen@^1.11.0, escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" -eslint-config-prettier@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz#f6d2238c1290d01c859a8b5c1f7d352a0b0da8b1" - integrity sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA== +eslint-config-prettier@6.12.0: + version "6.12.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.12.0.tgz#9eb2bccff727db1c52104f0b49e87ea46605a0d2" + integrity sha512-9jWPlFlgNwRUYVoujvWTQ1aMO8o6648r+K7qU7K5Jmkbyqav1fuEZC0COYpGBxyiAJb65Ra9hrmFx19xRGwXWw== dependencies: get-stdin "^6.0.0" @@ -8118,7 +8305,7 @@ eslint-config-react-app@^5.2.1: dependencies: confusing-browser-globals "^1.0.9" -eslint-import-resolver-node@^0.3.2, eslint-import-resolver-node@^0.3.3: +eslint-import-resolver-node@^0.3.2, eslint-import-resolver-node@^0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== @@ -8170,17 +8357,17 @@ eslint-plugin-import@2.20.1: read-pkg-up "^2.0.0" resolve "^1.12.0" -eslint-plugin-import@2.22.0: - version "2.22.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" - integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg== +eslint-plugin-import@2.22.1: + version "2.22.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702" + integrity sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw== dependencies: array-includes "^3.1.1" array.prototype.flat "^1.2.3" contains-path "^0.1.0" debug "^2.6.9" doctrine "1.5.0" - eslint-import-resolver-node "^0.3.3" + eslint-import-resolver-node "^0.3.4" eslint-module-utils "^2.6.0" has "^1.0.3" minimatch "^3.0.4" @@ -8251,16 +8438,16 @@ eslint-plugin-react@7.19.0: string.prototype.matchall "^4.0.2" xregexp "^4.3.0" -eslint-plugin-react@^7.20.6: - version "7.20.6" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.6.tgz#4d7845311a93c463493ccfa0a19c9c5d0fd69f60" - integrity sha512-kidMTE5HAEBSLu23CUDvj8dc3LdBU0ri1scwHBZjI41oDv4tjsWZKU7MQccFzH1QYPYhsnTF2ovh7JlcIcmxgg== +eslint-plugin-react@^7.21.4: + version "7.21.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.4.tgz#31060b2e5ff82b12e24a3cc33edb7d12f904775c" + integrity sha512-uHeQ8A0hg0ltNDXFu3qSfFqTNPXm1XithH6/SY318UX76CMj7Q599qWpgmMhVQyvhq36pm7qvoN3pb6/3jsTFg== dependencies: array-includes "^3.1.1" array.prototype.flatmap "^1.2.3" doctrine "^2.1.0" has "^1.0.3" - jsx-ast-utils "^2.4.1" + jsx-ast-utils "^2.4.1 || ^3.0.0" object.entries "^1.1.2" object.fromentries "^2.0.2" object.values "^1.1.1" @@ -8413,7 +8600,7 @@ eth-block-tracker@^4.2.0, eth-block-tracker@^4.4.1, eth-block-tracker@^4.4.2: pify "^3.0.0" safe-event-emitter "^1.0.1" -eth-ens-namehash@2.0.8: +eth-ens-namehash@2.0.8, eth-ens-namehash@^2.0.0: version "2.0.8" resolved "https://registry.yarnpkg.com/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz#229ac46eca86d52e0c991e7cb2aef83ff0f68bcf" integrity sha1-IprEbsqG1S4MmR58sq74P/D2i88= @@ -8645,6 +8832,18 @@ ethereum-cryptography@^0.1.3: secp256k1 "^4.0.1" setimmediate "^1.0.5" +ethereum-ens@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/ethereum-ens/-/ethereum-ens-0.8.0.tgz#6d0f79acaa61fdbc87d2821779c4e550243d4c57" + integrity sha512-a8cBTF4AWw1Q1Y37V1LSCS9pRY4Mh3f8vCg5cbXCCEJ3eno1hbI/+Ccv9SZLISYpqQhaglP3Bxb/34lS4Qf7Bg== + dependencies: + bluebird "^3.4.7" + eth-ens-namehash "^2.0.0" + js-sha3 "^0.5.7" + pako "^1.0.4" + underscore "^1.8.3" + web3 "^1.0.0-beta.34" + ethereum-private-key-to-address@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/ethereum-private-key-to-address/-/ethereum-private-key-to-address-0.0.3.tgz#1f1dccaefd1198c2dcde55501f331a846bd0aad0" @@ -9356,10 +9555,10 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -final-form-calculate@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/final-form-calculate/-/final-form-calculate-1.3.1.tgz#463089114245afa97fea94712bfbfca11da8413e" - integrity sha512-vZCvQ08w9FIoHLkZMcJSIXQr5TAVLxHfLD0thmm50zcNyJESruqhgvurSjWYPLoJGnIgbIb94Rumdg5ZXX5WiQ== +final-form-calculate@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/final-form-calculate/-/final-form-calculate-1.3.2.tgz#a5e1908d1aa34eeec6faccdba3fd9516e7fd3d4f" + integrity sha512-pon2K9yNbyqmF8UTpDvxwhk+Hvqpl8Fm3qgwkHniNAmCQe+6YxB1aw4cBAHzmRc39jGl2bYsvKyabQOIWLtrPg== final-form@^4.20.1: version "4.20.1" @@ -10221,7 +10420,7 @@ hdkey@^2.0.1: safe-buffer "^5.1.1" secp256k1 "^4.0.0" -he@1.2.0, he@^1.2.0: +he@1.2.0, he@^1.1.1, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -10231,11 +10430,21 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== +highlight.js@^9.12.0, highlight.js@^9.15.8: + version "9.18.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.3.tgz#a1a0a2028d5e3149e2380f8a865ee8516703d634" + integrity sha512-zBZAmhSupHIl5sITeMqIJnYCDfAEc3Gdkqj65wC1lpI468MMQeeQkhcIAvk+RylAkxrCcI9xy9piHiXeQ1BdzQ== + highlight.js@~9.13.0: version "9.13.1" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e" integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A== +highlightjs-solidity@^1.0.18: + version "1.0.18" + resolved "https://registry.yarnpkg.com/highlightjs-solidity/-/highlightjs-solidity-1.0.18.tgz#3deb0593689a26fbadf98e631bf2cd305a6417c9" + integrity sha512-k15h0br4oCRT0F0jTRuZbimerVt5V4n0k25h7oWi0kVqlBNeXPbSr5ddw02/2ukJmYfB8jauFDmxSauJjwM7Eg== + history@4.10.1, history@^4.9.0: version "4.10.1" resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" @@ -10371,7 +10580,7 @@ html-webpack-plugin@^4.0.0-beta.2: tapable "^1.1.3" util.promisify "1.0.0" -htmlparser2@^3.3.0: +htmlparser2@^3.3.0, htmlparser2@^3.9.1: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== @@ -10473,15 +10682,24 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== -husky@^4.2.5: - version "4.2.5" - resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36" - integrity sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ== +husky@^0.14.3: + version "0.14.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-0.14.3.tgz#c69ed74e2d2779769a17ba8399b54ce0b63c12c3" + integrity sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA== + dependencies: + is-ci "^1.0.10" + normalize-path "^1.0.0" + strip-indent "^2.0.0" + +husky@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.0.tgz#0b2ec1d66424e9219d359e26a51c58ec5278f0de" + integrity sha512-tTMeLCLqSBqnflBZnlVDhpaIMucSGaYyX6855jM4AguGeWCeSzNdb1mfyWduTZ3pe3SJVvVWGL0jO1iKZVPfTA== dependencies: chalk "^4.0.0" ci-info "^2.0.0" compare-versions "^3.6.0" - cosmiconfig "^6.0.0" + cosmiconfig "^7.0.0" find-versions "^3.2.0" opencollective-postinstall "^2.0.2" pkg-dir "^4.2.0" @@ -10534,7 +10752,7 @@ idna-uts46-hx@^2.3.1: dependencies: punycode "2.1.0" -ieee754@^1.1.4: +ieee754@^1.1.13, ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== @@ -10872,6 +11090,13 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.0: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== +is-ci@^1.0.10: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" + integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== + dependencies: + ci-info "^1.5.0" + is-ci@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -11272,6 +11497,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +iso-url@~0.4.7: + version "0.4.7" + resolved "https://registry.yarnpkg.com/iso-url/-/iso-url-0.4.7.tgz#de7e48120dae46921079fe78f325ac9e9217a385" + integrity sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog== + isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" @@ -12003,6 +12233,13 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= +json-text-sequence@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/json-text-sequence/-/json-text-sequence-0.1.1.tgz#a72f217dc4afc4629fff5feb304dc1bd51a2f3d2" + integrity sha1-py8hfcSvxGKf/1/rME3BvVGi89I= + dependencies: + delimit-stream "0.1.0" + json2mq@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a" @@ -12155,6 +12392,14 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3, jsx-ast-utils@^2.4.1: array-includes "^3.1.1" object.assign "^4.1.0" +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891" + integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA== + dependencies: + array-includes "^3.1.1" + object.assign "^4.1.1" + just-curry-it@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/just-curry-it/-/just-curry-it-3.1.0.tgz#ab59daed308a58b847ada166edd0a2d40766fbc5" @@ -12387,7 +12632,7 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -lint-staged@10.4.0: +lint-staged@^10.4.0: version "10.4.0" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.4.0.tgz#d18628f737328e0bbbf87d183f4020930e9a984e" integrity sha512-uaiX4U5yERUSiIEQc329vhCTDDwUcSvKdRLsNomkYLRzijk3v8V9GWm2Nz0RMVB87VcuzLvtgy6OsjoH++QHIg== @@ -12521,6 +12766,11 @@ lodash._reinterpolate@^3.0.0: resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -12531,6 +12781,11 @@ lodash.defaults@^4.2.0: resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= +lodash.escaperegexp@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" + integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= + lodash.flatmap@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e" @@ -12556,6 +12811,16 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.partition@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.partition/-/lodash.partition-4.6.0.tgz#a38e46b73469e0420b0da1212e66d414be364ba4" + integrity sha1-o45GtzRp4EILDaEhLmbUFL42S6Q= + lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" @@ -12566,6 +12831,11 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= +lodash.sum@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/lodash.sum/-/lodash.sum-4.0.2.tgz#ad90e397965d803d4f1ff7aa5b2d0197f3b4637b" + integrity sha1-rZDjl5ZdgD1PH/eqWy0Bl/O0Y3s= + lodash.template@^4.4.0, lodash.template@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" @@ -12596,7 +12866,7 @@ lodash.unset@^4.5.2: resolved "https://registry.yarnpkg.com/lodash.unset/-/lodash.unset-4.5.2.tgz#370d1d3e85b72a7e1b0cdf2d272121306f23e4ed" integrity sha1-Nw0dPoW3Kn4bDN8tJyEhMG8j5O0= -"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: +"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -13458,6 +13728,11 @@ node-gyp-build@^4.2.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739" integrity sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg== +node-gyp-build@~3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d" + integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w== + node-gyp@^3.8.0, node-gyp@^5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-5.1.1.tgz#eb915f7b631c937d282e33aed44cb7a025f62a3e" @@ -13475,7 +13750,7 @@ node-gyp@^3.8.0, node-gyp@^5.1.0: tar "^4.4.12" which "^1.3.1" -node-hid@^1.3.0: +node-hid@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/node-hid/-/node-hid-1.3.0.tgz#346a468505cee13d69ccd760052cbaf749f66a41" integrity sha512-BA6G4V84kiNd1uAChub/Z/5s/xS3EHBCxotQ0nyYrUG65mXewUDHE1tWOSqA2dp3N+mV0Ffq9wo2AW9t4p/G7g== @@ -13593,6 +13868,11 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package- semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" +normalize-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-1.0.0.tgz#32d0e472f91ff345701c15a8311018d3b0a90379" + integrity sha1-MtDkcvkf80VwHBWoMRAY07CpA3k= + normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -13784,6 +14064,16 @@ object.assign@4.1.0, object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" +object.assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" + integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.0" + has-symbols "^1.0.1" + object-keys "^1.1.1" + object.entries@^1.1.0, object.entries@^1.1.1, object.entries@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" @@ -13840,6 +14130,13 @@ oboe@2.1.4: dependencies: http-https "^1.0.0" +oboe@2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/oboe/-/oboe-2.1.5.tgz#5554284c543a2266d7a38f17e073821fbde393cd" + integrity sha1-VVQoTFQ6ImbXo48X4HOCH73jk80= + dependencies: + http-https "^1.0.0" + obs-store@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/obs-store/-/obs-store-4.0.3.tgz#b632ec7814baa604fae084a4c97e87c0b7a6d14c" @@ -13903,14 +14200,6 @@ open@^7.0.0, open@^7.0.2: is-docker "^2.0.0" is-wsl "^2.1.1" -open@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/open/-/open-7.2.0.tgz#212959bd7b0ce2e8e3676adc76e3cf2f0a2498b4" - integrity sha512-4HeyhxCvBTI5uBePsAdi55C5fmqnWZ2e2MlmvWi5KW5tdH5rxoiv/aMtbeVxKZc3eWkT1GymMnLG8XC4Rq4TDQ== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - opencollective-postinstall@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" @@ -14098,7 +14387,7 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" -pako@~1.0.5: +pako@^1.0.4, pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== @@ -14190,6 +14479,13 @@ parse5@5.1.0: resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== +parse5@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" + integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA== + dependencies: + "@types/node" "*" + parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -15533,10 +15829,10 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@6.13.1: - version "6.13.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.1.tgz#d913ccfce3b4b3a713989fe6d39466d92e71ccad" - integrity sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA== +query-string@6.13.5: + version "6.13.5" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.5.tgz#99e95e2fb7021db90a6f373f990c0c814b3812d8" + integrity sha512-svk3xg9qHR39P3JlHuD7g3nRnyay5mHbrPctEBDUxUkHRifPHXJDhBUycdCC0NBjXoDf44Gb+IsOZL1Uwn8M/Q== dependencies: decode-uri-component "^0.2.0" split-on-first "^1.0.0" @@ -15846,10 +16142,10 @@ react-helmet-async@^1.0.2: react-fast-compare "^3.0.1" shallowequal "^1.1.0" -react-hot-loader@4.12.21: - version "4.12.21" - resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.21.tgz#332e830801fb33024b5a147d6b13417f491eb975" - integrity sha512-Ynxa6ROfWUeKWsTHxsrL2KMzujxJVPjs385lmB2t5cHUxdoRPGind9F00tOkdc1l5WBleOF4XEAMILY1KPIIDA== +react-hot-loader@4.13.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.13.0.tgz#c27e9408581c2a678f5316e69c061b226dc6a202" + integrity sha512-JrLlvUPqh6wIkrK2hZDfOyq/Uh/WeVEr8nc7hkn2/3Ul0sx1Kr5y4kOGNacNRoj7RhwLNcQ3Udf1KJXrqc0ZtA== dependencies: fast-levenshtein "^2.0.6" global "^4.3.0" @@ -16781,6 +17077,13 @@ rxjs@^6.4.0, rxjs@^6.5.2, rxjs@^6.5.3, rxjs@^6.5.4, rxjs@^6.5.5, rxjs@^6.6.0, rx dependencies: tslib "^1.9.0" +rxjs@^6.6.3: + version "6.6.3" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" + integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ== + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -18045,6 +18348,11 @@ sumchecker@^3.0.1: dependencies: debug "^4.1.0" +super-split@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/super-split/-/super-split-1.1.0.tgz#43b3ba719155f4d43891a32729d59b213d9155fc" + integrity sha512-I4bA5mgcb6Fw5UJ+EkpzqXfiuvVGS/7MuND+oBxNFmxu3ugLNrdIatzBLfhFRMVMLxgSsRy+TjIktgkF9RFSNQ== + supports-color@7.1.0, supports-color@^7.0.0, supports-color@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" @@ -18551,32 +18859,6 @@ trim-right@^1.0.1: dependencies: glob "^7.1.2" -truffle-contract@4.0.31: - version "4.0.31" - resolved "https://registry.yarnpkg.com/truffle-contract/-/truffle-contract-4.0.31.tgz#e43b7f648e2db352c857d1202d710029b107b68d" - integrity sha512-u3q+p1wiX5C2GpnluGx/d2iaJk7bcWshk2/TohiJyA2iQiTfkS7M4n9D9tY3JqpXR8PmD/TrA69RylO0RhITFA== - dependencies: - "@truffle/blockchain-utils" "^0.0.11" - "@truffle/contract-schema" "^3.0.14" - "@truffle/error" "^0.0.6" - bignumber.js "^7.2.1" - ethers "^4.0.0-beta.1" - truffle-interface-adapter "^0.2.5" - web3 "1.2.1" - web3-core-promievent "1.2.1" - web3-eth-abi "1.2.1" - web3-utils "1.2.1" - -truffle-interface-adapter@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/truffle-interface-adapter/-/truffle-interface-adapter-0.2.5.tgz#aa0bee635517b4a8e06adcdc99eacb993e68c243" - integrity sha512-EL39OpP8FcZ99ne1Rno3jImfb92Nectd4iVsZzoEUCBfbwHe7sr0k+i45guoruSoP8nMUE81Mov2s8I5pi6d9Q== - dependencies: - bn.js "^4.11.8" - ethers "^4.0.32" - lodash "^4.17.13" - web3 "1.2.1" - truffle@^5.1.21: version "5.1.40" resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.1.40.tgz#f93a147612b3b083354808dd0abbe6723bd04f7f" @@ -18800,6 +19082,11 @@ underscore@1.9.1: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== +underscore@^1.8.3: + version "1.11.0" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.11.0.tgz#dd7c23a195db34267186044649870ff1bab5929e" + integrity sha512-xY96SsN3NA461qIRKZ/+qox37YXPtSBswMGfiNptr+wrt6ds4HaMw23TP612fEyGekRE6LNRiLYr/aqbHXNedw== + unfetch@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.1.0.tgz#6ec2dd0de887e58a4dee83a050ded80ffc4137db" @@ -19018,6 +19305,13 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== +utf-8-validate@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.2.tgz#63cfbccd85dc1f2b66cf7a1d0eebc08ed056bfb3" + integrity sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw== + dependencies: + node-gyp-build "~3.7.0" + utf8-byte-length@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" @@ -19260,6 +19554,16 @@ web3-bzz@1.2.9: swarm-js "^0.1.40" underscore "1.9.1" +web3-bzz@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.3.0.tgz#83dfd77fa8a64bbb660462dffd0fee2a02ef1051" + integrity sha512-ibYAnKab+sgTo/UdfbrvYfWblXjjgSMgyy9/FHa6WXS14n/HVB+HfWqGz2EM3fok8Wy5XoKGMvdqvERQ/mzq1w== + dependencies: + "@types/node" "^12.12.6" + got "9.6.0" + swarm-js "^0.1.40" + underscore "1.9.1" + web3-core-helpers@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.2.1.tgz#f5f32d71c60a4a3bd14786118e633ce7ca6d5d0d" @@ -19287,6 +19591,15 @@ web3-core-helpers@1.2.9: web3-eth-iban "1.2.9" web3-utils "1.2.9" +web3-core-helpers@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.3.0.tgz#697cc3246a7eaaaac64ea506828d861c981c3f31" + integrity sha512-+MFb1kZCrRctf7UYE7NCG4rGhSXaQJ/KF07di9GVK1pxy1K0+rFi61ZobuV1ky9uQp+uhhSPts4Zp55kRDB5sw== + dependencies: + underscore "1.9.1" + web3-eth-iban "1.3.0" + web3-utils "1.3.0" + web3-core-method@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.2.1.tgz#9df1bafa2cd8be9d9937e01c6a47fc768d15d90a" @@ -19322,6 +19635,18 @@ web3-core-method@1.2.9: web3-core-subscriptions "1.2.9" web3-utils "1.2.9" +web3-core-method@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.3.0.tgz#a71387af842aec7dbad5dbbd1130c14cc6c8beb3" + integrity sha512-h0yFDrYVzy5WkLxC/C3q+hiMnzxdWm9p1T1rslnuHgOp6nYfqzu/6mUIXrsS4h/OWiGJt+BZ0xVZmtC31HDWtg== + dependencies: + "@ethersproject/transactions" "^5.0.0-beta.135" + underscore "1.9.1" + web3-core-helpers "1.3.0" + web3-core-promievent "1.3.0" + web3-core-subscriptions "1.3.0" + web3-utils "1.3.0" + web3-core-promievent@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.2.1.tgz#003e8a3eb82fb27b6164a6d5b9cad04acf733838" @@ -19344,6 +19669,13 @@ web3-core-promievent@1.2.9: dependencies: eventemitter3 "3.1.2" +web3-core-promievent@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.3.0.tgz#e0442dd0a8989b6bdce09293976cee6d9237a484" + integrity sha512-blv69wrXw447TP3iPvYJpllkhW6B18nfuEbrfcr3n2Y0v1Jx8VJacNZFDFsFIcgXcgUIVCtOpimU7w9v4+rtaw== + dependencies: + eventemitter3 "4.0.4" + web3-core-requestmanager@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.2.1.tgz#fa2e2206c3d738db38db7c8fe9c107006f5c6e3d" @@ -19377,6 +19709,17 @@ web3-core-requestmanager@1.2.9: web3-providers-ipc "1.2.9" web3-providers-ws "1.2.9" +web3-core-requestmanager@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.3.0.tgz#c5b9a0304504c0e6cce6c90bc1a3bff82732aa1f" + integrity sha512-3yMbuGcomtzlmvTVqNRydxsx7oPlw3ioRL6ReF9PeNYDkUsZaUib+6Dp5eBt7UXh5X+SIn/xa1smhDHz5/HpAw== + dependencies: + underscore "1.9.1" + web3-core-helpers "1.3.0" + web3-providers-http "1.3.0" + web3-providers-ipc "1.3.0" + web3-providers-ws "1.3.0" + web3-core-subscriptions@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.2.1.tgz#8c2368a839d4eec1c01a4b5650bbeb82d0e4a099" @@ -19404,6 +19747,15 @@ web3-core-subscriptions@1.2.9: underscore "1.9.1" web3-core-helpers "1.2.9" +web3-core-subscriptions@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.3.0.tgz#c2622ccd2b84f4687475398ff966b579dba0847e" + integrity sha512-MUUQUAhJDb+Nz3S97ExVWveH4utoUnsbPWP+q1HJH437hEGb4vunIb9KvN3hFHLB+aHJfPeStM/4yYTz5PeuyQ== + dependencies: + eventemitter3 "4.0.4" + underscore "1.9.1" + web3-core-helpers "1.3.0" + web3-core@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.2.1.tgz#7278b58fb6495065e73a77efbbce781a7fddf1a9" @@ -19440,6 +19792,19 @@ web3-core@1.2.9: web3-core-requestmanager "1.2.9" web3-utils "1.2.9" +web3-core@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.3.0.tgz#b818903738461c1cca0163339e1d6d3fa51242cf" + integrity sha512-BwWvAaKJf4KFG9QsKRi3MNoNgzjI6szyUlgme1qNPxUdCkaS3Rdpa0VKYNHP7M/YTk82/59kNE66mH5vmoaXjA== + dependencies: + "@types/bn.js" "^4.11.5" + "@types/node" "^12.12.6" + bignumber.js "^9.0.0" + web3-core-helpers "1.3.0" + web3-core-method "1.3.0" + web3-core-requestmanager "1.3.0" + web3-utils "1.3.0" + web3-eth-abi@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.1.tgz#9b915b1c9ebf82f70cca631147035d5419064689" @@ -19467,6 +19832,15 @@ web3-eth-abi@1.2.9: underscore "1.9.1" web3-utils "1.2.9" +web3-eth-abi@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.3.0.tgz#387b7ea9b38be69ad8856bc7b4e9a6a69bb4d22b" + integrity sha512-1OrZ9+KGrBeBRd3lO8upkpNua9+7cBsQAgor9wbA25UrcUYSyL8teV66JNRu9gFxaTbkpdrGqM7J/LXpraXWrg== + dependencies: + "@ethersproject/abi" "5.0.0-beta.153" + underscore "1.9.1" + web3-utils "1.3.0" + web3-eth-accounts@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.2.1.tgz#2741a8ef337a7219d57959ac8bd118b9d68d63cf" @@ -19518,6 +19892,23 @@ web3-eth-accounts@1.2.9: web3-core-method "1.2.9" web3-utils "1.2.9" +web3-eth-accounts@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.3.0.tgz#010acf389b2bee6d5e1aecb2fe78bfa5c8f26c7a" + integrity sha512-/Q7EVW4L2wWUbNRtOTwAIrYvJid/5UnKMw67x/JpvRMwYC+e+744P536Ja6SG4X3MnzFvd3E/jruV4qa6k+zIw== + dependencies: + crypto-browserify "3.12.0" + eth-lib "0.2.8" + ethereumjs-common "^1.3.2" + ethereumjs-tx "^2.1.1" + scrypt-js "^3.0.1" + underscore "1.9.1" + uuid "3.3.2" + web3-core "1.3.0" + web3-core-helpers "1.3.0" + web3-core-method "1.3.0" + web3-utils "1.3.0" + web3-eth-contract@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.2.1.tgz#3542424f3d341386fd9ff65e78060b85ac0ea8c4" @@ -19562,6 +19953,21 @@ web3-eth-contract@1.2.9: web3-eth-abi "1.2.9" web3-utils "1.2.9" +web3-eth-contract@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.3.0.tgz#c758340ac800788e29fa29edc8b0c0ac957b741c" + integrity sha512-3SCge4SRNCnzLxf0R+sXk6vyTOl05g80Z5+9/B5pERwtPpPWaQGw8w01vqYqsYBKC7zH+dxhMaUgVzU2Dgf7bQ== + dependencies: + "@types/bn.js" "^4.11.5" + underscore "1.9.1" + web3-core "1.3.0" + web3-core-helpers "1.3.0" + web3-core-method "1.3.0" + web3-core-promievent "1.3.0" + web3-core-subscriptions "1.3.0" + web3-eth-abi "1.3.0" + web3-utils "1.3.0" + web3-eth-ens@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.2.1.tgz#a0e52eee68c42a8b9865ceb04e5fb022c2d971d5" @@ -19606,6 +20012,21 @@ web3-eth-ens@1.2.9: web3-eth-contract "1.2.9" web3-utils "1.2.9" +web3-eth-ens@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.3.0.tgz#0887ba38473c104cf5fb8a715828b3b354fa02a2" + integrity sha512-WnOru+EcuM5dteiVYJcHXo/I7Wq+ei8RrlS2nir49M0QpYvUPGbCGgTbifcjJQTWamgORtWdljSA1s2Asdb74w== + dependencies: + content-hash "^2.5.2" + eth-ens-namehash "2.0.8" + underscore "1.9.1" + web3-core "1.3.0" + web3-core-helpers "1.3.0" + web3-core-promievent "1.3.0" + web3-eth-abi "1.3.0" + web3-eth-contract "1.3.0" + web3-utils "1.3.0" + web3-eth-iban@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.2.1.tgz#2c3801718946bea24e9296993a975c80b5acf880" @@ -19630,6 +20051,14 @@ web3-eth-iban@1.2.9: bn.js "4.11.8" web3-utils "1.2.9" +web3-eth-iban@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.3.0.tgz#15b782dfaf273ebc4e3f389f1367f4e88ddce4a5" + integrity sha512-v9mZWhR4fPF17/KhHLiWir4YHWLe09O3B/NTdhWqw3fdAMJNztzMHGzgHxA/4fU+rhrs/FhDzc4yt32zMEXBZw== + dependencies: + bn.js "^4.11.9" + web3-utils "1.3.0" + web3-eth-personal@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.2.1.tgz#244e9911b7b482dc17c02f23a061a627c6e47faf" @@ -19665,6 +20094,18 @@ web3-eth-personal@1.2.9: web3-net "1.2.9" web3-utils "1.2.9" +web3-eth-personal@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.3.0.tgz#d376e03dc737d961ff1f8d1aca866efad8477135" + integrity sha512-2czUhElsJdLpuNfun9GeLiClo5O6Xw+bLSjl3f4bNG5X2V4wcIjX2ygep/nfstLLtkz8jSkgl/bV7esANJyeRA== + dependencies: + "@types/node" "^12.12.6" + web3-core "1.3.0" + web3-core-helpers "1.3.0" + web3-core-method "1.3.0" + web3-net "1.3.0" + web3-utils "1.3.0" + web3-eth@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.2.1.tgz#b9989e2557c73a9e8ffdc107c6dafbe72c79c1b0" @@ -19722,6 +20163,25 @@ web3-eth@1.2.9: web3-net "1.2.9" web3-utils "1.2.9" +web3-eth@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.3.0.tgz#898e5f5a8827f9bc6844e267a52eb388916a6771" + integrity sha512-/bzJcxXPM9EM18JM5kO2JjZ3nEqVo3HxqU93aWAEgJNqaP/Lltmufl2GpvIB2Hvj+FXAjAXquxUdQ2/xP7BzHQ== + dependencies: + underscore "1.9.1" + web3-core "1.3.0" + web3-core-helpers "1.3.0" + web3-core-method "1.3.0" + web3-core-subscriptions "1.3.0" + web3-eth-abi "1.3.0" + web3-eth-accounts "1.3.0" + web3-eth-contract "1.3.0" + web3-eth-ens "1.3.0" + web3-eth-iban "1.3.0" + web3-eth-personal "1.3.0" + web3-net "1.3.0" + web3-utils "1.3.0" + web3-net@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.2.1.tgz#edd249503315dd5ab4fa00220f6509d95bb7ab10" @@ -19749,6 +20209,15 @@ web3-net@1.2.9: web3-core-method "1.2.9" web3-utils "1.2.9" +web3-net@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.3.0.tgz#b69068cccffab58911c2f08ca4abfbefb0f948c6" + integrity sha512-Xz02KylOyrB2YZzCkysEDrY7RbKxb7LADzx3Zlovfvuby7HBwtXVexXKtoGqksa+ns1lvjQLLQGb+OeLi7Sr7w== + dependencies: + web3-core "1.3.0" + web3-core-method "1.3.0" + web3-utils "1.3.0" + web3-provider-engine@15.0.12, web3-provider-engine@^15.0.4: version "15.0.12" resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-15.0.12.tgz#24d7f2f6fb6de856824c7306291018c4fc543ac3" @@ -19829,6 +20298,14 @@ web3-providers-http@1.2.9: web3-core-helpers "1.2.9" xhr2-cookies "1.1.0" +web3-providers-http@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.3.0.tgz#88227f64c88b32abed4359383c2663616e0dc531" + integrity sha512-cMKhUI6PqlY/EC+ZDacAxajySBu8AzW8jOjt1Pe/mbRQgS0rcZyvLePGTTuoyaA8C21F8UW+EE5jj7YsNgOuqA== + dependencies: + web3-core-helpers "1.3.0" + xhr2-cookies "1.1.0" + web3-providers-ipc@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.2.1.tgz#017bfc687a8fc5398df2241eb98f135e3edd672c" @@ -19856,6 +20333,15 @@ web3-providers-ipc@1.2.9: underscore "1.9.1" web3-core-helpers "1.2.9" +web3-providers-ipc@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.3.0.tgz#d7c2b203733b46f7b4e7b15633d891648cf9a293" + integrity sha512-0CrLuRofR+1J38nEj4WsId/oolwQEM6Yl1sOt41S/6bNI7htdkwgVhSloFIMJMDFHtRw229QIJ6wIaKQz0X1Og== + dependencies: + oboe "2.1.5" + underscore "1.9.1" + web3-core-helpers "1.3.0" + web3-providers-ws@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.2.1.tgz#2d941eaf3d5a8caa3214eff8dc16d96252b842cb" @@ -19885,6 +20371,16 @@ web3-providers-ws@1.2.9: web3-core-helpers "1.2.9" websocket "^1.0.31" +web3-providers-ws@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.3.0.tgz#84adeff65acd4624d7f5bb43c5b2b22d8f0f63a4" + integrity sha512-Im5MthhJnJst8nSoq0TgbyOdaiFQFa5r6sHPOVllhgIgViDqzbnlAFW9sNzQ0Q8VXPNfPIQKi9cOrHlSRNPjRw== + dependencies: + eventemitter3 "4.0.4" + underscore "1.9.1" + web3-core-helpers "1.3.0" + websocket "^1.0.32" + web3-shh@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.2.1.tgz#4460e3c1e07faf73ddec24ccd00da46f89152b0c" @@ -19915,6 +20411,16 @@ web3-shh@1.2.9: web3-core-subscriptions "1.2.9" web3-net "1.2.9" +web3-shh@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.3.0.tgz#62d15297da8fb5f733dd1b98f9ade300590f4d49" + integrity sha512-IZTojA4VCwVq+7eEIHuL1tJXtU+LJDhO8Y2QmuwetEWW1iBgWCGPHZasipWP+7kDpSm/5lo5GRxL72FF/Os/tA== + dependencies: + web3-core "1.3.0" + web3-core-method "1.3.0" + web3-core-subscriptions "1.3.0" + web3-net "1.3.0" + web3-utils@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.1.tgz#21466e38291551de0ab34558de21512ac4274534" @@ -19956,6 +20462,20 @@ web3-utils@1.2.9: underscore "1.9.1" utf8 "3.0.0" +web3-utils@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.3.0.tgz#5bac16e5e0ec9fe7bdcfadb621655e8aa3cf14e1" + integrity sha512-2mS5axFCbkhicmoDRuJeuo0TVGQDgC2sPi/5dblfVC+PMtX0efrb8Xlttv/eGkq7X4E83Pds34FH98TP2WOUZA== + dependencies: + bn.js "^4.11.9" + eth-lib "0.2.8" + ethereum-bloom-filters "^1.0.6" + ethjs-unit "0.1.6" + number-to-bn "1.7.0" + randombytes "^2.1.0" + underscore "1.9.1" + utf8 "3.0.0" + web3@*: version "1.2.11" resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.11.tgz#50f458b2e8b11aa37302071c170ed61cff332975" @@ -20006,6 +20526,19 @@ web3@^0.20.7: xhr2-cookies "^1.1.0" xmlhttprequest "*" +web3@^1.0.0-beta.34: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3/-/web3-1.3.0.tgz#8fe4cd6e2a21c91904f343ba75717ee4c76bb349" + integrity sha512-4q9dna0RecnrlgD/bD1C5S+81Untbd6Z/TBD7rb+D5Bvvc0Wxjr4OP70x+LlnwuRDjDtzBwJbNUblh2grlVArw== + dependencies: + web3-bzz "1.3.0" + web3-core "1.3.0" + web3-eth "1.3.0" + web3-eth-personal "1.3.0" + web3-net "1.3.0" + web3-shh "1.3.0" + web3-utils "1.3.0" + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -20212,6 +20745,18 @@ websocket@^1.0.31: typedarray-to-buffer "^3.1.5" yaeti "^0.0.6" +websocket@^1.0.32: + version "1.0.32" + resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.32.tgz#1f16ddab3a21a2d929dec1687ab21cfdc6d3dbb1" + integrity sha512-i4yhcllSP4wrpoPMU2N0TQ/q0O94LRG/eUQjEAamRltjQ1oT1PFFKOG4i877OlJgCG8rw6LrrowJp+TYCEWF7Q== + dependencies: + bufferutil "^4.0.1" + debug "^2.2.0" + es5-ext "^0.10.50" + typedarray-to-buffer "^3.1.5" + utf-8-validate "^5.0.2" + yaeti "^0.0.6" + "websocket@github:web3-js/WebSocket-Node#polyfill/globalThis": version "1.0.29" resolved "https://codeload.github.com/web3-js/WebSocket-Node/tar.gz/ef5ea2f41daf4a2113b80c9223df884b4d56c400"