Merge pull request #229 from gnosis/124-web3connect-new
Feature 124: Web3connect
This commit is contained in:
commit
4579f88fa6
|
@ -4,4 +4,4 @@ build_storybook/
|
|||
.DS_Store
|
||||
build/
|
||||
yarn-error.log
|
||||
.env.*
|
||||
.env*
|
||||
|
|
35
package.json
35
package.json
|
@ -35,27 +35,29 @@
|
|||
"@gnosis.pm/util-contracts": "2.0.4",
|
||||
"@material-ui/core": "4.6.0",
|
||||
"@material-ui/icons": "4.5.1",
|
||||
"@portis/web3": "^2.0.0-beta.45",
|
||||
"@testing-library/jest-dom": "4.2.3",
|
||||
"@welldone-software/why-did-you-render": "3.3.8",
|
||||
"@toruslabs/torus-embed": "0.2.6",
|
||||
"@walletconnect/web3-provider": "^1.0.0-beta.37",
|
||||
"@welldone-software/why-did-you-render": "3.3.9",
|
||||
"axios": "0.19.0",
|
||||
"bignumber.js": "9.0.0",
|
||||
"connected-react-router": "6.5.2",
|
||||
"date-fns": "2.6.0",
|
||||
"date-fns": "2.7.0",
|
||||
"ethereum-ens": "0.7.8",
|
||||
"final-form": "4.18.5",
|
||||
"final-form": "4.18.6",
|
||||
"history": "4.10.1",
|
||||
"immortal-db": "^1.0.2",
|
||||
"immutable": "^4.0.0-rc.9",
|
||||
"material-ui-search-bar": "^1.0.0-beta.13",
|
||||
"notistack": "https://github.com/gnosis/notistack.git#v0.9.4",
|
||||
"optimize-css-assets-webpack-plugin": "5.0.3",
|
||||
"qrcode.react": "^0.9.3",
|
||||
"qrcode.react": "1.0.0",
|
||||
"react": "16.11.0",
|
||||
"react-dom": "16.11.0",
|
||||
"react-final-form": "6.3.0",
|
||||
"react-final-form-listeners": "^1.0.2",
|
||||
"react-hot-loader": "4.12.15",
|
||||
"react-infinite-scroll-component": "5.0.2",
|
||||
"react-hot-loader": "4.12.16",
|
||||
"react-qr-reader": "^2.2.1",
|
||||
"react-redux": "7.1.3",
|
||||
"react-router-dom": "5.1.2",
|
||||
|
@ -65,11 +67,13 @@
|
|||
"redux-actions": "^2.6.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"web3": "1.2.2"
|
||||
"squarelink": "^1.1.3",
|
||||
"web3": "1.2.2",
|
||||
"web3connect": "^1.0.0-beta.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.7.0",
|
||||
"@babel/core": "7.7.0",
|
||||
"@babel/core": "7.7.2",
|
||||
"@babel/plugin-proposal-class-properties": "7.7.0",
|
||||
"@babel/plugin-proposal-decorators": "7.7.0",
|
||||
"@babel/plugin-proposal-do-expressions": "7.6.0",
|
||||
|
@ -93,10 +97,10 @@
|
|||
"@babel/preset-flow": "^7.0.0",
|
||||
"@babel/preset-react": "7.7.0",
|
||||
"@sambego/storybook-state": "^1.3.6",
|
||||
"@storybook/addon-actions": "5.2.5",
|
||||
"@storybook/addon-knobs": "5.2.5",
|
||||
"@storybook/addon-links": "5.2.5",
|
||||
"@storybook/react": "5.2.5",
|
||||
"@storybook/addon-actions": "5.2.6",
|
||||
"@storybook/addon-knobs": "5.2.6",
|
||||
"@storybook/addon-links": "5.2.6",
|
||||
"@storybook/react": "5.2.6",
|
||||
"@testing-library/react": "9.3.2",
|
||||
"autoprefixer": "9.7.1",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
|
@ -106,14 +110,15 @@
|
|||
"babel-plugin-dynamic-import-node": "^2.3.0",
|
||||
"babel-plugin-transform-es3-member-expression-literals": "^6.22.0",
|
||||
"babel-plugin-transform-es3-property-literals": "^6.22.0",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"classnames": "^2.2.6",
|
||||
"css-loader": "3.2.0",
|
||||
"detect-port": "^1.3.0",
|
||||
"eslint": "6.6.0",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-config-airbnb": "18.0.1",
|
||||
"eslint-plugin-flowtype": "4.3.0",
|
||||
"eslint-plugin-import": "2.18.2",
|
||||
"eslint-plugin-jest": "23.0.2",
|
||||
"eslint-plugin-jest": "23.0.3",
|
||||
"eslint-plugin-jsx-a11y": "6.2.3",
|
||||
"eslint-plugin-react": "7.16.0",
|
||||
"ethereumjs-abi": "0.6.8",
|
||||
|
@ -136,7 +141,7 @@
|
|||
"storybook-host": "5.1.0",
|
||||
"storybook-router": "^0.3.4",
|
||||
"style-loader": "1.0.0",
|
||||
"truffle": "5.0.43",
|
||||
"truffle": "5.0.44",
|
||||
"truffle-contract": "4.0.31",
|
||||
"truffle-solidity-loader": "0.1.32",
|
||||
"uglifyjs-webpack-plugin": "2.2.0",
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
// @flow
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import Web3Connect from 'web3connect'
|
||||
// import Torus from '@toruslabs/torus-embed'
|
||||
import WalletConnectProvider from '@walletconnect/web3-provider'
|
||||
import Portis from '@portis/web3'
|
||||
import Squarelink from 'squarelink'
|
||||
import Button from '~/components/layout/Button'
|
||||
import { fetchProvider } from '~/logic/wallets/store/actions'
|
||||
import { getNetwork } from '~/config'
|
||||
import { store } from '~/store'
|
||||
|
||||
const PORTIS_DAPP_ID = process.env.REACT_APP_NETWORK === 'mainnet' ? process.env.REACT_APP_PORTIS_ID : '852b763d-f28b-4463-80cb-846d7ec5806b'
|
||||
const SQUARELINK_CLIENT_ID = process.env.REACT_APP_NETWORK === 'mainnet' ? process.env.REACT_APP_SQUARELINK_ID : '46ce08fe50913cfa1b78'
|
||||
|
||||
export const web3Connect = new Web3Connect.Core({
|
||||
network: getNetwork().toLowerCase(),
|
||||
providerOptions: {
|
||||
walletconnect: {
|
||||
package: WalletConnectProvider,
|
||||
options: {
|
||||
infuraId: process.env.REACT_APP_INFURA_TOKEN,
|
||||
},
|
||||
},
|
||||
portis: {
|
||||
package: Portis,
|
||||
options: {
|
||||
id: PORTIS_DAPP_ID,
|
||||
},
|
||||
},
|
||||
squarelink: {
|
||||
package: Squarelink,
|
||||
options: {
|
||||
id: SQUARELINK_CLIENT_ID,
|
||||
},
|
||||
},
|
||||
// torus: {
|
||||
// package: Torus,
|
||||
// options: {
|
||||
// enableLogging: false,
|
||||
// buttonPosition: 'bottom-left',
|
||||
// buildEnv: process.env.NODE_ENV,
|
||||
// showTorusButton: true,
|
||||
// },
|
||||
// },
|
||||
},
|
||||
})
|
||||
|
||||
web3Connect.on('connect', (provider: any) => {
|
||||
if (provider) {
|
||||
store.dispatch(fetchProvider(provider))
|
||||
}
|
||||
})
|
||||
|
||||
type Props = {
|
||||
registerProvider: Function,
|
||||
enqueueSnackbar: Function,
|
||||
closeSnackbar: Function,
|
||||
}
|
||||
|
||||
const ConnectButton = ({
|
||||
registerProvider, ...props
|
||||
}: Props) => (
|
||||
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
minWidth={140}
|
||||
onClick={() => {
|
||||
web3Connect.toggleModal()
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
)
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
{ registerProvider: fetchProvider },
|
||||
)(ConnectButton)
|
|
@ -1 +0,0 @@
|
|||
<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><mask id="a" fill="#fff"><path d="m6.72043466.05975844h-6.62356495v11.84970376h6.62356495z" fill="#fff" fill-rule="evenodd"/></mask><g fill="#001428" fill-rule="evenodd"><path d="m4.14977665 6.07551536-1.71737342-1.70732942c-.154797.20785847-.24831026.46589514-.24831026.74441596 0 .67391239.54853704 1.22126772 1.22091168 1.22126772.27926965 0 .53821724-.09336958.744772-.25835426"/><path d="m1.87126776 2.19027617c1.03982725-1.02106347 2.40224018-1.57576779 3.8633547-1.57576779h.00917804c.01609919 0 .03219839.00091234.04814712.00121646v5.5467391zm3.85282252-2.13062417c-1.69583171 0-3.2740039.69414069-4.43103929 1.93158142l-.18657008.19904275 4.68546671 4.75589009v.85273625l-.01850655.01870302-1.20548342-1.2283173c-.55925885.37618928-1.27484536.48551834-1.95071051.24739691-1.13777654-.41618029-1.72637503-1.67445284-1.31411535-2.81366468.05852884-.17821093.14669825-.3371106.24449708-.48536628l-.51938702-.5253573-.09794929.16847927c-.5392477.89211904-.83339649 1.92230593-.83339649 2.98260013-.00947896 3.16111517 2.53900808 5.74578182 5.66631374 5.74578182l.95722485.0003041v-11.84889785z" mask="url(#a)" transform="translate(.218409)"/><path d="m11.7375061 6.11561396c0-2.64879063-2.15538824-4.8037754-4.80500459-4.80515818v.57492892c2.33248021.00122914 4.22991669 1.89824874 4.22991669 4.23022926 0 2.33136596-1.89743648 4.22869284-4.22991669 4.22992194v.574929c2.64961635-.0013828 4.80500459-2.15636759 4.80500459-4.80485094"/><path d="m8.02004901 6.01820975-.22912891 1.40770636h1.1220214l-.2288201-1.40770636c.25522242-.12241582.43247309-.37950419.43247309-.67820486 0-.41582039-.34214937-.75340965-.76443209-.75340965-.42197392 0-.76443209.33758926-.76443209.75340965-.00046319.2990033.17647867.55578904.4323187.67820486"/><path d="m7.75184322 2.83111882c.28114484.0716844.38948776-.34809944.1084868-.42035731-.28100096-.07197114-.38934388.34809944-.1084868.42035731"/><path d="m8.97850178 3.44035856c.21945107.18400118.4955759-.14470408.27612484-.3285634-.21945107-.18371744-.49543387.14484596-.27612484.3285634"/><path d="m9.79686881 4.68163059c.12643597.25912811.51314779.0686432.38671179-.19048491-.1261512-.25841307-.51314776-.06821418-.38671179.19048491"/><path d="m7.75184322 9.3998634c.28114484-.07200813.38948776.34827835.1084868.42028648s-.38934388-.34799147-.1084868-.42028648"/><path d="m8.97850178 8.5721264c.21945107-.18396063.4955759.14481403.27612484.32849098-.21945107.18396062-.49543387-.14481403-.27612484-.32849098"/><path d="m9.79686881 7.54913594c.12643597-.25888516.51314779-.06847377.38671179.19069729-.1261512.25831336-.51314776.06818787-.38671179-.19069729"/><path d="m10.208641 6.11535204c0 .29135677.4368186.29135677.4368186 0 0-.29106802-.4368186-.29106802-.4368186 0"/></g></svg>
|
Before Width: | Height: | Size: 2.8 KiB |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 13 13">
|
||||
<path fill="#001428" fill-rule="evenodd" d="M.658 0L0 1.936l.43 2.053-.278.216.404.313-.308.238.41.368-.26.175.595.686-.881 2.78.83 2.798 2.975-.777 1.58 1.231h1.976l1.72-1.26 2.866.806.83-2.798h.003l-.885-2.78.594-.686-.26-.174.41-.368-.307-.239.404-.313-.278-.217.43-2.052L12.341 0 8.197 1.522H4.804L.658 0zm7.427 6.826l1.693.796-2.369.683.676-1.48zm-4.863.794l1.693-.794.676 1.479-2.37-.685zm2.276 2.274l.294-.17h1.416l.277.178.093 1.033H5.401l.097-1.041z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 557 B |
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="14" viewBox="0 0 19 14">
|
||||
<path fill="#B2B5B2" fill-rule="nonzero" d="M16 4V2H2v10h14v-2h-3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h3zm2 6v2a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1zm-4-4v2h3V6h-3z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 314 B |
|
@ -2,14 +2,14 @@
|
|||
import * as React from 'react'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import Button from '~/components/layout/Button'
|
||||
import ConnectButton from '~/components/ConnectButton'
|
||||
import Row from '~/components/layout/Row'
|
||||
import Block from '~/components/layout/Block'
|
||||
import { md, lg } from '~/theme/variables'
|
||||
import CircleDot from '~/components/Header/component/CircleDot'
|
||||
import CircleDot from '~/components/Header/components/CircleDot'
|
||||
|
||||
type Props = {
|
||||
classes: Object,
|
||||
onConnect: Function,
|
||||
}
|
||||
|
||||
const styles = () => ({
|
||||
|
@ -26,6 +26,7 @@ const styles = () => ({
|
|||
},
|
||||
connect: {
|
||||
padding: `${md} ${lg}`,
|
||||
textAlign: 'center',
|
||||
},
|
||||
connectText: {
|
||||
letterSpacing: '1px',
|
||||
|
@ -35,7 +36,7 @@ const styles = () => ({
|
|||
},
|
||||
})
|
||||
|
||||
const ConnectDetails = ({ classes, onConnect }: Props) => (
|
||||
const ConnectDetails = ({ classes }: Props) => (
|
||||
<>
|
||||
<div className={classes.container}>
|
||||
<Row margin="lg" align="center">
|
||||
|
@ -47,13 +48,9 @@ const ConnectDetails = ({ classes, onConnect }: Props) => (
|
|||
<Row className={classes.logo} margin="lg">
|
||||
<CircleDot keySize={32} circleSize={75} dotSize={25} dotTop={50} dotRight={25} center mode="error" />
|
||||
</Row>
|
||||
<Row className={classes.connect}>
|
||||
<Button onClick={onConnect} size="medium" variant="contained" color="primary" fullWidth>
|
||||
<Paragraph className={classes.connectText} size="sm" weight="regular" color="white" noMargin>
|
||||
CONNECT
|
||||
</Paragraph>
|
||||
</Button>
|
||||
</Row>
|
||||
<Block className={classes.connect}>
|
||||
<ConnectButton />
|
||||
</Block>
|
||||
</>
|
||||
)
|
||||
|
|
@ -18,10 +18,9 @@ import {
|
|||
} from '~/theme/variables'
|
||||
import { upperFirst } from '~/utils/css'
|
||||
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
|
||||
import CircleDot from '~/components/Header/component/CircleDot'
|
||||
import CircleDot from '~/components/Header/components/CircleDot'
|
||||
|
||||
const metamaskIcon = require('../../assets/metamask-icon.svg')
|
||||
const safeIcon = require('../../assets/gnosis-safe-icon.svg')
|
||||
const walletIcon = require('../../assets/wallet.svg')
|
||||
const dot = require('../../assets/dotRinkeby.svg')
|
||||
|
||||
type Props = {
|
||||
|
@ -141,11 +140,7 @@ const UserDetails = ({
|
|||
Wallet
|
||||
</Paragraph>
|
||||
<Spacer />
|
||||
{provider === 'safe' ? (
|
||||
<Img className={classes.logo} src={safeIcon} height={14} alt="Safe client" />
|
||||
) : (
|
||||
<Img className={classes.logo} src={metamaskIcon} height={14} alt="Metamask client" />
|
||||
)}
|
||||
<Img className={classes.logo} src={walletIcon} height={14} alt="Wallet icon" />
|
||||
<Paragraph noMargin align="right" weight="bolder" className={classes.labels}>
|
||||
{upperFirst(provider)}
|
||||
</Paragraph>
|
|
@ -7,7 +7,7 @@ import Col from '~/components/layout/Col'
|
|||
import { connected as connectedBg, sm } from '~/theme/variables'
|
||||
import Identicon from '~/components/Identicon'
|
||||
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
|
||||
import CircleDot from '~/components/Header/component/CircleDot'
|
||||
import CircleDot from '~/components/Header/components/CircleDot'
|
||||
|
||||
type Props = {
|
||||
provider: string,
|
|
@ -5,7 +5,7 @@ import Paragraph from '~/components/layout/Paragraph'
|
|||
import Col from '~/components/layout/Col'
|
||||
import { type Open } from '~/components/hoc/OpenHoc'
|
||||
import { sm } from '~/theme/variables'
|
||||
import CircleDot from '~/components/Header/component/CircleDot'
|
||||
import CircleDot from '~/components/Header/components/CircleDot'
|
||||
|
||||
type Props = Open & {
|
||||
classes: Object,
|
||||
|
@ -29,7 +29,7 @@ const styles = () => ({
|
|||
},
|
||||
})
|
||||
|
||||
const ProviderDesconnected = ({ classes }: Props) => (
|
||||
const ProviderDisconnected = ({ classes }: Props) => (
|
||||
<>
|
||||
<CircleDot keySize={17} circleSize={35} dotSize={16} dotTop={24} dotRight={11} mode="error" />
|
||||
<Col end="sm" middle="xs" layout="column" className={classes.account}>
|
||||
|
@ -43,4 +43,4 @@ const ProviderDesconnected = ({ classes }: Props) => (
|
|||
</>
|
||||
)
|
||||
|
||||
export default withStyles(styles)(ProviderDesconnected)
|
||||
export default withStyles(styles)(ProviderDisconnected)
|
|
@ -3,20 +3,22 @@ import * as React from 'react'
|
|||
import { connect } from 'react-redux'
|
||||
import { withSnackbar } from 'notistack'
|
||||
import { logComponentStack, type Info } from '~/utils/logBoundaries'
|
||||
import { getProviderInfo } from '~/logic/wallets/getWeb3'
|
||||
import type { ProviderProps } from '~/logic/wallets/store/model/provider'
|
||||
import { NOTIFICATIONS, showSnackbar } from '~/logic/notifications'
|
||||
import ProviderAccesible from './component/ProviderInfo/ProviderAccesible'
|
||||
import UserDetails from './component/ProviderDetails/UserDetails'
|
||||
import ProviderDisconnected from './component/ProviderInfo/ProviderDisconnected'
|
||||
import ConnectDetails from './component/ProviderDetails/ConnectDetails'
|
||||
import Layout from './component/Layout'
|
||||
import { web3Connect } from '~/components/ConnectButton'
|
||||
import { INJECTED_PROVIDERS } from '~/logic/wallets/getWeb3'
|
||||
import { loadLastUsedProvider } from '~/logic/wallets/store/middlewares/providerWatcher'
|
||||
import ProviderAccessible from './components/ProviderInfo/ProviderAccessible'
|
||||
import UserDetails from './components/ProviderDetails/UserDetails'
|
||||
import ProviderDisconnected from './components/ProviderInfo/ProviderDisconnected'
|
||||
import ConnectDetails from './components/ProviderDetails/ConnectDetails'
|
||||
import Layout from './components/Layout'
|
||||
import actions, { type Actions } from './actions'
|
||||
import selector, { type SelectorProps } from './selector'
|
||||
|
||||
type Props = Actions &
|
||||
SelectorProps & {
|
||||
enqueueSnackbar: Function,
|
||||
closeSnackbar: Function,
|
||||
}
|
||||
|
||||
type State = {
|
||||
|
@ -24,8 +26,6 @@ type State = {
|
|||
}
|
||||
|
||||
class HeaderComponent extends React.PureComponent<Props, State> {
|
||||
providerListener: ?IntervalID
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
|
@ -34,8 +34,11 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.onConnect()
|
||||
async componentDidMount() {
|
||||
const lastUsedProvider = await loadLastUsedProvider()
|
||||
if (INJECTED_PROVIDERS.includes(lastUsedProvider) || process.env.NODE_ENV === 'test') {
|
||||
web3Connect.connectToInjected()
|
||||
}
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, info: Info) {
|
||||
|
@ -50,27 +53,9 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
|||
onDisconnect = () => {
|
||||
const { removeProvider, enqueueSnackbar, closeSnackbar } = this.props
|
||||
|
||||
clearInterval(this.providerListener)
|
||||
|
||||
removeProvider(enqueueSnackbar, closeSnackbar)
|
||||
}
|
||||
|
||||
onConnect = async () => {
|
||||
const { fetchProvider, enqueueSnackbar, closeSnackbar } = this.props
|
||||
|
||||
clearInterval(this.providerListener)
|
||||
let currentProvider: ProviderProps = await getProviderInfo()
|
||||
fetchProvider(currentProvider, enqueueSnackbar, closeSnackbar)
|
||||
|
||||
this.providerListener = setInterval(async () => {
|
||||
const newProvider: ProviderProps = await getProviderInfo()
|
||||
if (currentProvider && JSON.stringify(currentProvider) !== JSON.stringify(newProvider)) {
|
||||
fetchProvider(newProvider, enqueueSnackbar, closeSnackbar)
|
||||
}
|
||||
currentProvider = newProvider
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
getProviderInfoBased = () => {
|
||||
const { hasError } = this.state
|
||||
const {
|
||||
|
@ -81,7 +66,7 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
|||
return <ProviderDisconnected />
|
||||
}
|
||||
|
||||
return <ProviderAccesible provider={provider} network={network} userAddress={userAddress} connected={available} />
|
||||
return <ProviderAccessible provider={provider} network={network} userAddress={userAddress} connected={available} />
|
||||
}
|
||||
|
||||
getProviderDetailsBased = () => {
|
||||
|
@ -91,7 +76,7 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
|||
} = this.props
|
||||
|
||||
if (hasError || !loaded) {
|
||||
return <ConnectDetails onConnect={this.onConnect} />
|
||||
return <ConnectDetails />
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
// @flow
|
||||
import enqueueSnackbar from '~/logic/notifications/store/actions/enqueueSnackbar'
|
||||
import closeSnackbar from '~/logic/notifications/store/actions/closeSnackbar'
|
||||
import removeSnackbar from '~/logic/notifications/store/actions/removeSnackbar'
|
||||
|
||||
export type Actions = {
|
||||
enqueueSnackbar: typeof enqueueSnackbar,
|
||||
closeSnackbar: typeof closeSnackbar,
|
||||
removeSnackbar: typeof removeSnackbar,
|
||||
}
|
||||
|
||||
export default {
|
||||
enqueueSnackbar,
|
||||
closeSnackbar,
|
||||
removeSnackbar,
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import selector from './selector'
|
|||
|
||||
type Props = Actions & {
|
||||
notifications: List<Notification>,
|
||||
closeSnackbar: Function,
|
||||
enqueueSnackbar: Function,
|
||||
}
|
||||
|
||||
class Notifier extends Component<Props> {
|
||||
|
@ -46,8 +48,10 @@ class Notifier extends Component<Props> {
|
|||
if (this.displayed.includes(notification.key)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Display snackbar using notistack
|
||||
enqueueSnackbar(notification.message, {
|
||||
key: notification.key,
|
||||
...notification.options,
|
||||
onClose: (event, reason, key) => {
|
||||
if (notification.options.onClose) {
|
||||
|
|
|
@ -73,7 +73,7 @@ const SafeList = ({
|
|||
{safes.map((safe) => (
|
||||
<React.Fragment key={safe.address}>
|
||||
<Link
|
||||
to={`${SAFELIST_ADDRESS}/${safe.address}`}
|
||||
to={`${SAFELIST_ADDRESS}/${safe.address}/balances`}
|
||||
onClick={onSafeClick}
|
||||
data-testid={SIDEBAR_SAFELIST_ROW_TESTID}
|
||||
>
|
||||
|
|
|
@ -4,6 +4,7 @@ import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/ProxyFact
|
|||
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
|
||||
import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/Proxy.json'
|
||||
import { ensureOnce } from '~/utils/singleton'
|
||||
import { simpleMemoize } from '~/components/forms/validator'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
|
||||
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
||||
|
@ -27,10 +28,10 @@ const createProxyFactoryContract = (web3: any) => {
|
|||
return proxyFactory
|
||||
}
|
||||
|
||||
export const getGnosisSafeContract = ensureOnce(createGnosisSafeContract)
|
||||
const getCreateProxyFactoryContract = ensureOnce(createProxyFactoryContract)
|
||||
export const getGnosisSafeContract = simpleMemoize(createGnosisSafeContract)
|
||||
const getCreateProxyFactoryContract = simpleMemoize(createProxyFactoryContract)
|
||||
|
||||
const instanciateMasterCopies = async () => {
|
||||
const instantiateMasterCopies = async () => {
|
||||
const web3 = getWeb3()
|
||||
|
||||
// Create ProxyFactory Master Copy
|
||||
|
@ -55,7 +56,7 @@ const createMasterCopies = async () => {
|
|||
safeMaster = await GnosisSafe.new({ from: userAccount, gas: '7000000' })
|
||||
}
|
||||
|
||||
export const initContracts = ensureOnce(process.env.NODE_ENV === 'test' ? createMasterCopies : instanciateMasterCopies)
|
||||
export const initContracts = process.env.NODE_ENV === 'test' ? ensureOnce(createMasterCopies) : instantiateMasterCopies
|
||||
|
||||
export const getSafeMasterContract = async () => {
|
||||
await initContracts()
|
||||
|
@ -106,7 +107,7 @@ const cleanByteCodeMetadata = (bytecode: string): string => {
|
|||
return bytecode.substring(0, bytecode.lastIndexOf(metaData))
|
||||
}
|
||||
|
||||
export const validateProxy = async (safeAddress: string): boolean => {
|
||||
export const validateProxy = async (safeAddress: string): Promise<boolean> => {
|
||||
// https://solidity.readthedocs.io/en/latest/metadata.html#usage-for-source-code-verification
|
||||
const web3 = getWeb3()
|
||||
const code = await web3.eth.getCode(safeAddress)
|
||||
|
|
|
@ -3,6 +3,8 @@ import * as React from 'react'
|
|||
import { IconButton } from '@material-ui/core'
|
||||
import { Close as IconClose } from '@material-ui/icons'
|
||||
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||
import { store } from '~/store'
|
||||
import closeSnackbarAction from '~/logic/notifications/store/actions/closeSnackbar'
|
||||
import { type Notification, NOTIFICATIONS } from './notificationTypes'
|
||||
|
||||
export type NotificationsQueue = {
|
||||
|
@ -52,12 +54,12 @@ const cancellationTxNotificationsQueue: NotificationsQueue = {
|
|||
const ownerChangeTxNotificationsQueue: NotificationsQueue = {
|
||||
beforeExecution: NOTIFICATIONS.SIGN_OWNER_CHANGE_MSG,
|
||||
pendingExecution: {
|
||||
noMoreConfirmationsNeeded: NOTIFICATIONS.ONWER_CHANGE_PENDING_MSG,
|
||||
moreConfirmationsNeeded: NOTIFICATIONS.ONWER_CHANGE_PENDING_MORE_CONFIRMATIONS_MSG,
|
||||
noMoreConfirmationsNeeded: NOTIFICATIONS.OWNER_CHANGE_PENDING_MSG,
|
||||
moreConfirmationsNeeded: NOTIFICATIONS.OWNER_CHANGE_PENDING_MORE_CONFIRMATIONS_MSG,
|
||||
},
|
||||
afterRejection: NOTIFICATIONS.ONWER_CHANGE_REJECTED_MSG,
|
||||
afterRejection: NOTIFICATIONS.OWNER_CHANGE_REJECTED_MSG,
|
||||
afterExecution: NOTIFICATIONS.OWNER_CHANGE_EXECUTED_MSG,
|
||||
afterExecutionError: NOTIFICATIONS.ONWER_CHANGE_FAILED_MSG,
|
||||
afterExecutionError: NOTIFICATIONS.OWNER_CHANGE_FAILED_MSG,
|
||||
}
|
||||
|
||||
const safeNameChangeNotificationsQueue: NotificationsQueue = {
|
||||
|
@ -67,7 +69,7 @@ const safeNameChangeNotificationsQueue: NotificationsQueue = {
|
|||
moreConfirmationsNeeded: null,
|
||||
},
|
||||
afterRejection: null,
|
||||
afterExecution: NOTIFICATIONS.SAFE_NAME_CHANGE_EXECUTED_MSG,
|
||||
afterExecution: NOTIFICATIONS.SAFE_NAME_CHANGED_MSG,
|
||||
afterExecutionError: null,
|
||||
}
|
||||
|
||||
|
@ -145,11 +147,19 @@ export const getNotificationsFromTxType = (txType: string) => {
|
|||
return notificationsQueue
|
||||
}
|
||||
|
||||
export const showSnackbar = (
|
||||
notification: Notification,
|
||||
enqueueSnackbar: Function,
|
||||
closeSnackbar: Function,
|
||||
) => enqueueSnackbar(notification.message, {
|
||||
export const enhanceSnackbarForAction = (notification: Notification) => ({
|
||||
...notification,
|
||||
options: {
|
||||
...notification.options,
|
||||
action: (key) => (
|
||||
<IconButton onClick={() => store.dispatch(closeSnackbarAction(key))}>
|
||||
<IconClose />
|
||||
</IconButton>
|
||||
),
|
||||
},
|
||||
})
|
||||
|
||||
export const showSnackbar = (notification: Notification, enqueueSnackbar: Function, closeSnackbar: Function) => enqueueSnackbar(notification.message, {
|
||||
...notification.options,
|
||||
action: (key) => (
|
||||
<IconButton onClick={() => closeSnackbar(key)}>
|
||||
|
|
|
@ -10,15 +10,15 @@ export const INFO = 'info'
|
|||
const shortDuration = 5000
|
||||
const longDuration = 10000
|
||||
|
||||
export type Variant = SUCCESS | ERROR | WARNING | INFO
|
||||
export type Variant = 'success' | 'error' | 'warning' | 'info'
|
||||
|
||||
export type Notification = {
|
||||
message: string,
|
||||
options: {
|
||||
variant: Variant,
|
||||
persist: boolean,
|
||||
autoHideDuration?: shortDuration | longDuration,
|
||||
preventDuplicate: boolean,
|
||||
autoHideDuration?: 5000 | 10000,
|
||||
preventDuplicate?: boolean,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -52,11 +52,11 @@ export type Notifications = {
|
|||
|
||||
// Owners
|
||||
SIGN_OWNER_CHANGE_MSG: Notification,
|
||||
ONWER_CHANGE_PENDING_MSG: Notification,
|
||||
ONWER_CHANGE_PENDING_MORE_CONFIRMATIONS_MSG: Notification,
|
||||
ONWER_CHANGE_REJECTED_MSG: Notification,
|
||||
ONWER_CHANGE_EXECUTED_MSG: Notification,
|
||||
ONWER_CHANGE_FAILED_MSG: Notification,
|
||||
OWNER_CHANGE_PENDING_MSG: Notification,
|
||||
OWNER_CHANGE_PENDING_MORE_CONFIRMATIONS_MSG: Notification,
|
||||
OWNER_CHANGE_REJECTED_MSG: Notification,
|
||||
OWNER_CHANGE_EXECUTED_MSG: Notification,
|
||||
OWNER_CHANGE_FAILED_MSG: Notification,
|
||||
|
||||
// Threshold
|
||||
SIGN_THRESHOLD_CHANGE_MSG: Notification,
|
||||
|
@ -147,7 +147,7 @@ export const NOTIFICATIONS: Notifications = {
|
|||
},
|
||||
|
||||
// Safe Name
|
||||
SAFE_NAME_CHANGE_EXECUTED_MSG: {
|
||||
SAFE_NAME_CHANGED_MSG: {
|
||||
message: 'Safe name changed',
|
||||
options: { variant: SUCCESS, persist: false, autoHideDuration: shortDuration },
|
||||
},
|
||||
|
@ -163,15 +163,15 @@ export const NOTIFICATIONS: Notifications = {
|
|||
message: 'Please sign the owner change',
|
||||
options: { variant: SUCCESS, persist: true },
|
||||
},
|
||||
ONWER_CHANGE_PENDING_MSG: {
|
||||
OWNER_CHANGE_PENDING_MSG: {
|
||||
message: 'Owner change pending',
|
||||
options: { variant: SUCCESS, persist: true },
|
||||
},
|
||||
ONWER_CHANGE_PENDING_MORE_CONFIRMATIONS_MSG: {
|
||||
OWNER_CHANGE_PENDING_MORE_CONFIRMATIONS_MSG: {
|
||||
message: 'Owner change pending: More confirmations required to execute',
|
||||
options: { variant: SUCCESS, persist: true },
|
||||
},
|
||||
ONWER_CHANGE_REJECTED_MSG: {
|
||||
OWNER_CHANGE_REJECTED_MSG: {
|
||||
message: 'Owner change rejected',
|
||||
options: { variant: ERROR, persist: false, autoHideDuration: longDuration },
|
||||
},
|
||||
|
@ -179,7 +179,7 @@ export const NOTIFICATIONS: Notifications = {
|
|||
message: 'Owner change successfully executed',
|
||||
options: { variant: SUCCESS, persist: false, autoHideDuration: longDuration },
|
||||
},
|
||||
ONWER_CHANGE_FAILED_MSG: {
|
||||
OWNER_CHANGE_FAILED_MSG: {
|
||||
message: 'Owner change failed',
|
||||
options: { variant: ERROR, persist: false, autoHideDuration: longDuration },
|
||||
},
|
||||
|
|
|
@ -15,6 +15,7 @@ const enqueueSnackbar = (notification: NotificationProps) => (
|
|||
...notification,
|
||||
key: new Date().getTime(),
|
||||
}
|
||||
|
||||
dispatch(addSnackbar(newNotification))
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ export type NotificationProps = {
|
|||
key?: number,
|
||||
message: string,
|
||||
options: Object,
|
||||
dismissed: boolean,
|
||||
dismissed?: boolean,
|
||||
}
|
||||
|
||||
export const makeNotification: RecordFactory<NotificationProps> = Record({
|
||||
|
|
|
@ -18,10 +18,9 @@ export default handleActions<NotificationReducerState, *>(
|
|||
return state.set(notification.key, makeNotification(notification))
|
||||
},
|
||||
[CLOSE_SNACKBAR]: (state: NotificationReducerState, action: ActionType<Function>): NotificationReducerState => {
|
||||
const { notification }: { notification: NotificationProps } = action.payload
|
||||
notification.dismissed = true
|
||||
const key = action.payload
|
||||
|
||||
return state.update(notification.key, (prev) => prev.merge(notification))
|
||||
return state.update(key, (prev) => prev.set('dismissed', true))
|
||||
},
|
||||
[REMOVE_SNACKBAR]: (state: NotificationReducerState, action: ActionType<Function>): NotificationReducerState => {
|
||||
const key = action.payload
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
import axios from 'axios'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { getWeb3, web3ReadOnly } from '~/logic/wallets/getWeb3'
|
||||
|
||||
// const MAINNET_NETWORK = 1
|
||||
export const EMPTY_DATA = '0x'
|
||||
|
@ -11,8 +11,7 @@ export const checkReceiptStatus = async (hash: string) => {
|
|||
return Promise.reject(new Error('No valid Tx hash to get receipt from'))
|
||||
}
|
||||
|
||||
const web3 = getWeb3()
|
||||
const txReceipt = await web3.eth.getTransactionReceipt(hash)
|
||||
const txReceipt = await web3ReadOnly.eth.getTransactionReceipt(hash)
|
||||
|
||||
const { status } = txReceipt
|
||||
if (!status) {
|
||||
|
|
|
@ -17,11 +17,23 @@ export const ETHEREUM_NETWORK = {
|
|||
export const WALLET_PROVIDER = {
|
||||
SAFE: 'SAFE',
|
||||
METAMASK: 'METAMASK',
|
||||
PARITY: 'PARITY',
|
||||
REMOTE: 'REMOTE',
|
||||
UPORT: 'UPORT',
|
||||
TORUS: 'TORUS',
|
||||
PORTIS: 'PORTIS',
|
||||
FORTMATIC: 'FORTMATIC',
|
||||
SQUARELINK: 'SQUARELINK',
|
||||
WALLETCONNECT: 'WALLETCONNECT',
|
||||
OPERA: 'OPERA',
|
||||
DAPPER: 'DAPPER',
|
||||
}
|
||||
|
||||
export const INJECTED_PROVIDERS = [
|
||||
WALLET_PROVIDER.SAFE,
|
||||
WALLET_PROVIDER.METAMASK,
|
||||
WALLET_PROVIDER.OPERA,
|
||||
WALLET_PROVIDER.DAPPER,
|
||||
]
|
||||
|
||||
export const ETHEREUM_NETWORK_IDS = {
|
||||
// $FlowFixMe
|
||||
1: ETHEREUM_NETWORK.MAINNET,
|
||||
|
@ -44,8 +56,24 @@ export const getEtherScanLink = (type: 'address' | 'tx', value: string) => {
|
|||
}etherscan.io/${type}/${value}`
|
||||
}
|
||||
|
||||
let web3
|
||||
export const getWeb3 = () => web3 || (window.web3 && new Web3(window.web3.currentProvider)) || (window.ethereum && new Web3(window.ethereum))
|
||||
const getInfuraUrl = () => {
|
||||
const isMainnet = process.env.REACT_APP_NETWORK === 'mainnet'
|
||||
|
||||
return `https://${isMainnet ? '' : '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 = process.env.NODE_ENV !== 'test'
|
||||
? new Web3(new Web3.providers.HttpProvider(getInfuraUrl()))
|
||||
: new Web3(window.web3.currentProvider)
|
||||
|
||||
let web3 = web3ReadOnly
|
||||
export const getWeb3 = () => web3
|
||||
|
||||
export const resetWeb3 = () => {
|
||||
web3 = web3ReadOnly
|
||||
}
|
||||
|
||||
const getProviderName: Function = (web3Provider): string => {
|
||||
let name
|
||||
|
@ -56,11 +84,41 @@ const getProviderName: Function = (web3Provider): string => {
|
|||
break
|
||||
case 'MetamaskInpageProvider':
|
||||
name = WALLET_PROVIDER.METAMASK
|
||||
|
||||
if (web3Provider.currentProvider.isTorus) {
|
||||
name = WALLET_PROVIDER.TORUS
|
||||
}
|
||||
break
|
||||
case 'Object':
|
||||
if (navigator && /Opera|OPR\//.test(navigator.userAgent)) {
|
||||
name = WALLET_PROVIDER.OPERA
|
||||
} else {
|
||||
name = 'Wallet'
|
||||
}
|
||||
break
|
||||
case 'DapperLegacyProvider':
|
||||
name = WALLET_PROVIDER.DAPPER
|
||||
break
|
||||
default:
|
||||
name = 'Wallet'
|
||||
}
|
||||
|
||||
if (web3Provider.currentProvider.isPortis) {
|
||||
name = WALLET_PROVIDER.PORTIS
|
||||
}
|
||||
|
||||
if (web3Provider.currentProvider.isFortmatic) {
|
||||
name = WALLET_PROVIDER.FORTMATIC
|
||||
}
|
||||
|
||||
if (web3Provider.currentProvider.isSquarelink) {
|
||||
name = WALLET_PROVIDER.SQUARELINK
|
||||
}
|
||||
|
||||
if (web3Provider.currentProvider.isWalletConnect) {
|
||||
name = WALLET_PROVIDER.WALLETCONNECT
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
|
@ -80,28 +138,7 @@ const getNetworkIdFrom = async (web3Provider) => {
|
|||
return networkId
|
||||
}
|
||||
|
||||
export const getProviderInfo: Function = async (): Promise<ProviderProps> => {
|
||||
let web3Provider
|
||||
|
||||
if (window.ethereum) {
|
||||
web3Provider = window.ethereum
|
||||
try {
|
||||
await web3Provider.enable()
|
||||
} catch (error) {
|
||||
console.error('Error when enabling web3 provider', error)
|
||||
}
|
||||
} else if (window.web3) {
|
||||
web3Provider = window.web3.currentProvider
|
||||
} else {
|
||||
return {
|
||||
name: '',
|
||||
available: false,
|
||||
loaded: false,
|
||||
account: '',
|
||||
network: 0,
|
||||
}
|
||||
}
|
||||
|
||||
export const getProviderInfo: Function = async (web3Provider): Promise<ProviderProps> => {
|
||||
web3 = new Web3(web3Provider)
|
||||
|
||||
const name = getProviderName(web3)
|
||||
|
|
|
@ -4,6 +4,6 @@ import { type Provider } from '~/logic/wallets/store/model/provider'
|
|||
|
||||
export const ADD_PROVIDER = 'ADD_PROVIDER'
|
||||
|
||||
const addProvider = createAction(ADD_PROVIDER, (provider: Provider) => provider)
|
||||
const addProvider = createAction<string, *, *>(ADD_PROVIDER, (provider: Provider) => provider)
|
||||
|
||||
export default addProvider
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
// @flow
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { ETHEREUM_NETWORK_IDS, ETHEREUM_NETWORK } from '~/logic/wallets/getWeb3'
|
||||
import { ETHEREUM_NETWORK_IDS, ETHEREUM_NETWORK, getProviderInfo } from '~/logic/wallets/getWeb3'
|
||||
import { getNetwork } from '~/config'
|
||||
import type { ProviderProps } from '~/logic/wallets/store/model/provider'
|
||||
import { makeProvider } from '~/logic/wallets/store/model/provider'
|
||||
import { NOTIFICATIONS, showSnackbar } from '~/logic/notifications'
|
||||
import { NOTIFICATIONS, showSnackbar, enhanceSnackbarForAction } from '~/logic/notifications'
|
||||
import enqueueSnackbar from '~/logic/notifications/store/actions/enqueueSnackbar'
|
||||
import closeSnackbar from '~/logic/notifications/store/actions/closeSnackbar'
|
||||
|
||||
import addProvider from './addProvider'
|
||||
|
||||
export const processProviderResponse = (dispatch: ReduxDispatch<*>, provider: ProviderProps) => {
|
||||
|
@ -23,24 +26,20 @@ export const processProviderResponse = (dispatch: ReduxDispatch<*>, provider: Pr
|
|||
dispatch(addProvider(walletRecord))
|
||||
}
|
||||
|
||||
const handleProviderNotification = (
|
||||
provider: ProviderProps,
|
||||
enqueueSnackbar: Function,
|
||||
closeSnackbar: Function,
|
||||
) => {
|
||||
const { loaded, available, network } = provider
|
||||
const handleProviderNotification = (provider: ProviderProps, dispatch: Function) => {
|
||||
const { loaded, network, available } = provider
|
||||
|
||||
if (!loaded) {
|
||||
showSnackbar(NOTIFICATIONS.CONNECT_WALLET_ERROR_MSG, enqueueSnackbar, closeSnackbar)
|
||||
dispatch(enqueueSnackbar(enhanceSnackbarForAction(NOTIFICATIONS.CONNECT_WALLET_ERROR_MSG)))
|
||||
return
|
||||
}
|
||||
|
||||
if (ETHEREUM_NETWORK_IDS[network] !== getNetwork()) {
|
||||
showSnackbar(NOTIFICATIONS.WRONG_NETWORK_MSG, enqueueSnackbar, closeSnackbar)
|
||||
dispatch(enqueueSnackbar(enhanceSnackbarForAction(NOTIFICATIONS.WRONG_NETWORK_MSG)))
|
||||
return
|
||||
}
|
||||
if (ETHEREUM_NETWORK.RINKEBY === getNetwork()) {
|
||||
showSnackbar(NOTIFICATIONS.RINKEBY_VERSION_MSG, enqueueSnackbar, closeSnackbar)
|
||||
dispatch(enqueueSnackbar(enhanceSnackbarForAction(NOTIFICATIONS.RINKEBY_VERSION_MSG)))
|
||||
}
|
||||
|
||||
if (available) {
|
||||
|
@ -49,15 +48,14 @@ const handleProviderNotification = (
|
|||
// you SHOULD pass your own `key` in the options. `key` can be any sequence
|
||||
// of number or characters, but it has to be unique to a given snackbar.
|
||||
|
||||
showSnackbar(NOTIFICATIONS.WALLET_CONNECTED_MSG, enqueueSnackbar, closeSnackbar)
|
||||
dispatch(enqueueSnackbar(enhanceSnackbarForAction(NOTIFICATIONS.WALLET_CONNECTED_MSG)))
|
||||
} else {
|
||||
showSnackbar(NOTIFICATIONS.UNLOCK_WALLET_MSG, enqueueSnackbar, closeSnackbar)
|
||||
dispatch(enqueueSnackbar(enhanceSnackbarForAction(NOTIFICATIONS.UNLOCK_WALLET_MSG)))
|
||||
}
|
||||
}
|
||||
|
||||
export default (provider: ProviderProps, enqueueSnackbar: Function, closeSnackbar: Function) => (
|
||||
dispatch: ReduxDispatch<*>,
|
||||
) => {
|
||||
handleProviderNotification(provider, enqueueSnackbar, closeSnackbar)
|
||||
processProviderResponse(dispatch, provider)
|
||||
export default (provider: Object) => async (dispatch: ReduxDispatch<*>) => {
|
||||
const providerInfo: ProviderProps = await getProviderInfo(provider)
|
||||
await handleProviderNotification(providerInfo, dispatch)
|
||||
processProviderResponse(dispatch, providerInfo)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
export * from './addProvider'
|
||||
export * from './fetchProvider'
|
||||
export * from './removeProvider'
|
||||
export { default as addProvider } from './addProvider'
|
||||
export { default as fetchProvider } from './fetchProvider'
|
||||
export { default as removeProvider } from './removeProvider'
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
// @flow
|
||||
import { createAction } from 'redux-actions'
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { makeProvider, type ProviderProps, type Provider } from '~/logic/wallets/store/model/provider'
|
||||
import { NOTIFICATIONS, showSnackbar } from '~/logic/notifications'
|
||||
import addProvider from './addProvider'
|
||||
import { getWeb3, resetWeb3 } from '~/logic/wallets/getWeb3'
|
||||
|
||||
export default (enqueueSnackbar: Function, closeSnackbar: Function) => async (dispatch: ReduxDispatch<*>) => {
|
||||
const providerProps: ProviderProps = {
|
||||
name: '',
|
||||
available: false,
|
||||
loaded: false,
|
||||
account: '',
|
||||
network: 0,
|
||||
}
|
||||
export const REMOVE_PROVIDER = 'REMOVE_PROVIDER'
|
||||
|
||||
const provider: Provider = makeProvider(providerProps)
|
||||
const removeProvider = createAction<string, *, *>(REMOVE_PROVIDER)
|
||||
|
||||
export default (enqueueSnackbar: Function, closeSnackbar: Function) => (dispatch: ReduxDispatch<*>) => {
|
||||
showSnackbar(NOTIFICATIONS.WALLET_DISCONNECTED_MSG, enqueueSnackbar, closeSnackbar)
|
||||
|
||||
dispatch(addProvider(provider))
|
||||
const web3 = getWeb3()
|
||||
|
||||
if (web3.currentProvider && web3.currentProvider.close) {
|
||||
web3.currentProvider.close()
|
||||
}
|
||||
|
||||
resetWeb3()
|
||||
dispatch(removeProvider())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
// @flow
|
||||
import type { Store, AnyAction } from 'redux'
|
||||
import { type GlobalState } from '~/store/'
|
||||
import { ADD_PROVIDER, REMOVE_PROVIDER } from '../actions'
|
||||
import { getWeb3, getProviderInfo } from '~/logic/wallets/getWeb3'
|
||||
import { fetchProvider } from '~/logic/wallets/store/actions'
|
||||
import { loadFromStorage, saveToStorage, removeFromStorage } from '~/utils/storage'
|
||||
|
||||
const watchedActions = [ADD_PROVIDER, REMOVE_PROVIDER]
|
||||
|
||||
const LAST_USED_PROVIDER_KEY = 'LAST_USED_PROVIDER'
|
||||
|
||||
export const loadLastUsedProvider = async () => {
|
||||
const lastUsedProvider = await loadFromStorage(LAST_USED_PROVIDER_KEY)
|
||||
|
||||
return lastUsedProvider || ''
|
||||
}
|
||||
|
||||
let watcherInterval = null
|
||||
|
||||
const providerWatcherMware = (store: Store<GlobalState>) => (next: Function) => async (action: AnyAction) => {
|
||||
const handledAction = next(action)
|
||||
|
||||
if (watchedActions.includes(action.type)) {
|
||||
switch (action.type) {
|
||||
case ADD_PROVIDER: {
|
||||
const currentProviderProps = action.payload.toJS()
|
||||
|
||||
if (watcherInterval) {
|
||||
clearInterval(watcherInterval)
|
||||
}
|
||||
|
||||
saveToStorage(LAST_USED_PROVIDER_KEY, currentProviderProps.name)
|
||||
|
||||
watcherInterval = setInterval(async () => {
|
||||
const web3 = getWeb3()
|
||||
const providerInfo = await getProviderInfo(web3)
|
||||
|
||||
if (
|
||||
currentProviderProps.account !== providerInfo.account
|
||||
|| currentProviderProps.network !== providerInfo.network
|
||||
) {
|
||||
store.dispatch(fetchProvider(web3, () => {}, () => {}))
|
||||
}
|
||||
}, 2000)
|
||||
|
||||
break
|
||||
}
|
||||
case REMOVE_PROVIDER:
|
||||
clearInterval(watcherInterval)
|
||||
removeFromStorage(LAST_USED_PROVIDER_KEY)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return handledAction
|
||||
}
|
||||
|
||||
export default providerWatcherMware
|
|
@ -2,6 +2,7 @@
|
|||
import { handleActions, type ActionType } from 'redux-actions'
|
||||
import { makeProvider, type Provider } from '~/logic/wallets/store/model/provider'
|
||||
import addProvider, { ADD_PROVIDER } from '~/logic/wallets/store/actions/addProvider'
|
||||
import { REMOVE_PROVIDER } from '~/logic/wallets/store/actions/removeProvider'
|
||||
|
||||
export const PROVIDER_REDUCER_ID = 'providers'
|
||||
|
||||
|
@ -10,6 +11,7 @@ export type State = Provider
|
|||
export default handleActions<State, Function>(
|
||||
{
|
||||
[ADD_PROVIDER]: (state: State, { payload }: ActionType<typeof addProvider>) => makeProvider(payload),
|
||||
[REMOVE_PROVIDER]: () => makeProvider(),
|
||||
},
|
||||
makeProvider(),
|
||||
)
|
||||
|
|
|
@ -56,7 +56,7 @@ const Routes = ({ defaultSafe, location }: RoutesProps) => {
|
|||
|
||||
setInitialLoad(false)
|
||||
if (defaultSafe) {
|
||||
return <Redirect to={`${SAFELIST_ADDRESS}/${defaultSafe}`} />
|
||||
return <Redirect to={`${SAFELIST_ADDRESS}/${defaultSafe}/balances`} />
|
||||
}
|
||||
|
||||
return <Redirect to={WELCOME_ADDRESS} />
|
||||
|
|
|
@ -15,7 +15,7 @@ import { type SelectorProps } from '~/routes/load/container/selector'
|
|||
|
||||
const getSteps = () => ['Name and address', 'Owners', 'Review']
|
||||
|
||||
type Props = SelectorProps & {
|
||||
export type LayoutProps = SelectorProps & {
|
||||
onLoadSafeSubmit: (values: Object) => Promise<void>,
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import { shortVersionOf } from '~/logic/wallets/ethAddresses'
|
|||
import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
|
||||
import { getOwnerNameBy, getOwnerAddressBy, getNumOwnersFrom } from '~/routes/open/components/fields'
|
||||
import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS, THRESHOLD } from '~/routes/load/components/fields'
|
||||
import type { LayoutProps } from '../Layout'
|
||||
|
||||
const styles = () => ({
|
||||
root: {
|
||||
|
|
|
@ -47,7 +47,7 @@ class Load extends React.Component<Props> {
|
|||
|
||||
await loadSafe(safeName, safeAddress, owners, addSafe)
|
||||
|
||||
const url = `${SAFELIST_ADDRESS}/${safeAddress}`
|
||||
const url = `${SAFELIST_ADDRESS}/${safeAddress}/balances`
|
||||
history.push(url)
|
||||
} catch (error) {
|
||||
console.error('Error while loading the Safe', error)
|
||||
|
|
|
@ -42,10 +42,9 @@ export const createSafe = async (values: Object, userAccount: string, addSafe: A
|
|||
safeProps.owners = owners
|
||||
|
||||
addSafe(safeProps)
|
||||
|
||||
if (stillInOpeningView()) {
|
||||
const url = {
|
||||
pathname: `${SAFELIST_ADDRESS}/${safeContract.address}`,
|
||||
pathname: `${SAFELIST_ADDRESS}/${safeContract.address}/balances`,
|
||||
state: {
|
||||
name,
|
||||
tx: safe.tx,
|
||||
|
|
|
@ -72,34 +72,42 @@ const createTransaction = (
|
|||
|
||||
await tx
|
||||
.send(sendParams)
|
||||
.once('transactionHash', (hash) => {
|
||||
.once('transactionHash', async (hash) => {
|
||||
txHash = hash
|
||||
closeSnackbar(beforeExecutionKey)
|
||||
const pendingExecutionNotification: Notification = isExecution ? {
|
||||
message: notificationsQueue.pendingExecution.noMoreConfirmationsNeeded.message,
|
||||
options: notificationsQueue.pendingExecution.noMoreConfirmationsNeeded.options,
|
||||
} : {
|
||||
message: notificationsQueue.pendingExecution.moreConfirmationsNeeded.message,
|
||||
options: notificationsQueue.pendingExecution.moreConfirmationsNeeded.options,
|
||||
}
|
||||
const pendingExecutionNotification: Notification = isExecution
|
||||
? {
|
||||
message: notificationsQueue.pendingExecution.noMoreConfirmationsNeeded.message,
|
||||
options: notificationsQueue.pendingExecution.noMoreConfirmationsNeeded.options,
|
||||
}
|
||||
: {
|
||||
message: notificationsQueue.pendingExecution.moreConfirmationsNeeded.message,
|
||||
options: notificationsQueue.pendingExecution.moreConfirmationsNeeded.options,
|
||||
}
|
||||
pendingExecutionKey = showSnackbar(pendingExecutionNotification, enqueueSnackbar, closeSnackbar)
|
||||
|
||||
try {
|
||||
await saveTxToHistory(
|
||||
safeInstance,
|
||||
to,
|
||||
valueInWei,
|
||||
txData,
|
||||
CALL,
|
||||
nonce,
|
||||
txHash,
|
||||
from,
|
||||
isExecution ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
|
||||
)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
.on('error', (error) => {
|
||||
console.error('Tx error: ', error)
|
||||
})
|
||||
.then(async (receipt) => {
|
||||
.then((receipt) => {
|
||||
closeSnackbar(pendingExecutionKey)
|
||||
await saveTxToHistory(
|
||||
safeInstance,
|
||||
to,
|
||||
valueInWei,
|
||||
txData,
|
||||
CALL,
|
||||
nonce,
|
||||
receipt.transactionHash,
|
||||
from,
|
||||
isExecution ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
|
||||
)
|
||||
|
||||
if (isExecution) {
|
||||
showSnackbar(notificationsQueue.afterExecution, enqueueSnackbar, closeSnackbar)
|
||||
}
|
||||
|
|
|
@ -130,7 +130,11 @@ export const loadSafeTransactions = async (safeAddress: string) => {
|
|||
}
|
||||
|
||||
export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
const transactions: Map<string, List<Transaction>> = await loadSafeTransactions(safeAddress)
|
||||
try {
|
||||
const transactions: Map<string, List<Transaction>> = await loadSafeTransactions(safeAddress)
|
||||
|
||||
return dispatch(addTransactions(transactions))
|
||||
return dispatch(addTransactions(transactions))
|
||||
} catch (err) {
|
||||
console.error(`Requests for transactions for ${safeAddress} failed with 404`)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,10 @@ import { getErrorMessage } from '~/test/utils/ethereumErrors'
|
|||
|
||||
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
|
||||
// https://github.com/gnosis/safe-contracts/blob/master/test/gnosisSafeTeamEdition.js#L26
|
||||
export const generateSignaturesFromTxConfirmations = (confirmations: List<Confirmation>, preApprovingOwner?: string) => {
|
||||
export const generateSignaturesFromTxConfirmations = (
|
||||
confirmations: List<Confirmation>,
|
||||
preApprovingOwner?: string,
|
||||
) => {
|
||||
// The constant parts need to be sorted so that the recovered signers are sorted ascending
|
||||
// (natural order) by address (not checksummed).
|
||||
let confirmedAdresses = confirmations.map((conf) => conf.owner.address)
|
||||
|
@ -101,7 +104,7 @@ const processTransaction = (
|
|||
|
||||
await transaction
|
||||
.send(sendParams)
|
||||
.once('transactionHash', (hash) => {
|
||||
.once('transactionHash', async (hash) => {
|
||||
txHash = hash
|
||||
closeSnackbar(beforeExecutionKey)
|
||||
const notification: Notification = {
|
||||
|
@ -109,23 +112,29 @@ const processTransaction = (
|
|||
options: notificationsQueue.pendingExecution.noMoreConfirmationsNeeded.options,
|
||||
}
|
||||
pendingExecutionKey = showSnackbar(notification, enqueueSnackbar, closeSnackbar)
|
||||
|
||||
try {
|
||||
await saveTxToHistory(
|
||||
safeInstance,
|
||||
tx.recipient,
|
||||
tx.value,
|
||||
tx.data,
|
||||
CALL,
|
||||
nonce,
|
||||
txHash,
|
||||
from,
|
||||
shouldExecute ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
|
||||
)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
.on('error', (error) => {
|
||||
console.error('Processing transaction error: ', error)
|
||||
})
|
||||
.then(async (receipt) => {
|
||||
.then((receipt) => {
|
||||
closeSnackbar(pendingExecutionKey)
|
||||
await saveTxToHistory(
|
||||
safeInstance,
|
||||
tx.recipient,
|
||||
tx.value,
|
||||
tx.data,
|
||||
CALL,
|
||||
nonce,
|
||||
receipt.transactionHash,
|
||||
from,
|
||||
shouldExecute ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
|
||||
)
|
||||
|
||||
showSnackbar(notificationsQueue.afterExecution, enqueueSnackbar, closeSnackbar)
|
||||
dispatch(fetchTransactions(safeAddress))
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import Heading from '~/components/layout/Heading'
|
|||
import Img from '~/components/layout/Img'
|
||||
import Button from '~/components/layout/Button'
|
||||
import Link from '~/components/layout/Link'
|
||||
import ConnectButton from '~/components/ConnectButton'
|
||||
import { OPEN_ADDRESS, LOAD_ADDRESS } from '~/routes/routes'
|
||||
import { marginButtonImg, secondary } from '~/theme/variables'
|
||||
import styles from './Layout.scss'
|
||||
|
@ -84,12 +85,23 @@ const Welcome = ({ provider }: Props) => (
|
|||
<OpenInNew style={openIconStyle} />
|
||||
</a>
|
||||
</Heading>
|
||||
<Block className={styles.safeActions} margin="md">
|
||||
<CreateSafe size="large" provider={provider} />
|
||||
</Block>
|
||||
<Block className={styles.safeActions} margin="md">
|
||||
<LoadSafe size="large" provider={provider} />
|
||||
</Block>
|
||||
{provider ? (
|
||||
<>
|
||||
<Block className={styles.safeActions} margin="md">
|
||||
<CreateSafe size="large" provider={provider} />
|
||||
</Block>
|
||||
<Block className={styles.safeActions} margin="md">
|
||||
<LoadSafe size="large" provider={provider} />
|
||||
</Block>
|
||||
</>
|
||||
) : (
|
||||
<Block margin="md" className={styles.connectWallet}>
|
||||
<Heading tag="h3" align="center" margin="md">
|
||||
Get Started by Connecting a Wallet
|
||||
</Heading>
|
||||
<ConnectButton minWidth={240} minHeight={42} />
|
||||
</Block>
|
||||
)}
|
||||
</Block>
|
||||
)
|
||||
|
||||
|
|
|
@ -17,3 +17,7 @@
|
|||
.learnMoreLink {
|
||||
color: $secondary;
|
||||
}
|
||||
|
||||
.connectWallet {
|
||||
text-align: center;
|
||||
}
|
|
@ -7,6 +7,7 @@ import {
|
|||
import thunk from 'redux-thunk'
|
||||
import safe, { SAFE_REDUCER_ID, type SafeReducerState as SafeState } from '~/routes/safe/store/reducer/safe'
|
||||
import safeStorage from '~/routes/safe/store/middleware/safeStorage'
|
||||
import providerWatcher from '~/logic/wallets/store/middlewares/providerWatcher'
|
||||
import transactions, {
|
||||
type State as TransactionsState,
|
||||
TRANSACTIONS_REDUCER_ID,
|
||||
|
@ -22,7 +23,9 @@ export const history = createBrowserHistory()
|
|||
|
||||
// eslint-disable-next-line
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
||||
const finalCreateStore = composeEnhancers(applyMiddleware(thunk, routerMiddleware(history), safeStorage))
|
||||
const finalCreateStore = composeEnhancers(
|
||||
applyMiddleware(thunk, routerMiddleware(history), safeStorage, providerWatcher),
|
||||
)
|
||||
|
||||
export type GlobalState = {
|
||||
providers: ProviderState,
|
||||
|
@ -45,8 +48,4 @@ const reducers: Reducer<GlobalState> = combineReducers({
|
|||
|
||||
export const store: Store<GlobalState> = createStore(reducers, finalCreateStore)
|
||||
|
||||
export const aNewStore = (localState?: Object): Store<GlobalState> => createStore(
|
||||
reducers,
|
||||
localState,
|
||||
finalCreateStore,
|
||||
)
|
||||
export const aNewStore = (localState?: Object): Store<GlobalState> => createStore(reducers, localState, finalCreateStore)
|
||||
|
|
|
@ -102,7 +102,7 @@ const renderApp = (store: Store) => ({
|
|||
export const renderSafeView = (store: Store<GlobalState>, address: string) => {
|
||||
const app = renderApp(store)
|
||||
|
||||
const url = `${SAFELIST_ADDRESS}/${address}`
|
||||
const url = `${SAFELIST_ADDRESS}/${address}/balances`
|
||||
history.push(url)
|
||||
|
||||
return app
|
||||
|
@ -115,7 +115,7 @@ export const whenSafeDeployed = (): Promise<string> => new Promise((resolve, rej
|
|||
const interval = setInterval(() => {
|
||||
if (times >= MAX_TIMES_EXECUTED) {
|
||||
clearInterval(interval)
|
||||
reject()
|
||||
reject(new Error('Didn\'t load the safe'))
|
||||
}
|
||||
const url = `${window.location}`
|
||||
console.log(url)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import SafeRecord, { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import addSafe, { buildOwnersFrom } from '~/routes/safe/store/actions/addSafe'
|
||||
import {
|
||||
|
@ -74,7 +75,7 @@ export const aMinedSafe = async (
|
|||
threshold: number = 1,
|
||||
name: string = 'Safe Name',
|
||||
): Promise<string> => {
|
||||
const provider = await getProviderInfo()
|
||||
const provider = await getProviderInfo(window.web3.currentProvider)
|
||||
const walletRecord = makeProvider(provider)
|
||||
store.dispatch(addProvider(walletRecord))
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ afterAll(() => {
|
|||
})
|
||||
|
||||
const renderOpenSafeForm = async (localStore: Store<GlobalState>) => {
|
||||
const provider = await getProviderInfo()
|
||||
const provider = await getProviderInfo(window.web3.currentProvider)
|
||||
const walletRecord = makeProvider(provider)
|
||||
localStore.dispatch(addProvider(walletRecord))
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import * as React from 'react'
|
||||
import { type Store } from 'redux'
|
||||
import { Provider } from 'react-redux'
|
||||
import { render, fireEvent } from '@testing-library/react'
|
||||
import { render, fireEvent, act } from '@testing-library/react'
|
||||
import { ConnectedRouter } from 'connected-react-router'
|
||||
import Load from '~/routes/load/container/Load'
|
||||
import { aNewStore, history, type GlobalState } from '~/store'
|
||||
|
@ -29,7 +29,7 @@ afterAll(() => {
|
|||
})
|
||||
|
||||
const renderLoadSafe = async (localStore: Store<GlobalState>) => {
|
||||
const provider = await getProviderInfo()
|
||||
const provider = await getProviderInfo(window.web3.currentProvider)
|
||||
const walletRecord = makeProvider(provider)
|
||||
localStore.dispatch(addProvider(walletRecord))
|
||||
|
||||
|
@ -52,19 +52,18 @@ describe('DOM > Feature > LOAD a Safe', () => {
|
|||
const safeAddressInput = LoadSafePage.getByPlaceholderText('Safe Address*')
|
||||
|
||||
// Fill Safe's name
|
||||
fireEvent.change(safeNameInput, { target: { value: 'A Safe To Load' } })
|
||||
fireEvent.change(safeAddressInput, { target: { value: address } })
|
||||
await sleep(400)
|
||||
// Click next
|
||||
fireEvent.submit(form)
|
||||
await sleep(400)
|
||||
await act(async () => {
|
||||
fireEvent.change(safeNameInput, { target: { value: 'A Safe To Load' } })
|
||||
fireEvent.change(safeAddressInput, { target: { value: address } })
|
||||
fireEvent.submit(form)
|
||||
|
||||
// submit form with owners names
|
||||
fireEvent.submit(form)
|
||||
await sleep(400)
|
||||
await sleep(500)
|
||||
fireEvent.submit(form)
|
||||
|
||||
await sleep(500)
|
||||
fireEvent.submit(form)
|
||||
})
|
||||
|
||||
// Submit
|
||||
fireEvent.submit(form)
|
||||
const deployedAddress = await whenSafeDeployed()
|
||||
expect(deployedAddress).toBe(address)
|
||||
})
|
||||
|
|
|
@ -40,6 +40,31 @@ import {
|
|||
} from '~/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/OwnerForm'
|
||||
import { REPLACE_OWNER_SUBMIT_BTN_TEST_ID } from '~/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/Review'
|
||||
|
||||
const originalError = console.error
|
||||
beforeAll(() => {
|
||||
console.error = (...args) => {
|
||||
if (/Warning.*not wrapped in act/.test(args[0])) {
|
||||
return
|
||||
}
|
||||
originalError.call(console, ...args)
|
||||
}
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
console.error = originalError
|
||||
})
|
||||
|
||||
const travelToOwnerSettings = async (dom) => {
|
||||
const settingsBtn = await waitForElement(() => dom.getByTestId(SETTINGS_TAB_BTN_TEST_ID))
|
||||
fireEvent.click(settingsBtn)
|
||||
|
||||
// click on owners settings
|
||||
const ownersSettingsBtn = await waitForElement(() => dom.getByTestId(OWNERS_SETTINGS_TAB_TEST_ID))
|
||||
fireEvent.click(ownersSettingsBtn)
|
||||
|
||||
await sleep(500)
|
||||
}
|
||||
|
||||
describe('DOM > Feature > Settings - Manage owners', () => {
|
||||
let store
|
||||
let safeAddress
|
||||
|
@ -55,12 +80,7 @@ describe('DOM > Feature > Settings - Manage owners', () => {
|
|||
await sleep(1300)
|
||||
|
||||
// Travel to settings
|
||||
const settingsBtn = await waitForElement(() => SafeDom.getByTestId(SETTINGS_TAB_BTN_TEST_ID))
|
||||
fireEvent.click(settingsBtn)
|
||||
|
||||
// click on owners settings
|
||||
const ownersSettingsBtn = await waitForElement(() => SafeDom.getByTestId(OWNERS_SETTINGS_TAB_TEST_ID))
|
||||
fireEvent.click(ownersSettingsBtn)
|
||||
await travelToOwnerSettings(SafeDom)
|
||||
|
||||
// open rename owner modal
|
||||
const renameOwnerBtn = await waitForElement(() => SafeDom.getByTestId(RENAME_OWNER_BTN_TEST_ID))
|
||||
|
@ -85,14 +105,7 @@ describe('DOM > Feature > Settings - Manage owners', () => {
|
|||
await sleep(1300)
|
||||
|
||||
// Travel to settings
|
||||
const settingsBtn = SafeDom.getByTestId(SETTINGS_TAB_BTN_TEST_ID)
|
||||
fireEvent.click(settingsBtn)
|
||||
await sleep(200)
|
||||
|
||||
// click on owners settings
|
||||
const ownersSettingsBtn = SafeDom.getByTestId(OWNERS_SETTINGS_TAB_TEST_ID)
|
||||
fireEvent.click(ownersSettingsBtn)
|
||||
await sleep(200)
|
||||
await travelToOwnerSettings(SafeDom)
|
||||
|
||||
// check if there are 2 owners
|
||||
let ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TEST_ID)
|
||||
|
@ -118,6 +131,8 @@ describe('DOM > Feature > Settings - Manage owners', () => {
|
|||
await sleep(1300)
|
||||
|
||||
// check if owner was removed
|
||||
await travelToOwnerSettings(SafeDom)
|
||||
|
||||
ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TEST_ID)
|
||||
expect(ownerRows.length).toBe(1)
|
||||
expect(ownerRows[0]).toHaveTextContent('Adol 1 Eth Account')
|
||||
|
@ -135,14 +150,7 @@ describe('DOM > Feature > Settings - Manage owners', () => {
|
|||
await sleep(1300)
|
||||
|
||||
// Travel to settings
|
||||
const settingsBtn = SafeDom.getByTestId(SETTINGS_TAB_BTN_TEST_ID)
|
||||
fireEvent.click(settingsBtn)
|
||||
await sleep(200)
|
||||
|
||||
// click on owners settings
|
||||
const ownersSettingsBtn = SafeDom.getByTestId(OWNERS_SETTINGS_TAB_TEST_ID)
|
||||
fireEvent.click(ownersSettingsBtn)
|
||||
await sleep(200)
|
||||
await travelToOwnerSettings(SafeDom)
|
||||
|
||||
// check if there is 1 owner
|
||||
let ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TEST_ID)
|
||||
|
@ -169,13 +177,14 @@ describe('DOM > Feature > Settings - Manage owners', () => {
|
|||
await sleep(1500)
|
||||
|
||||
// check if owner was added
|
||||
await travelToOwnerSettings(SafeDom)
|
||||
|
||||
ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TEST_ID)
|
||||
expect(ownerRows.length).toBe(2)
|
||||
expect(ownerRows[0]).toHaveTextContent('Adol 1 Eth Account')
|
||||
expect(ownerRows[0]).toHaveTextContent('0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1')
|
||||
expect(ownerRows[1]).toHaveTextContent(NEW_OWNER_NAME)
|
||||
expect(ownerRows[1]).toHaveTextContent(NEW_OWNER_ADDRESS)
|
||||
|
||||
// Check that the transaction was registered
|
||||
await checkRegisteredTxAddOwner(SafeDom, NEW_OWNER_ADDRESS)
|
||||
})
|
||||
|
@ -190,14 +199,7 @@ describe('DOM > Feature > Settings - Manage owners', () => {
|
|||
await sleep(1300)
|
||||
|
||||
// Travel to settings
|
||||
const settingsBtn = SafeDom.getByTestId(SETTINGS_TAB_BTN_TEST_ID)
|
||||
fireEvent.click(settingsBtn)
|
||||
await sleep(200)
|
||||
|
||||
// click on owners settings
|
||||
const ownersSettingsBtn = SafeDom.getByTestId(OWNERS_SETTINGS_TAB_TEST_ID)
|
||||
fireEvent.click(ownersSettingsBtn)
|
||||
await sleep(200)
|
||||
await travelToOwnerSettings(SafeDom)
|
||||
|
||||
// check if there are 2 owners
|
||||
let ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TEST_ID)
|
||||
|
@ -225,6 +227,8 @@ describe('DOM > Feature > Settings - Manage owners', () => {
|
|||
await sleep(1000)
|
||||
|
||||
// check if the owner was replaced
|
||||
await travelToOwnerSettings(SafeDom)
|
||||
|
||||
ownerRows = SafeDom.getAllByTestId(OWNERS_ROW_TEST_ID)
|
||||
expect(ownerRows.length).toBe(2)
|
||||
expect(ownerRows[0]).toHaveTextContent('Adol 1 Eth Account')
|
||||
|
|
|
@ -4,7 +4,7 @@ import { aNewStore } from '~/store'
|
|||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { renderSafeView } from '~/test/builder/safe.dom.utils'
|
||||
import '@testing-library/jest-dom/extend-expect'
|
||||
import { TOGGLE_SIDEBAR_BTN_TESTID } from '~/components/Header/component/SafeListHeader'
|
||||
import { TOGGLE_SIDEBAR_BTN_TESTID } from '~/components/Header/components/SafeListHeader'
|
||||
import { SIDEBAR_SAFELIST_ROW_TESTID } from '~/components/Sidebar/SafeList'
|
||||
import { sleep } from '~/utils/timer'
|
||||
|
||||
|
|
Loading…
Reference in New Issue