diff --git a/.travis.yml b/.travis.yml index 0804243db5..f374e0adf6 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 (feature/#1353-xDai-compatibility) OR (type = pull_request) OR (tag IS present) sudo: required dist: bionic language: node_js @@ -16,6 +16,10 @@ matrix: - env: - REACT_APP_NETWORK='rinkeby' - REACT_APP_GNOSIS_APPS_URL=${REACT_APP_GNOSIS_APPS_URL_STAGING} + - env: + - REACT_APP_NETWORK='xdai' + - STAGING_BUCKET_NAME=${STAGING_XDAI_BUCKET_NAME} + if: ((branch = master OR branch = feature/#1353-xDai-compatibility) AND NOT type = pull_request) OR tag IS present cache: yarn: true before_script: @@ -62,7 +66,9 @@ deploy: upload-dir: current/app region: $AWS_DEFAULT_REGION on: - branch: master + # branch: master + all_branches: true + condition: $TRAVIS_BRANCH =~ ^(master|feature/#1353-xDai-compatibility)$ # Prepare production deployment - provider: s3 diff --git a/package.json b/package.json index 4ecdde6b2d..311da5491d 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,7 @@ "src/**/*.{js,jsx,ts,tsx}", "!src/**/*.{.test.*}", "!src/**/test/**/*", - "!src/**/assets/**", - "!src/config/**/*" + "!src/**/assets/**" ] }, "productName": "Safe Multisig", @@ -166,7 +165,7 @@ "dependencies": { "@gnosis.pm/safe-apps-sdk": "0.4.0", "@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", "@material-ui/core": "4.11.0", diff --git a/src/components/AddressInfo/index.tsx b/src/components/AddressInfo/index.tsx index efd40e50e0..99f94a0903 100644 --- a/src/components/AddressInfo/index.tsx +++ b/src/components/AddressInfo/index.tsx @@ -1,5 +1,4 @@ import React from 'react' -import styled from 'styled-components' import CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' @@ -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; @@ -59,7 +59,7 @@ const AddressInfo = ({ ethBalance, safeAddress, safeName }: Props): React.ReactE {safeAddress} - + {ethBalance && ( diff --git a/src/components/App/ReceiveModal.tsx b/src/components/App/ReceiveModal.tsx index 535a50bd7f..f91d341cc7 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,6 +101,9 @@ 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! @@ -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 ee90992af8..a916ac985b 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 2ae0fe65b7..83ce6a0d2a 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, -}) +type Props = { + className: string +} -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 +export const CircleDot = (props: Props): React.ReactElement => { + const networkInfo = getNetworkInfo() return ( - <> - - - Status connection - - {!hideDot && } - - +
+ + + +
) } - -export default withStyles(styles as any)(KeyRing) diff --git a/src/components/AppLayout/Header/components/KeyRing.tsx b/src/components/AppLayout/Header/components/KeyRing.tsx new file mode 100644 index 0000000000..10c39b7494 --- /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 594c0ceef6..edd88fe3a6 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 76300a966a..0b52d4e596 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 1319e05f9d..0f3ff77f0d 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 07989c052a..563c3abbb6 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 c038f916da..bb8e57fc9a 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 9d3c2f3f6f..48e1c7fc7c 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' +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 ( - - - - - - - - - - {safeName} - - - - - - - - - - {granted ? null : ( - - - READ ONLY + <> + + {networkInfo.label} + + + + + + + + + + + {safeName} + + + + + + + + + + {granted ? null : ( + + + READ ONLY + + + )} + + {balance} + + + + New Transaction - - )} - - {balance} - - - - New Transaction - - - + + + ) } diff --git a/src/components/ConnectButton/index.tsx b/src/components/ConnectButton/index.tsx index a719776f04..a520594bf8 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 c81d0be868..f760948943 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 fe33a953d2..e37beeefea 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 feb7d42370..dbb5b2a0f5 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 0000000000..dac6c17a6d --- /dev/null +++ b/src/components/SafeListSidebar/SafeList/AddresWrapper.tsx @@ -0,0 +1,78 @@ +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' + +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 +} + +export const AddressWrapper = (props: Props): React.ReactElement => { + const classes = useStyles() + const { safe, defaultSafe, setDefaultSafe } = props + + return ( +
+ + +
+ {`${formatAmount(safe.ethBalance)} ETH`} + {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 23ecd43609..57b60238da 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/config/__tests__/config.test.ts b/src/config/__tests__/config.test.ts new file mode 100644 index 0000000000..021a4a20e7 --- /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 a26a7075ae..0000000000 --- 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 fa26777e48..0000000000 --- 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 929cbf1b38..2560cb2af2 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' - -const configuration = () => { - if (process.env.NODE_ENV === 'test') { - return testConfig - } +import memoize from 'lodash.memoize' - if (process.env.NODE_ENV === 'production') { - if (process.env.REACT_APP_NETWORK === 'mainnet') { - return process.env.REACT_APP_ENV === 'production' - ? mainnetProdConfig - : mainnetStagingConfig - } +export const getNetworkId = (): ETHEREUM_NETWORK => ETHEREUM_NETWORK[NETWORK] + +export const getNetworkName = (): string => ETHEREUM_NETWORK[getNetworkId()] - return process.env.REACT_APP_ENV === 'production' - ? prodConfig - : stagingConfig +const getCurrentEnvironment = (): string => { + switch (NODE_ENV) { + case 'test': { + return 'test' + } + case 'production': { + return APP_ENV === 'production' ? 'production' : 'staging' + } + default: { + return 'dev' + } } +} - return process.env.REACT_APP_NETWORK === 'mainnet' - ? mainnetDevConfig - : devConfig +type NetworkSpecificConfiguration = EnvironmentSettings & { + network: NetworkSettings, + features?: SafeFeatures, } -export const getNetwork = () => - process.env.REACT_APP_NETWORK === 'mainnet' - ? ETHEREUM_NETWORK.MAINNET - : ETHEREUM_NETWORK.RINKEBY +const configuration = (): NetworkSpecificConfiguration => { + const currentEnvironment = getCurrentEnvironment() -export const getNetworkId = () => - process.env.REACT_APP_NETWORK === 'mainnet' ? 1 : 4 + // special case for test environment + if (currentEnvironment === 'test') { + const configFile = networks.local -const getConfig = ensureOnce(configuration) + return { + ...configFile.environment.production, + network: configFile.network, + features: configFile.features, + } + } -export const getTxServiceHost = () => { - const config = getConfig() + // 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 config[TX_SERVICE_HOST] + return { + ...networkBaseConfig, + network: configFile.network, + features: configFile.features, + } } -export const getTxServiceUriFrom = (safeAddress) => - `safes/${safeAddress}/transactions/` +const getConfig: () => NetworkSpecificConfiguration = ensureOnce(configuration) -export const getIncomingTxServiceUriTo = (safeAddress) => - `safes/${safeAddress}/incoming-transfers/` +export const getTxServiceUrl = (): string => getConfig()?.txServiceUrl -export const getAllTransactionsUriFrom = (safeAddress: string): string => - `safes/${safeAddress}/all-transactions/` +export const getRelayUrl = (): string | undefined => getConfig()?.relayApiUrl -export const getSafeCreationTxUri = (safeAddress) => `safes/${safeAddress}/creation/` +export const getGnosisSafeAppsUrl = (): string => getConfig()?.safeAppsUrl -export const getRelayUrl = () => getConfig()[RELAY_API_URL] +export const getRpcServiceUrl = (): string => { + const usesInfuraRPC = [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY].includes(getNetworkId()) -export const signaturesViaMetamask = () => { - const config = getConfig() + if (usesInfuraRPC) { + return getConfig()?.rpcServiceUrl + INFURA_TOKEN + } - return config[SIGNATURES_VIA_METAMASK] + return getConfig()?.rpcServiceUrl } -export const getGnosisSafeAppsUrl = () => { - const config = getConfig() +export const getNetworkExplorerInfo = (): { name: string; url: string; apiUrl: string } => ({ + name: getConfig()?.networkExplorerName, + url: getConfig()?.networkExplorerUrl, + apiUrl: getConfig()?.networkExplorerApiUrl, +}) + +export const getNetworkConfigFeatures = (): SafeFeatures | undefined => getConfig()?.features + +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[getNetworkId()] + +const fetchContractABI = memoize( + async (url: string, contractAddress: string, apiKey?: string) => { + let params: any = { + module: 'contract', + action: 'getAbi', + address: contractAddress, + } + + 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}`, +) - return config[SAFE_APPS_URL] +const getNetworkExplorerApiKey = (networkExplorerName: string): string | undefined=> { + switch (networkExplorerName.toLowerCase()) { + case 'etherscan': { + return ETHERSCAN_API_KEY + } + default: { + return undefined + } + } } -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 getContractABI = async (contractAddress: string) =>{ + const { apiUrl, name } = getNetworkExplorerInfo() -export const getIntercomId = () => - process.env.REACT_APP_ENV === 'production' - ? process.env.REACT_APP_INTERCOM_ID - : 'plssl1fl' + const apiKey = getNetworkExplorerApiKey(name) -export const getExchangeRatesUrl = () => 'https://api.exchangeratesapi.io/latest' + try { + const { result, status } = await fetchContractABI(apiUrl, contractAddress, apiKey) -export const getExchangeRatesUrlFallback = () => 'https://api.coinbase.com/v2/exchange-rates' + if (status === '0') { + return [] + } -export const getSafeLastVersion = () => process.env.REACT_APP_LATEST_SAFE_VERSION || '1.1.1' + return result + } catch (e) { + console.error('Failed to retrieve ABI', e) + return undefined + } +} + +export type BlockScanInfo = () => { + alt: string + url: string +} -export const buildSafeCreationTxUrl = (safeAddress) => { - const host = getTxServiceHost() - const address = checksumAddress(safeAddress) - const base = getSafeCreationTxUri(address) +export const getExplorerInfo = (hash: string): BlockScanInfo => { + const { name, url } = getNetworkExplorerInfo() + + const blockScanInfo = () => { + const type = hash.length > 42 ? 'tx' : 'address' + + return { + url: `${url}${type}/${hash}`, + alt: name || '', + } + } - return `${host}${base}` + return blockScanInfo } diff --git a/src/config/names.ts b/src/config/names.ts deleted file mode 100644 index 3390fbecb8..0000000000 --- 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 0000000000..00a79dfdf0 --- /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 0000000000..8944d16d91 --- /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 0000000000..ad51c88312 --- /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 0000000000..89945ae6af --- /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 0000000000..fbdebfca83 --- /dev/null +++ b/src/config/networks/network.d.ts @@ -0,0 +1,73 @@ +// matches src/logic/tokens/store/model/token.ts `TokenProps` type +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 = { + safeApps?: boolean, + collectibles?: boolean, + contractInteraction?: boolean +} + +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 + features?: SafeFeatures + environment: SafeEnvironments +} diff --git a/src/config/networks/rinkeby.ts b/src/config/networks/rinkeby.ts new file mode 100644 index 0000000000..2f0650b0e3 --- /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 0000000000..2d8e41d618 --- /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: 'http://safe-apps-xdai.staging.gnosisdev.com', + gasPrice: 1e9, + rpcServiceUrl: 'https://rpc.xdaichain.com', + networkExplorerName: 'Blockscout', + networkExplorerUrl: 'https://blockscout.com/poa/xdai', + networkExplorerApiUrl: 'https://blockscout.com/poa/xdai/api', +} + +const xDai: NetworkConfig = { + environment: { + staging: { + ...baseConfig + }, + production: { + ...baseConfig, + safeAppsUrl: 'http://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 d24d20e19a..0000000000 --- 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 246fcb9c71..0000000000 --- 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 08f225fad6..0000000000 --- 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 61b4ebca25..0000000000 --- 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 cce8aa963a..0000000000 --- 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/collectibles/sources/OpenSea.ts b/src/logic/collectibles/sources/OpenSea.ts index d626e82531..9e2c1a5076 100644 --- a/src/logic/collectibles/sources/OpenSea.ts +++ b/src/logic/collectibles/sources/OpenSea.ts @@ -1,6 +1,6 @@ import { RateLimit } from 'async-sema' -import { ETHEREUM_NETWORK } from 'src/logic/wallets/getWeb3' +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' import NFTIcon from 'src/routes/safe/components/Balances/assets/nft_icon.png' import { OPENSEA_API_KEY } from 'src/utils/constants' @@ -136,7 +136,7 @@ class OpenSea { * @param {string} network * @returns {Promise} */ - async fetchAllUserCollectiblesByCategoryAsync(safeAddress: string, network: string): Promise { + async fetchAllUserCollectiblesByCategoryAsync(safeAddress: string, network: ETHEREUM_NETWORK): Promise { // eslint-disable-next-line no-underscore-dangle const metadataSourceUrl = this._endpointsUrls[network] const url = `${metadataSourceUrl}/assets/?owner=${safeAddress}` diff --git a/src/logic/collectibles/store/actions/fetchCollectibles.ts b/src/logic/collectibles/store/actions/fetchCollectibles.ts index c87e51df06..feb4dc2df1 100644 --- a/src/logic/collectibles/store/actions/fetchCollectibles.ts +++ b/src/logic/collectibles/store/actions/fetchCollectibles.ts @@ -1,13 +1,13 @@ import { batch } from 'react-redux' -import { getNetwork } from 'src/config' +import { getNetworkId } 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 network = getNetworkId() const source = getConfiguredSource() const collectibles = await source.fetchAllUserCollectiblesByCategoryAsync(safeAddress, network) diff --git a/src/logic/contractInteraction/sources/EtherscanService.ts b/src/logic/contractInteraction/sources/EtherscanService.ts deleted file mode 100644 index d9e7bfc5df..0000000000 --- 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 b5bc4f3fd1..0000000000 --- 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/safeContracts.ts b/src/logic/contracts/safeContracts.ts index 68d971f92b..5d1aa9086f 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 ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafeProxyFactory.json' 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 { 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' +import Web3 from 'web3' +import { AbiItem } from 'web3-utils' 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 - -const createGnosisSafeContract = (web3: Web3) => { - const gnosisSafe = contract(GnosisSafeSol) - gnosisSafe.setProvider(web3.currentProvider) - - return gnosisSafe +let proxyFactoryMaster: GnosisSafeProxyFactory +let safeMaster: 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() -} - -// 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' }) + // Create Safe Master copy + safeMaster = getGnosisSafeContract(web3, networkId) } -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 08313a649e..7b9f8ce7ea 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 32bf362ff5..2afecf0cfc 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 axios from 'axios' -import { getTxServiceHost } from 'src/config' +import { getTxServiceUrl } from 'src/config' jest.mock('axios') describe('fetchTokenCurrenciesBalances', () => { @@ -38,7 +38,7 @@ describe('fetchTokenCurrenciesBalances', () => { usdConversion: '1.188', }, ] - const apiUrl = getTxServiceHost() + const apiUrl = getTxServiceUrl() // @ts-ignore axios.get.mockImplementationOnce(() => Promise.resolve(expectedResult)) @@ -49,8 +49,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 00e2c48a1d..6f0c0f59f2 100644 --- a/src/logic/currencyValues/api/fetchCurrenciesRates.ts +++ b/src/logic/currencyValues/api/fetchCurrenciesRates.ts @@ -1,6 +1,6 @@ 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 BigNumber from 'bignumber.js' @@ -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 c2804708a9..06c290dc8d 100644 --- a/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.ts +++ b/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.ts @@ -1,6 +1,6 @@ import axios, { AxiosResponse } from 'axios' -import { getTxServiceHost } from 'src/config' +import { getTxServiceUrl } from 'src/config' import { TokenProps } from 'src/logic/tokens/store/model/token' export type BalanceEndpoint = { @@ -15,14 +15,10 @@ 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 7d4536afa7..9765045660 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 38bc655bb5..a97e09ab53 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/allTransactions/loadAllTransactions.ts b/src/logic/safe/store/actions/allTransactions/loadAllTransactions.ts index b054b123e8..b06c9ba13f 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/fetchSafeCreationTx.ts b/src/logic/safe/store/actions/fetchSafeCreationTx.ts index d0e79de406..6504d22600 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/transactions/incomingTxHistory.ts b/src/logic/safe/transactions/incomingTxHistory.ts index c7d78e5476..8eec0f6f10 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 eeb144364f..ea787b17ee 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 0000000000..19554e563a --- /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 5fe5ee3460..de6fc60b03 100644 --- a/src/logic/safe/utils/safeVersion.ts +++ b/src/logic/safe/utils/safeVersion.ts @@ -3,8 +3,8 @@ 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' export const FEATURES = [ { name: 'ERC721', validVersion: '>=1.1.1' }, @@ -60,11 +60,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 0000000000..b7874b643a --- /dev/null +++ b/src/logic/tokens/api/fetchErc20AndErc721AssetsList.ts @@ -0,0 +1,24 @@ +import axios, { AxiosResponse } from 'axios' + +import { getTxServiceUrl } from 'src/config/index' + +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/fetchToken.ts b/src/logic/tokens/api/fetchToken.ts deleted file mode 100644 index 7584521ecd..0000000000 --- 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 c446858c45..6712a09eaf 100644 --- a/src/logic/tokens/api/fetchTokenBalanceList.ts +++ b/src/logic/tokens/api/fetchTokenBalanceList.ts @@ -1,16 +1,12 @@ import axios from 'axios' -import { getTxServiceHost } from 'src/config/index' +import { getTxServiceUrl } from 'src/config/index' const fetchTokenBalanceList = (safeAddress) => { - const apiUrl = getTxServiceHost() - const url = `${apiUrl}safes/${safeAddress}/balances/` + const apiUrl = getTxServiceUrl() + const url = `${apiUrl}/safes/${safeAddress}/balances/` - return axios.get(url, { - params: { - limit: 3000, - }, - }) + return axios.get(url) } export default fetchTokenBalanceList diff --git a/src/logic/tokens/api/fetchTokenList.ts b/src/logic/tokens/api/fetchTokenList.ts deleted file mode 100644 index dd9054b039..0000000000 --- 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 7ff3aa7de3..408b30b38e 100644 --- a/src/logic/tokens/api/index.ts +++ b/src/logic/tokens/api/index.ts @@ -1,2 +1 @@ -export { default as fetchTokenList } from './fetchTokenList' -export { default as fetchToken } from './fetchToken' +export { fetchErc20AndErc721AssetsList } from './fetchErc20AndErc721AssetsList' diff --git a/src/logic/tokens/store/actions/fetchTokens.ts b/src/logic/tokens/store/actions/fetchTokens.ts index 5c5bde4959..6bccf77034 100644 --- a/src/logic/tokens/store/actions/fetchTokens.ts +++ b/src/logic/tokens/store/actions/fetchTokens.ts @@ -8,7 +8,7 @@ import contract from 'truffle-contract' 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' @@ -98,13 +98,15 @@ export const fetchTokens = () => 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/wallets/getWeb3.ts b/src/logic/wallets/getWeb3.ts index c3c7b0f407..c6c61ab4c4 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,13 @@ 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 = +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()) + : window.web3?.currentProvider || 'ws://localhost:8545', +) let web3 = web3ReadOnly export const getWeb3 = (): Web3 => web3 @@ -77,7 +44,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 3a7573fcd4..79ce447960 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 1e8ad09af1..146b2ddb70 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 ed147c3859..56e41f67c8 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 70452f18b9..1939a1c3d8 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 66a198b994..1524f9e122 100644 --- a/src/routes/load/components/DetailsForm/index.tsx +++ b/src/routes/load/components/DetailsForm/index.tsx @@ -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 49a7588f9e..2c5ba0759a 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 a3452bfd1e..6e4b530568 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 c5d6c4c1ce..2ed3dff91e 100644 --- a/src/routes/open/components/ReviewInformation/index.tsx +++ b/src/routes/open/components/ReviewInformation/index.tsx @@ -2,21 +2,21 @@ 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 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 = { @@ -118,7 +118,7 @@ const ReviewComponent = ({ userAccount, values }: ReviewComponentProps) => { {addresses[index]} - + diff --git a/src/routes/open/container/Open.tsx b/src/routes/open/container/Open.tsx index 360d388438..9b98eac323 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 292000e375..8f213484c4 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/Apps/hooks/useAppList.ts b/src/routes/safe/components/Apps/hooks/useAppList.ts index fc48a71682..c2f13c1393 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,18 +29,29 @@ 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[] = [] diff --git a/src/routes/safe/components/Apps/hooks/useIframeMessageHandler.ts b/src/routes/safe/components/Apps/hooks/useIframeMessageHandler.ts index a1a0e69523..966c988bda 100644 --- a/src/routes/safe/components/Apps/hooks/useIframeMessageHandler.ts +++ b/src/routes/safe/components/Apps/hooks/useIframeMessageHandler.ts @@ -12,7 +12,8 @@ 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 { getTxServiceUrl } from 'src/config/' +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' import { safeEthBalanceSelector, safeNameSelector, @@ -90,14 +91,14 @@ const useIframeMessageHandler = ( messageId: INTERFACE_MESSAGES.ON_SAFE_INFO, data: { safeAddress: safeAddress as string, - network: network.toLowerCase() as LowercaseNetworks, + network: ETHEREUM_NETWORK[network].toLowerCase() as LowercaseNetworks, ethBalance: ethBalance as string, }, } const envInfoMessage = { messageId: INTERFACE_MESSAGES.ENV_INFO, data: { - txServiceUrl: getTxServiceHost(), + txServiceUrl: getTxServiceUrl(), }, } diff --git a/src/routes/safe/components/Apps/index.tsx b/src/routes/safe/components/Apps/index.tsx index d34d8dd4db..40962238e3 100644 --- a/src/routes/safe/components/Apps/index.tsx +++ b/src/routes/safe/components/Apps/index.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useRef, useState, useMemo } from 'react' import { INTERFACE_MESSAGES, Transaction, RequestId, LowercaseNetworks } from '@gnosis.pm/safe-apps-sdk' import { Card, IconText, Loader, Menu, Title } from '@gnosis.pm/safe-react-components' import { useSelector } from 'react-redux' +import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' import styled, { css } from 'styled-components' import ManageApps from './components/ManageApps' @@ -41,7 +42,7 @@ const StyledCard = styled(Card)` const CenteredMT = styled.div` ${centerCSS}; - margin-top: 5px; + margin-top: 16px; ` type ConfirmTransactionModalState = { @@ -155,7 +156,7 @@ const Apps = (): React.ReactElement => { messageId: INTERFACE_MESSAGES.ON_SAFE_INFO, data: { safeAddress: safeAddress as string, - network: network.toLowerCase() as LowercaseNetworks, + network: ETHEREUM_NETWORK[network].toLowerCase() as LowercaseNetworks, ethBalance: ethBalance as string, }, }) @@ -185,7 +186,7 @@ const Apps = (): React.ReactElement => { granted={granted} selectedApp={selectedApp} safeAddress={safeAddress} - network={network} + network={ETHEREUM_NETWORK[network]} appIsLoading={appIsLoading} onIframeLoad={handleIframeLoad} /> diff --git a/src/routes/safe/components/Apps/utils.ts b/src/routes/safe/components/Apps/utils.ts index beeba0c089..c2fd724cff 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}/QmeHa5CS6eAMRvQfTBwWfcXKrXZ7itZTpWSM6625ZZ522N`, disabled: false }, + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmeHa5CS6eAMRvQfTBwWfcXKrXZ7itZTpWSM6625ZZ522N`, + 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/ContractInteraction/ContractABI/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ContractABI/index.tsx index 57168b1605..0df470c87a 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 @@ -5,8 +5,7 @@ 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 { getNetwork } from 'src/config' -import { getConfiguredSource } from 'src/logic/contractInteraction/sources' +import { getContractABI } from 'src/config' import { extractUsefulMethods } from 'src/logic/contractInteraction/sources/ABIService' export const NO_DATA = 'no data' @@ -36,9 +35,7 @@ const ContractABI = (): React.ReactElement => { const isEthereumContractAddress = (await mustBeEthereumContractAddress(contractAddress)) === undefined if (isEthereumAddress && isEthereumContractAddress) { - const network = getNetwork() - const source = getConfiguredSource() - const abi = await source.getContractABI(contractAddress, network) + const abi = await getContractABI(contractAddress) const isValidABI = hasUsefulMethods(abi) === undefined // this check may help in scenarios where the user first pastes the ABI, 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 b47b8f2f97..4b6ffa401e 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 @@ -4,10 +4,6 @@ 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 CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' import Identicon from 'src/components/Identicon' @@ -18,6 +14,8 @@ 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' @@ -25,10 +23,12 @@ 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 @@ -122,7 +122,7 @@ const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => { {tx.contractAddress} - + 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 99bbd1008f..2599f4f358 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,16 @@ 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' + export interface CreatedTx { contractAddress: string data: string @@ -177,7 +177,7 @@ const SendCustomTx: React.FC = ({ initialValues, onClose, onNext, contrac - + 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 1b7cb68d8b..7a77be83b8 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/index.tsx @@ -5,10 +5,6 @@ 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 CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' import Identicon from 'src/components/Identicon' @@ -20,6 +16,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 { @@ -32,11 +30,13 @@ import { SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH } from 'src/logic/tokens/utils/tok 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 useStyles = makeStyles(styles as any) const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { @@ -135,7 +135,7 @@ const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx {tx.recipientAddress} - + 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 0ee764bbe5..f9e1ff3035 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx @@ -3,13 +3,9 @@ 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 CopyBtn from 'src/components/CopyBtn' import EtherscanBtn from 'src/components/EtherscanBtn' import Identicon from 'src/components/Identicon' @@ -20,6 +16,8 @@ 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' @@ -30,10 +28,12 @@ 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 ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { @@ -149,7 +149,7 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => { {tx.recipientAddress} - + 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 14c54f06cb..833f4e9956 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 27db52e9e4..8ccd275e05 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx @@ -6,18 +6,13 @@ 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 +20,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 +30,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]) @@ -184,7 +184,7 @@ const SendFunds = ({ - + diff --git a/src/routes/safe/components/Balances/index.tsx b/src/routes/safe/components/Balances/index.tsx index 1f5489c6af..c72c001919 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' @@ -53,7 +53,7 @@ const Balances = (): React.ReactElement => { const address = useSelector(safeParamAddressFromStateSelector) const featuresEnabled = useSelector(safeFeaturesEnabledSelector) - const safeName = useSelector(safeNameSelector) + const safeName = useSelector(safeNameSelector) ?? '' useFetchTokens(address as string) @@ -229,7 +229,7 @@ const Balances = (): React.ReactElement => { paperClassName={receiveModal} title="Receive Tokens" > - onHide('Receive')} /> + onHide('Receive')} /> ) diff --git a/src/routes/safe/components/Settings/Advanced/RemoveModuleModal.tsx b/src/routes/safe/components/Settings/Advanced/RemoveModuleModal.tsx index e630c45d5f..cfe23ac0fa 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 dde8e118dc..1f659eba39 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 @@ -5,8 +5,6 @@ import classNames from 'classnames' import React, { useEffect, useState } 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' @@ -17,10 +15,12 @@ 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' @@ -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} - + diff --git a/src/routes/safe/components/Settings/ManageOwners/EditOwnerModal/index.tsx b/src/routes/safe/components/Settings/ManageOwners/EditOwnerModal/index.tsx index 7f6fbec5fc..6d750aed04 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 13313a5496..0caf445ca6 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 eb2853abd1..3ab15c42a6 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 8328f9399e..0a9b13b336 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 @@ -5,8 +5,6 @@ import classNames from 'classnames' import React, { useEffect, useState } 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' @@ -16,11 +14,13 @@ 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' @@ -120,7 +120,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre {owner.address} - + @@ -149,7 +149,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre {ownerAddress} - + 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 c8ce72bfd8..c808f411df 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 80939f2110..1b1e475243 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 @@ -5,8 +5,6 @@ import classNames from 'classnames' import React, { useEffect, useState } 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' @@ -16,16 +14,18 @@ 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 { getWeb3 } from 'src/logic/wallets/getWeb3' + +import { styles } from './style' export const REPLACE_OWNER_SUBMIT_BTN_TEST_ID = 'replace-owner-submit-btn' @@ -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} - + diff --git a/src/routes/safe/components/Settings/RemoveSafeModal/index.tsx b/src/routes/safe/components/Settings/RemoveSafeModal/index.tsx index e301c1b04d..5524ecd31d 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/Transactions/TxsTable/ExpandedTx/CreationTx/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/CreationTx/index.tsx index d4e609b76f..874224ea5f 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: { @@ -30,10 +31,12 @@ type Props = { export const CreationTx = ({ tx }: Props): React.ReactElement | null => { const classes = useStyles() - if (!tx) { return null } + const explorerUrl = getExplorerInfo(tx.creator) + const scanBlockFactoryAddressUrl = getExplorerInfo(tx.factoryAddress) + const scanBlockMasterCopyUrl = getExplorerInfo(tx.masterCopy) const isCreationTx = tx.type === TransactionTypes.CREATION @@ -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 bab3846617..532ce9ac18 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 8ba79a839e..06bbaec641 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/TxDescription/CustomDescription.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx index 0ed87bec5f..55f12bd443 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' @@ -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 } 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' @@ -75,7 +77,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: - + {!!tx.data && } @@ -177,7 +179,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 +190,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 596f3224e1..ccaf06fe80 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 4fc0be9780..afc5f2c13c 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 7a0108e2c4..9bc97b09c3 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 2bc2c2bb2b..eaae1cb214 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 } from 'src/config' const useStyles = makeStyles(styles as any) @@ -59,6 +59,8 @@ const ExpandedTx = ({ cancelTx, tx }: ExpandedTxProps): React.ReactElement => { } } + const explorerUrl = tx.executionTxHash ? getExplorerInfo(tx.executionTxHash) : null + return ( <> @@ -68,13 +70,7 @@ const ExpandedTx = ({ cancelTx, tx }: ExpandedTxProps): React.ReactElement => {
Hash: {tx.executionTxHash ? ( - + ) : ( 'n/a' )} diff --git a/src/test/builder/safe.redux.builder.ts b/src/test/builder/safe.redux.builder.ts index cf6f297ba6..8a44555ae7 100644 --- a/src/test/builder/safe.redux.builder.ts +++ b/src/test/builder/safe.redux.builder.ts @@ -89,9 +89,7 @@ export const aMinedSafe = async ( form[getOwnerAddressBy(i)] = accounts[i] } - const openSafeProps = await createSafe(form, accounts[0]) - - return openSafeProps.safeAddress + return createSafe(form, accounts[0]).then((receipt) => receipt.events?.ProxyCreation.returnValues.proxy) } export default aSafe diff --git a/src/utils/constants.ts b/src/utils/constants.ts index a21a6b6c9d..cb56ff7b04 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,16 +1,44 @@ -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 = { + [ETHEREUM_NETWORK.RINKEBY]: process.env.REACT_APP_GOOGLE_ANALYTICS_ID_RINKEBY, + [ETHEREUM_NETWORK.MAINNET]: process.env.REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET, + [ETHEREUM_NETWORK.XDAI]: process.env.REACT_APP_GOOGLE_ANALYTICS_ID_XDAI, +} +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 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 6fb98cbf97..68035e7d98 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 d2dff5b7e7..d9f7a13328 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 d2f4648c17..e961b253bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"