Merge pull request #116 from gnosis/web3-providers-refactor
Web3 providers refactoring
This commit is contained in:
commit
eb8b3585fa
|
@ -0,0 +1 @@
|
|||
<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="#4a5579" 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>
|
After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 557 B After Width: | Height: | Size: 557 B |
|
@ -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 = ({
|
|||
<Row className={classes.details}>
|
||||
<Paragraph noMargin align="right" className={classes.labels}>
|
||||
Status
|
||||
{' '}
|
||||
</Paragraph>
|
||||
<Spacer />
|
||||
<Dot className={classNames(classes.dot, connected ? classes.connected : classes.warning)} />
|
||||
|
@ -144,10 +144,12 @@ const UserDetails = ({
|
|||
<Row className={classes.details}>
|
||||
<Paragraph noMargin align="right" className={classes.labels}>
|
||||
Client
|
||||
{' '}
|
||||
</Paragraph>
|
||||
<Spacer />
|
||||
<Img className={classes.logo} src={metamask} height={14} alt="Metamask client" />
|
||||
{provider === 'safe'
|
||||
? <Img className={classes.logo} src={safeIcon} height={14} alt="Safe client" />
|
||||
: <Img className={classes.logo} src={metamaskIcon} height={14} alt="Metamask client" />
|
||||
}
|
||||
<Paragraph noMargin align="right" weight="bolder" className={classes.labels}>
|
||||
{upperFirst(provider)}
|
||||
</Paragraph>
|
||||
|
@ -156,7 +158,6 @@ const UserDetails = ({
|
|||
<Row className={classes.details}>
|
||||
<Paragraph noMargin align="right" className={classes.labels}>
|
||||
Network
|
||||
{' '}
|
||||
</Paragraph>
|
||||
<Spacer />
|
||||
<Img className={classes.logo} src={dot} height={14} alt="Network" />
|
||||
|
|
|
@ -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<Props, State> {
|
|||
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<Props, State> {
|
|||
)
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -9,7 +9,6 @@ import saveTokens from './saveTokens'
|
|||
const loadActiveTokens = () => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
try {
|
||||
const tokens: Map<string, TokenProps> = await getActiveTokens()
|
||||
|
||||
const tokenRecordsList: List<Token> = List(
|
||||
Object.values(tokens).map((token: TokenProps): Token => makeToken(token)),
|
||||
)
|
||||
|
|
|
@ -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<string | null> => {
|
||||
|
@ -57,7 +69,14 @@ const getNetworkIdFrom = async (web3Provider) => {
|
|||
}
|
||||
|
||||
export const getProviderInfo: Function = async (): Promise<ProviderProps> => {
|
||||
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<ProviderProps> => {
|
|||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -25,13 +25,9 @@ export const addSafe = createAction<string, Function, ActionReturn>(
|
|||
}),
|
||||
)
|
||||
|
||||
const saveSafe = (
|
||||
name: string,
|
||||
address: string,
|
||||
threshold: number,
|
||||
ownersName: string[],
|
||||
ownersAddress: string[],
|
||||
) => (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
const saveSafe = (name: string, address: string, threshold: number, ownersName: string[], ownersAddress: string[]) => (
|
||||
dispatch: ReduxDispatch<GlobalState>,
|
||||
) => {
|
||||
const owners: List<Owner> = buildOwnersFrom(ownersName, ownersAddress)
|
||||
|
||||
const safe: Safe = SafeRecord({
|
||||
|
|
|
@ -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<Token>) => 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
|
||||
|
|
|
@ -9,15 +9,19 @@ const FrameDecorator = story => <div className={styles.frame}>{story()}</div>
|
|||
|
||||
storiesOf('Routes /welcome', module)
|
||||
.addDecorator(FrameDecorator)
|
||||
.add('Welcome with Gnosis Safe connected', () => {
|
||||
const provider = select('Status by Provider', ['', 'UNKNOWN', 'SAFE', 'METAMASK', 'PARITY'], 'SAFE')
|
||||
return <Component provider={provider} fetchProvider={() => {}} />
|
||||
})
|
||||
.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 <Component provider={provider} fetchProvider={() => {}} />
|
||||
})
|
||||
.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 <Component provider={provider} fetchProvider={() => {}} />
|
||||
})
|
||||
.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 <Component provider={provider} fetchProvider={() => {}} />
|
||||
})
|
||||
|
|
|
@ -30,7 +30,6 @@ afterAll(() => {
|
|||
console.error = originalError
|
||||
})
|
||||
|
||||
|
||||
const renderLoadSafe = async (localStore: Store<GlobalState>) => {
|
||||
const provider = await getProviderInfo()
|
||||
const walletRecord = makeProvider(provider)
|
||||
|
|
Loading…
Reference in New Issue