diff --git a/src/components/Header/assets/gnosis-safe-icon.svg b/src/components/Header/assets/gnosis-safe-icon.svg new file mode 100644 index 00000000..7c7d342a --- /dev/null +++ b/src/components/Header/assets/gnosis-safe-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Header/assets/metamask.svg b/src/components/Header/assets/metamask-icon.svg similarity index 100% rename from src/components/Header/assets/metamask.svg rename to src/components/Header/assets/metamask-icon.svg diff --git a/src/components/Header/component/ProviderDetails/UserDetails.jsx b/src/components/Header/component/ProviderDetails/UserDetails.jsx index e90b3bbc..4304541d 100644 --- a/src/components/Header/component/ProviderDetails/UserDetails.jsx +++ b/src/components/Header/component/ProviderDetails/UserDetails.jsx @@ -21,7 +21,8 @@ import { shortVersionOf } from '~/logic/wallets/ethAddresses' import { getEtherScanLink } from '~/logic/wallets/getWeb3' import CircleDot from '~/components/Header/component/CircleDot' -const metamask = require('../../assets/metamask.svg') +const metamaskIcon = require('../../assets/metamask-icon.svg') +const safeIcon = require('../../assets/gnosis-safe-icon.svg') const dot = require('../../assets/dotRinkeby.svg') type Props = { @@ -132,7 +133,6 @@ const UserDetails = ({ Status - {' '} @@ -144,10 +144,12 @@ const UserDetails = ({ Client - {' '} - Metamask client + {provider === 'safe' + ? Safe client + : Metamask client + } {upperFirst(provider)} @@ -156,7 +158,6 @@ const UserDetails = ({ Network - {' '} Network diff --git a/src/components/Header/index.jsx b/src/components/Header/index.jsx index 4ff64c7f..a45b2611 100644 --- a/src/components/Header/index.jsx +++ b/src/components/Header/index.jsx @@ -4,6 +4,7 @@ import { connect } from 'react-redux' import { logComponentStack, type Info } from '~/utils/logBoundaries' import { SharedSnackbarConsumer, type Variant } from '~/components/SharedSnackBar/Context' import { WALLET_ERROR_MSG } from '~/logic/wallets/store/actions' +import { getProviderInfo } from '~/logic/wallets/getWeb3' import ProviderAccesible from './component/ProviderInfo/ProviderAccesible' import UserDetails from './component/ProviderDetails/UserDetails' import ProviderDisconnected from './component/ProviderInfo/ProviderDisconnected' @@ -30,16 +31,35 @@ class HeaderComponent extends React.PureComponent { this.onConnect() } + componentDidCatch(error: Error, info: Info) { + const { openSnackbar } = this.props + this.setState({ hasError: true }) + openSnackbar(WALLET_ERROR_MSG, 'error') + + logComponentStack(error, info) + } + onDisconnect = () => { const { removeProvider, openSnackbar } = this.props + clearInterval(this.providerListener) removeProvider(openSnackbar) } - onConnect = () => { + onConnect = async () => { const { fetchProvider, openSnackbar } = this.props - fetchProvider(openSnackbar) + clearInterval(this.providerListener) + let currentProvider: ProviderProps = await getProviderInfo() + fetchProvider(currentProvider, openSnackbar) + + this.providerListener = setInterval(async () => { + const newProvider: ProviderProps = await getProviderInfo() + if (JSON.stringify(currentProvider) !== JSON.stringify(newProvider)) { + fetchProvider(newProvider, openSnackbar) + } + currentProvider = newProvider + }, 2000) } getProviderInfoBased = () => { @@ -76,14 +96,6 @@ class HeaderComponent extends React.PureComponent { ) } - componentDidCatch(error: Error, info: Info) { - const { openSnackbar } = this.props - this.setState({ hasError: true }) - openSnackbar(WALLET_ERROR_MSG, 'error') - - logComponentStack(error, info) - } - render() { const info = this.getProviderInfoBased() const details = this.getProviderDetailsBased() diff --git a/src/components/layout/Hairline/index.js b/src/components/layout/Hairline/index.js index b23445d8..223244f0 100644 --- a/src/components/layout/Hairline/index.js +++ b/src/components/layout/Hairline/index.js @@ -14,7 +14,7 @@ const calculateStyleFrom = (color?: string, margin?: Size) => ({ type Props = { margin?: Size, color?: string, - style?: Object + style?: Object, } const Hairline = ({ margin, color, style }: Props) => { diff --git a/src/logic/contracts/safeContracts.js b/src/logic/contracts/safeContracts.js index e2108346..cc4cab95 100644 --- a/src/logic/contracts/safeContracts.js +++ b/src/logic/contracts/safeContracts.js @@ -75,7 +75,7 @@ export const deploySafeContract = async (safeAccounts: string[], numConfirmation export const getGnosisSafeInstanceAt = async (safeAddress: string) => { const web3 = getWeb3() - const GnosisSafe = getGnosisSafeContract(web3) + const GnosisSafe = await getGnosisSafeContract(web3) const gnosisSafe = await GnosisSafe.at(safeAddress) return gnosisSafe diff --git a/src/logic/tokens/store/actions/fetchTokens.js b/src/logic/tokens/store/actions/fetchTokens.js index 204dc54a..04d8a1c3 100644 --- a/src/logic/tokens/store/actions/fetchTokens.js +++ b/src/logic/tokens/store/actions/fetchTokens.js @@ -15,9 +15,9 @@ const createStandardTokenContract = async () => { const web3 = getWeb3() const erc20Token = await contract(StandardToken) erc20Token.setProvider(web3.currentProvider) - return erc20Token } + const createHumanFriendlyTokenContract = async () => { const web3 = getWeb3() const humanErc20Token = await contract(HumanFriendlyToken) diff --git a/src/logic/tokens/store/actions/loadActiveTokens.js b/src/logic/tokens/store/actions/loadActiveTokens.js index 7ca0aa80..e93e35fd 100644 --- a/src/logic/tokens/store/actions/loadActiveTokens.js +++ b/src/logic/tokens/store/actions/loadActiveTokens.js @@ -9,7 +9,6 @@ import saveTokens from './saveTokens' const loadActiveTokens = () => async (dispatch: ReduxDispatch) => { try { const tokens: Map = await getActiveTokens() - const tokenRecordsList: List = List( Object.values(tokens).map((token: TokenProps): Token => makeToken(token)), ) diff --git a/src/logic/wallets/getWeb3.js b/src/logic/wallets/getWeb3.js index aecfaaa4..390f6b43 100644 --- a/src/logic/wallets/getWeb3.js +++ b/src/logic/wallets/getWeb3.js @@ -12,6 +12,7 @@ export const ETHEREUM_NETWORK = { } export const WALLET_PROVIDER = { + SAFE: 'SAFE', METAMASK: 'METAMASK', PARITY: 'PARITY', REMOTE: 'REMOTE', @@ -36,12 +37,23 @@ export const openTxInEtherScan = (tx: string, network: string) => `https://${net export const getEtherScanLink = (address: string, network: string) => `https://${network}.etherscan.io/address/${address}` let web3 -export const getWeb3 = () => web3 || new Web3(window.web3.currentProvider) +export const getWeb3 = () => web3 || (window.web3 && new Web3(window.web3.currentProvider)) || (window.ethereum && new Web3(window.ethereum)) -const isMetamask: Function = (web3Provider): boolean => { - const isMetamaskConstructor = web3Provider.currentProvider.constructor.name === 'MetamaskInpageProvider' +const getProviderName: Function = (web3Provider): boolean => { + let name - return isMetamaskConstructor || web3Provider.currentProvider.isMetaMask + switch (web3Provider.currentProvider.constructor.name) { + case 'SafeWeb3Provider': + name = WALLET_PROVIDER.SAFE + break + case 'MetamaskInpageProvider': + name = WALLET_PROVIDER.METAMASK + break + default: + name = 'UNKNOWN' + } + + return name } const getAccountFrom: Function = async (web3Provider): Promise => { @@ -57,7 +69,14 @@ const getNetworkIdFrom = async (web3Provider) => { } export const getProviderInfo: Function = async (): Promise => { - if (typeof window.web3 === 'undefined') { + let web3Provider + + if (window.ethereum) { + web3Provider = window.ethereum + await web3Provider.enable() + } else if (window.web3) { + web3Provider = window.web3.currentProvider + } else { return { name: '', available: false, @@ -67,15 +86,9 @@ export const getProviderInfo: Function = async (): Promise => { } } - // Use MetaMask's provider. - web3 = new Web3(window.web3.currentProvider) + web3 = new Web3(web3Provider) - if (process.env.NODE_ENV !== 'test') { - // eslint-disable-next-line - console.log('Injected web3 detected.') - } - - const name = isMetamask(window.web3) ? WALLET_PROVIDER.METAMASK : 'UNKNOWN' + const name = getProviderName(web3) const account = await getAccountFrom(web3) const network = await getNetworkIdFrom(web3) diff --git a/src/logic/wallets/store/actions/fetchProvider.js b/src/logic/wallets/store/actions/fetchProvider.js index 306664b0..c19f52ce 100644 --- a/src/logic/wallets/store/actions/fetchProvider.js +++ b/src/logic/wallets/store/actions/fetchProvider.js @@ -1,6 +1,6 @@ // @flow import type { Dispatch as ReduxDispatch } from 'redux' -import { getProviderInfo, ETHEREUM_NETWORK_IDS, ETHEREUM_NETWORK } from '~/logic/wallets/getWeb3' +import { ETHEREUM_NETWORK_IDS, ETHEREUM_NETWORK } from '~/logic/wallets/getWeb3' import type { ProviderProps } from '~/logic/wallets/store/model/provider' import { makeProvider } from '~/logic/wallets/store/model/provider' import addProvider from './addProvider' @@ -44,10 +44,7 @@ const handleProviderNotification = (openSnackbar: Function, provider: ProviderPr openSnackbar(msg, variant) } -export default (openSnackbar: Function) => async (dispatch: ReduxDispatch<*>) => { - const provider: ProviderProps = await getProviderInfo() - +export default (provider: ProviderProps, openSnackbar: Function) => (dispatch: ReduxDispatch<*>) => { handleProviderNotification(openSnackbar, provider) - processProviderResponse(dispatch, provider) } diff --git a/src/logic/wallets/store/test/provider.reducer.js b/src/logic/wallets/store/test/provider.reducer.js index 5cdb3c30..c2d648c6 100644 --- a/src/logic/wallets/store/test/provider.reducer.js +++ b/src/logic/wallets/store/test/provider.reducer.js @@ -20,9 +20,9 @@ const providerReducerTests = () => { store = createStore(reducers, compose(...enhancers)) }) - it('reducer should return default Provider record when no Metamask is loaded', () => { + it('reducer should return default Provider record when no provider is loaded', () => { // GIVEN - const emptyResponse: ProviderProps = { + const emptyProvider: ProviderProps = { name: '', loaded: false, available: false, @@ -31,17 +31,17 @@ const providerReducerTests = () => { } // WHEN - processProviderResponse(store.dispatch, emptyResponse) + processProviderResponse(store.dispatch, emptyProvider) const provider = store.getState()[PROVIDER_REDUCER_ID] // THEN - expect(makeProvider(emptyResponse)).toEqual(provider) + expect(makeProvider(emptyProvider)).toEqual(provider) }) it('reducer should return avaiable with its default value when is loaded but not available', () => { // GIVEN - const metamaskLoaded: ProviderProps = { - name: 'METAMASK', + const providerLoaded: ProviderProps = { + name: 'SAFE', loaded: true, available: false, account: '', @@ -49,17 +49,17 @@ const providerReducerTests = () => { } // WHEN - processProviderResponse(store.dispatch, metamaskLoaded) + processProviderResponse(store.dispatch, providerLoaded) const provider = store.getState()[PROVIDER_REDUCER_ID] // THEN - expect(makeProvider(metamaskLoaded)).toEqual(provider) + expect(makeProvider(providerLoaded)).toEqual(provider) }) - it('reducer should return metamask provider when it is loaded and available', () => { + it('reducer should return provider when it is loaded and available', () => { // GIVEN - const metamask: ProviderProps = { - name: 'METAMASK', + const providerLoaded: ProviderProps = { + name: 'SAFE', loaded: true, available: true, account: '', @@ -67,11 +67,11 @@ const providerReducerTests = () => { } // WHEN - processProviderResponse(store.dispatch, metamask) + processProviderResponse(store.dispatch, providerLoaded) const provider = store.getState()[PROVIDER_REDUCER_ID] // THEN - expect(makeProvider(metamask)).toEqual(provider) + expect(makeProvider(providerLoaded)).toEqual(provider) }) }) } diff --git a/src/routes/safe/store/actions/addSafe.js b/src/routes/safe/store/actions/addSafe.js index 0753e14d..97f3a312 100644 --- a/src/routes/safe/store/actions/addSafe.js +++ b/src/routes/safe/store/actions/addSafe.js @@ -25,13 +25,9 @@ export const addSafe = createAction( }), ) -const saveSafe = ( - name: string, - address: string, - threshold: number, - ownersName: string[], - ownersAddress: string[], -) => (dispatch: ReduxDispatch) => { +const saveSafe = (name: string, address: string, threshold: number, ownersName: string[], ownersAddress: string[]) => ( + dispatch: ReduxDispatch, +) => { const owners: List = buildOwnersFrom(ownersName, ownersAddress) const safe: Safe = SafeRecord({ diff --git a/src/routes/safe/store/actions/fetchTokenBalances.js b/src/routes/safe/store/actions/fetchTokenBalances.js index a9b3d503..d9dbcd91 100644 --- a/src/routes/safe/store/actions/fetchTokenBalances.js +++ b/src/routes/safe/store/actions/fetchTokenBalances.js @@ -13,7 +13,6 @@ export const calculateBalanceOf = async (tokenAddress: string, safeAddress: stri if (tokenAddress === ETH_ADDRESS) { return '0' } - const erc20Token = await getStandardTokenContract() let balance = 0 @@ -33,15 +32,16 @@ const fetchTokenBalances = (safeAddress: string, tokens: List) => async ( if (!safeAddress || !tokens || !tokens.size) { return } - try { const withBalances = await Promise.all( - tokens.map(async token => TokenBalanceRecord({ - address: token.address, - balance: await calculateBalanceOf(token.address, safeAddress, token.decimals), - })), + tokens.map(async (token) => { + const balance = await calculateBalanceOf(token.address, safeAddress, token.decimals) + return TokenBalanceRecord({ + address: token.address, + balance, + }) + }), ) - dispatch(updateSafe({ address: safeAddress, balances: List(withBalances) })) } catch (err) { // eslint-disable-next-line diff --git a/src/routes/welcome/components/Layout.stories.js b/src/routes/welcome/components/Layout.stories.js index fc696c66..d5b5c62c 100644 --- a/src/routes/welcome/components/Layout.stories.js +++ b/src/routes/welcome/components/Layout.stories.js @@ -9,15 +9,19 @@ const FrameDecorator = story =>
{story()}
storiesOf('Routes /welcome', module) .addDecorator(FrameDecorator) + .add('Welcome with Gnosis Safe connected', () => { + const provider = select('Status by Provider', ['', 'UNKNOWN', 'SAFE', 'METAMASK', 'PARITY'], 'SAFE') + return {}} /> + }) .add('Welcome with Metamask connected', () => { - const provider = select('Status by Provider', ['', 'UNKNOWN', 'METAMASK', 'PARITY'], 'METAMASK') + const provider = select('Status by Provider', ['', 'UNKNOWN', 'SAFE', 'METAMASK', 'PARITY'], 'METAMASK') return {}} /> }) .add('Welcome with unknown wallet', () => { - const provider = select('Status by Provider', ['', 'UNKNOWN', 'METAMASK', 'PARITY'], 'UNKNOWN') + const provider = select('Status by Provider', ['', 'UNKNOWN', 'SAFE', 'METAMASK', 'PARITY'], 'UNKNOWN') return {}} /> }) .add('Welcome without wallet connected', () => { - const provider = select('Status by Provider', ['', 'UNKNOWN', 'METAMASK', 'PARITY'], '') + const provider = select('Status by Provider', ['', 'UNKNOWN', 'SAFE', 'METAMASK', 'PARITY'], '') return {}} /> }) diff --git a/src/test/safe.dom.load.test.js b/src/test/safe.dom.load.test.js index d909127b..7b07b7ed 100644 --- a/src/test/safe.dom.load.test.js +++ b/src/test/safe.dom.load.test.js @@ -30,7 +30,6 @@ afterAll(() => { console.error = originalError }) - const renderLoadSafe = async (localStore: Store) => { const provider = await getProviderInfo() const walletRecord = makeProvider(provider)