This action requires two transactions to be signed in{' '}
- {getProviderString('your Ethereum wallet', walletProviderId)}.{' '}
+ {getProviderString('your wallet', walletProviderId)}.{' '}
{approveTransactionMessage}
Please confirm them one after another.
@@ -304,8 +304,8 @@ class ActionPathsContent extends React.Component {
`}
>
In some situations,{' '}
- {getProviderString('your Ethereum wallet', walletProviderId)} may
- warn you that the second transaction will fail.{' '}
+ {getProviderString('your wallet', walletProviderId)} may warn you
+ that the second transaction will fail.{' '}
Please ignore this warning
.
diff --git a/src/components/SignerPanel/SignMsgContent.js b/src/components/SignerPanel/SignMsgContent.js
index 157f1438c..2dcd8c317 100644
--- a/src/components/SignerPanel/SignMsgContent.js
+++ b/src/components/SignerPanel/SignMsgContent.js
@@ -6,7 +6,7 @@ import ToggleContent from './ToggleContent'
import LocalIdentityBadge from '../IdentityBadge/LocalIdentityBadge'
import AppInstanceLabel from '../AppInstanceLabel'
import { AppType, EthereumAddressType } from '../../prop-types'
-import { isHumanReadable } from '../../utils'
+import { isHumanReadable } from '../../util/utils'
const SignMsgContent = ({ apps, account, intent, onSign, signingEnabled }) => {
const { message, requestingApp: requestingAppAddress } = intent
diff --git a/src/components/SignerPanel/SignerPanel.js b/src/components/SignerPanel/SignerPanel.js
index 5ee771198..d6a71f62f 100644
--- a/src/components/SignerPanel/SignerPanel.js
+++ b/src/components/SignerPanel/SignerPanel.js
@@ -3,11 +3,10 @@ import PropTypes from 'prop-types'
import styled from 'styled-components'
import { SidePanel, GU, springs } from '@aragon/ui'
import { Transition, animated } from 'react-spring'
-import { network } from '../../environment'
-import { useWallet } from '../../wallet'
+import { useWallet } from '../../contexts/wallet'
import { ActivityContext } from '../../contexts/ActivityContext'
import { AppType, EthereumAddressType } from '../../prop-types'
-import { addressesEqual } from '../../web3-utils'
+import { addressesEqual, getPriorityFeeEstimation } from '../../util/web3'
import ConfirmTransaction from './ConfirmTransaction'
import ConfirmMsgSign from './ConfirmMsgSign'
import SigningStatus from './SigningStatus'
@@ -45,6 +44,9 @@ const WEB3_TX_OBJECT_KEYS = new Set([
'gasPrice',
'data',
'nonce',
+ 'maxPriorityFeePerGas',
+ 'gasPrice',
+ 'maxFeePerGas',
])
const getAppName = (apps, proxyAddress) => {
@@ -73,7 +75,6 @@ class SignerPanel extends React.PureComponent {
setActivityNonce: PropTypes.func.isRequired,
transactionBag: PropTypes.object,
signatureBag: PropTypes.object,
- walletNetwork: PropTypes.string.isRequired,
walletWeb3: PropTypes.object,
web3: PropTypes.object.isRequired,
walletProviderId: PropTypes.string.isRequired,
@@ -239,9 +240,13 @@ class SignerPanel extends React.PureComponent {
try {
if (pretransaction) {
+ pretransaction = await this.applyGasAndPriorityEstimation(
+ pretransaction
+ )
await this.signTransaction(pretransaction, intent, true)
}
+ transaction = await this.applyGasAndPriorityEstimation(transaction)
const transactionHash = await this.signTransaction(
transaction,
intent,
@@ -310,14 +315,18 @@ class SignerPanel extends React.PureComponent {
}
}
+ // adds maxPriorityFeePerGas, gasPrice and maxFeePerGas to the transaction if the RPC supports these
+ applyGasAndPriorityEstimation = async transaction => {
+ const { walletWeb3 } = this.props
+ const estimatedPriorityFee = await getPriorityFeeEstimation(walletWeb3)
+ return {
+ ...transaction,
+ maxPriorityFeePerGas: estimatedPriorityFee,
+ }
+ }
+
render() {
- const {
- account,
- apps,
- walletNetwork,
- walletProviderId,
- walletWeb3,
- } = this.props
+ const { account, apps, walletProviderId, walletWeb3 } = this.props
const {
actionPaths,
@@ -361,11 +370,8 @@ class SignerPanel extends React.PureComponent {
{isTransaction ? (
@@ -461,7 +467,6 @@ export default function SignerPanelWrapper(props) {
setActivityConfirmed={setActivityConfirmed}
setActivityFailed={setActivityFailed}
setActivityNonce={setActivityNonce}
- walletNetwork={wallet.networkType}
walletProviderId={wallet.providerInfo.id}
walletWeb3={wallet.web3}
/>
diff --git a/src/components/SignerPanel/SigningStatus.js b/src/components/SignerPanel/SigningStatus.js
index 96da41bc6..d6e89b7b5 100644
--- a/src/components/SignerPanel/SigningStatus.js
+++ b/src/components/SignerPanel/SigningStatus.js
@@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { Box, GU, Info, textStyle, useTheme } from '@aragon/ui'
-import { getProviderString } from '../../ethereum-providers'
+import { getProviderString } from 'use-wallet'
import FeedbackIndicator from '../FeedbackIndicator/FeedbackIndicator'
import SignerButton from './SignerButton'
@@ -51,7 +51,7 @@ class SigningStatus extends React.Component {
return (
{`Open ${getProviderString(
- 'your Ethereum wallet',
+ 'your wallet',
walletProviderId
)} to sign your transaction.`}
@@ -60,7 +60,7 @@ class SigningStatus extends React.Component {
if (status === STATUS_MSG_SIGNING) {
return (
{`Open ${getProviderString(
- 'your Ethereum wallet',
+ 'your wallet',
walletProviderId
)} to sign your message.`}
)
@@ -102,7 +102,7 @@ class SigningStatus extends React.Component {
{cleanedErrorMessage ? (
Error: “{cleanedErrorMessage}”
) : (
- There may have been a problem with your Ethereum wallet.
+ There may have been a problem with your wallet.
)}
)
diff --git a/src/components/SignerPanel/ValidateWalletWeb3.js b/src/components/SignerPanel/ValidateWalletWeb3.js
index 646aab618..d309d920d 100644
--- a/src/components/SignerPanel/ValidateWalletWeb3.js
+++ b/src/components/SignerPanel/ValidateWalletWeb3.js
@@ -1,16 +1,13 @@
import React from 'react'
import PropTypes from 'prop-types'
-import { useWallet } from '../../wallet'
-import { NoWeb3Provider, AccountLocked, WrongNetwork } from './Web3Errors'
+import { useWallet } from '../../contexts/wallet'
+import { NoWeb3Provider, AccountLocked } from './Web3Errors'
function ValidateWalletWeb3({
children,
hasWeb3,
intent,
- isTransaction,
- networkType,
onClose,
- walletNetworkType,
walletProviderId,
}) {
const wallet = useWallet()
@@ -29,17 +26,6 @@ function ValidateWalletWeb3({
)
}
- if (isTransaction && walletNetworkType !== networkType) {
- return (
-
- )
- }
-
return children
}
@@ -47,10 +33,7 @@ ValidateWalletWeb3.propTypes = {
children: PropTypes.node.isRequired,
hasWeb3: PropTypes.bool.isRequired,
intent: PropTypes.object.isRequired,
- isTransaction: PropTypes.bool.isRequired,
- networkType: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
- walletNetworkType: PropTypes.string.isRequired,
walletProviderId: PropTypes.string.isRequired,
}
diff --git a/src/components/SignerPanel/Web3Errors.js b/src/components/SignerPanel/Web3Errors.js
index d68ca01e5..b7b2493c8 100644
--- a/src/components/SignerPanel/Web3Errors.js
+++ b/src/components/SignerPanel/Web3Errors.js
@@ -4,8 +4,8 @@ import { Info, Link, GU } from '@aragon/ui'
import AddressLink from './AddressLink'
import SignerButton from './SignerButton'
-import { getProviderString } from '../../ethereum-providers'
-import { isElectron } from '../../utils'
+import { getProviderString } from 'use-wallet'
+import { isElectron } from '../../util/utils'
function Web3ProviderError({
intent: { description, name, to },
@@ -49,7 +49,7 @@ export function NoWeb3Provider({ intent, onClose }) {
const onElectron = isElectron()
const neededText = onElectron
? 'You need to have Frame installed and enabled'
- : 'You need to have an Ethereum wallet installed and enabled'
+ : 'You need to have a wallet installed and enabled'
const actionText = (
@@ -80,10 +80,7 @@ NoWeb3Provider.propTypes = {
}
export function AccountLocked({ intent, onClose, walletProviderId }) {
- const providerMessage = getProviderString(
- 'your Ethereum wallet',
- walletProviderId
- )
+ const providerMessage = getProviderString('your wallet', walletProviderId)
return (
- new StoredList(`activity:${network.type}:${daoDomain}:${account}`, {
- preStringify: activity => ({
- ...activity,
- status: activity.status.description.replace('ACTIVITY_STATUS_', ''),
- type: activity.type.description.replace('ACTIVITY_TYPE_', ''),
- }),
- postParse: activity => ({
- ...activity,
- status: SymbolsByName.get(`ACTIVITY_STATUS_${activity.status}`),
- type: SymbolsByName.get(`ACTIVITY_TYPE_${activity.type}`),
- }),
- })
+const getStoredList = (networkType, daoDomain, account) =>
+ new StoredList(
+ getLocalStorageKey(`activity:${daoDomain}:${account}`, networkType),
+ {
+ preStringify: activity => ({
+ ...activity,
+ status: activity.status.description.replace('ACTIVITY_STATUS_', ''),
+ type: activity.type.description.replace('ACTIVITY_TYPE_', ''),
+ }),
+ postParse: activity => ({
+ ...activity,
+ status: SymbolsByName.get(`ACTIVITY_STATUS_${activity.status}`),
+ type: SymbolsByName.get(`ACTIVITY_TYPE_${activity.type}`),
+ }),
+ }
+ )
// Provides easy access to the user activities list
class ActivityProviderBase extends React.Component {
@@ -48,6 +51,7 @@ class ActivityProviderBase extends React.Component {
children: PropTypes.node,
daoDomain: PropTypes.string, // domain of current DAO
web3: PropTypes.object,
+ networkType: PropTypes.string.isRequired,
}
static defaultProps = {
account: '',
@@ -66,15 +70,19 @@ class ActivityProviderBase extends React.Component {
}
componentDidUpdate(prevProps) {
- const { daoDomain, account } = this.props
- if (daoDomain !== prevProps.daoDomain || account !== prevProps.account) {
+ const { daoDomain, account, networkType } = this.props
+ if (
+ daoDomain !== prevProps.daoDomain ||
+ account !== prevProps.account ||
+ networkType !== prevProps.networkType
+ ) {
this.updateStoredList()
}
}
updateStoredList() {
- const { daoDomain, account } = this.props
- this._storedList = getStoredList(daoDomain, account)
+ const { daoDomain, account, networkType } = this.props
+ this._storedList = getStoredList(networkType, daoDomain, account)
this.setState(
{ activities: this._storedList.getItems() },
this.refreshPendingActivities
@@ -257,10 +265,15 @@ class ActivityProviderBase extends React.Component {
}
function ActivityProvider(props) {
- const { account } = useWallet()
- return
+ const { account, networkType } = useWallet()
+ return (
+
+ )
}
-ActivityProvider.propTypes = ActivityProviderBase.propTypes
const ActivityConsumer = ActivityContext.Consumer
diff --git a/src/contexts/ClientWeb3Context.js b/src/contexts/ClientWeb3Context.js
new file mode 100644
index 000000000..4be39ca9a
--- /dev/null
+++ b/src/contexts/ClientWeb3Context.js
@@ -0,0 +1,24 @@
+import React, { useContext, useMemo } from 'react'
+import { useWallet } from '../contexts/wallet'
+import { web3Provider } from '../Web3Provider'
+
+const ClientWeb3Context = React.createContext()
+
+function ClientWeb3Provider(props) {
+ const { networkType } = useWallet()
+
+ const contextValue = useMemo(
+ () => ({
+ web3: web3Provider.getProvider(networkType),
+ }),
+ [networkType]
+ )
+
+ return
+}
+
+function useClientWeb3() {
+ return useContext(ClientWeb3Context)
+}
+
+export { ClientWeb3Provider, useClientWeb3 }
diff --git a/src/contexts/FavoriteDaosContext.js b/src/contexts/FavoriteDaosContext.js
index 4755d8368..2bcfd6246 100644
--- a/src/contexts/FavoriteDaosContext.js
+++ b/src/contexts/FavoriteDaosContext.js
@@ -1,14 +1,20 @@
-import React, { useContext } from 'react'
+import React, {
+ useContext,
+ useCallback,
+ useMemo,
+ useState,
+ useEffect,
+} from 'react'
import uniqby from 'lodash.uniqby'
import PropTypes from 'prop-types'
-import { network } from '../environment'
import StoredList from '../StoredList'
-import { addressesEqual } from '../web3-utils'
+import { addressesEqual } from '../util/web3'
+import { useWallet } from '../contexts/wallet'
+import { getLocalStorageKey } from '../util/utils'
+import { trackEvent, events } from '../analytics'
const FavoriteDaosContext = React.createContext()
-const storedList = new StoredList(`favorite-daos:${network.type}`)
-
const filterFavoritesDaos = daos =>
uniqby(
daos
@@ -20,81 +26,96 @@ const filterFavoritesDaos = daos =>
dao => dao.address.toLowerCase()
)
-class FavoriteDaosProvider extends React.Component {
- static propTypes = {
- children: PropTypes.node,
- }
-
- state = {
- favoriteDaos: filterFavoritesDaos(storedList.loadItems()),
- }
-
- add = dao => {
- this.setState({
- favoriteDaos: storedList.add(dao),
- })
- }
-
- remove = index => {
- this.setState({
- favoriteDaos: storedList.remove(index),
- })
- }
-
- isAddressFavorited = address => {
- return (
- this.state.favoriteDaos.findIndex(dao =>
- addressesEqual(dao.address, address)
- ) > -1
- )
- }
-
- addFavorite = dao => {
- const daoIndex = this.state.favoriteDaos.findIndex(({ address }) =>
- addressesEqual(address, dao.address)
- )
- if (daoIndex === -1) {
- this.setState({
- favoriteDaos: storedList.add({ name: dao.name, address: dao.address }),
+function FavoriteDaosProvider({ children }) {
+ const { networkType } = useWallet()
+ const [favoriteDaos, setFavoriteDaos] = useState([])
+
+ const storedList = useMemo(() => {
+ return new StoredList(getLocalStorageKey(`favorite-daos`, networkType))
+ }, [networkType])
+
+ useEffect(() => {
+ let cancel = false
+
+ if (!cancel) {
+ setFavoriteDaos(() => {
+ const favs = storedList.loadItems()
+ return filterFavoritesDaos(favs)
})
}
- }
-
- removeFavoriteByAddress = address => {
- const daoIndex = this.state.favoriteDaos.findIndex(({ address }) =>
- addressesEqual(address, address)
- )
- if (daoIndex > -1) {
- const favs = storedList.remove(daoIndex)
- this.setState({
- favoriteDaos: favs,
- })
+
+ return () => {
+ cancel = true
}
- }
-
- updateFavoriteDaos = favoriteDaos => {
- this.setState({
- favoriteDaos: storedList.update(favoriteDaos),
- })
- }
-
- render() {
- const { children } = this.props
- const { favoriteDaos } = this.state
- return (
-
- {children}
-
- )
- }
+ }, [networkType, storedList])
+
+ const isAddressFavorited = useCallback(
+ address => {
+ return (
+ favoriteDaos.findIndex(dao => addressesEqual(dao.address, address)) > -1
+ )
+ },
+ [favoriteDaos]
+ )
+
+ const addFavorite = useCallback(
+ dao => {
+ const daoIndex = favoriteDaos.findIndex(({ address }) =>
+ addressesEqual(address, dao.address)
+ )
+ if (daoIndex === -1) {
+ setFavoriteDaos(
+ storedList.add({ name: dao.name, address: dao.address })
+ )
+
+ // analytics
+ favoriteToggleEvent(dao.name || dao.address, true, networkType)
+ }
+ },
+ [favoriteDaos, setFavoriteDaos, storedList, networkType]
+ )
+
+ const removeFavorite = useCallback(
+ dao => {
+ const daoIndex = favoriteDaos.findIndex(({ address }) =>
+ addressesEqual(dao.address, address)
+ )
+ if (daoIndex > -1) {
+ setFavoriteDaos(() => {
+ return storedList.remove(daoIndex)
+ })
+
+ // analytics
+ favoriteToggleEvent(dao.name || dao.address, false, networkType)
+ }
+ },
+ [favoriteDaos, setFavoriteDaos, storedList, networkType]
+ )
+
+ const updateFavoriteDaos = useCallback(
+ daos => {
+ setFavoriteDaos(storedList.update(daos))
+ },
+ [setFavoriteDaos, storedList]
+ )
+
+ return (
+
+ {children}
+
+ )
+}
+
+FavoriteDaosProvider.propTypes = {
+ children: PropTypes.node,
}
function useFavoriteDaos() {
@@ -103,4 +124,18 @@ function useFavoriteDaos() {
const FavoriteDaosConsumer = FavoriteDaosContext.Consumer
-export { FavoriteDaosProvider, FavoriteDaosConsumer, useFavoriteDaos }
+const favoriteToggleEvent = (daoIdentifier, toggle, networkType) => {
+ // analytics
+ trackEvent(events.FAVORITE_ORGANIZATION_TOGGLED, {
+ network: networkType,
+ dao_identifier: daoIdentifier,
+ favorited: toggle,
+ })
+}
+
+export {
+ FavoriteDaosProvider,
+ FavoriteDaosConsumer,
+ useFavoriteDaos,
+ favoriteToggleEvent,
+}
diff --git a/src/contexts/PermissionsContext.js b/src/contexts/PermissionsContext.js
index a47911dcb..f18001a48 100644
--- a/src/contexts/PermissionsContext.js
+++ b/src/contexts/PermissionsContext.js
@@ -7,8 +7,8 @@ import {
roleResolver,
permissionsByRole,
} from '../permissions'
-import { log, noop } from '../utils'
-import { addressesEqual, getEmptyAddress } from '../web3-utils'
+import { log, noop } from '../util/utils'
+import { addressesEqual, getEmptyAddress } from '../util/web3'
const PermissionsContext = React.createContext()
diff --git a/src/contexts/elasticAPM.js b/src/contexts/elasticAPM.js
new file mode 100644
index 000000000..6cffc530f
--- /dev/null
+++ b/src/contexts/elasticAPM.js
@@ -0,0 +1,83 @@
+import React, { useMemo, useContext, useState } from 'react'
+import PropTypes from 'prop-types'
+import { init as initApm } from '@elastic/apm-rum'
+import { afterFrame } from '@elastic/apm-rum-core'
+
+const UseAPMContext = React.createContext()
+
+function APMProvider({ children }) {
+ const [apm, setApm] = useState(() => {
+ if (
+ process.env.REACT_APP_DEPLOY_VERSION &&
+ process.env.REACT_APP_DEPLOY_ENVIRONMENT
+ ) {
+ return initApm({
+ serviceName: 'client',
+ serverUrl: 'https://apm-monitoring.aragon.org',
+ serviceVersion: process.env.REACT_APP_DEPLOY_VERSION,
+ environment: process.env.REACT_APP_DEPLOY_ENVIRONMENT,
+ })
+ } else {
+ console.warn(
+ 'REACT_APP_DEPLOY_VERSION or REACT_APP_DEPLOY_ENVIRONMENT is not provided.'
+ )
+ return null
+ }
+ })
+
+ const contextValue = useMemo(() => {
+ return { apm, setApm }
+ }, [apm, setApm])
+
+ return (
+
+ {children}
+
+ )
+}
+
+APMProvider.propTypes = {
+ children: PropTypes.node,
+}
+
+function useAPM() {
+ return useContext(UseAPMContext)
+}
+
+function updateAPMContext(apm, networkType) {
+ if (apm && networkType) {
+ const context = { networkType: networkType }
+ apm.addLabels(context)
+ apm.setCustomContext(context)
+ }
+}
+
+updateAPMContext.propTypes = {
+ apm: PropTypes.any,
+ networkType: PropTypes.string,
+}
+
+function instrumentAPMRouts(apm, routingMod) {
+ if (apm && routingMod) {
+ const { instanceId, instancePath, name, status } = routingMod
+ const path = status
+ ? `${name}/${status}`
+ : `${name}/${instanceId}${instancePath}`
+
+ const tx = apm.startTransaction(path, 'route-change', {
+ managed: false,
+ canReuse: false,
+ })
+
+ afterFrame(() => {
+ tx && tx.detectFinish()
+ })
+ }
+}
+
+instrumentAPMRouts.propTypes = {
+ apm: PropTypes.any,
+ routing: PropTypes.any,
+}
+
+export { useAPM, APMProvider, updateAPMContext, instrumentAPMRouts }
diff --git a/src/contexts/wallet.js b/src/contexts/wallet.js
new file mode 100644
index 000000000..ecb41972e
--- /dev/null
+++ b/src/contexts/wallet.js
@@ -0,0 +1,147 @@
+import React, {
+ useContext,
+ useEffect,
+ useMemo,
+ useCallback,
+ useState,
+} from 'react'
+import PropTypes from 'prop-types'
+import BN from 'bn.js'
+import {
+ useWallet as useWalletBase,
+ UseWalletProvider,
+ ChainUnsupportedError,
+ chains,
+} from 'use-wallet'
+import { getWeb3, filterBalanceValue } from '../util/web3'
+import { useWalletConnectors } from '../ethereum-providers/connectors'
+import { useAPM, updateAPMContext } from './elasticAPM'
+import { LocalStorageWrapper } from '../local-storage-wrapper'
+
+export const WALLET_STATUS = Object.freeze({
+ providers: 'providers',
+ connecting: 'connecting',
+ connected: 'connected',
+ disconnected: 'disconnected',
+ error: 'error',
+})
+
+// default network is mainnet if user is not connected
+const NETWORK_TYPE_DEFAULT = chains.getChainInformation(1)?.type
+
+const WalletContext = React.createContext()
+
+function WalletContextProvider({ children }) {
+ const {
+ account,
+ balance,
+ ethereum,
+ connector,
+ status,
+ chainId,
+ providerInfo,
+ type,
+ networkName,
+ ...walletBaseRest
+ } = useWalletBase()
+
+ const initialNetwork = useMemo(() => {
+ const lastNetwork = LocalStorageWrapper.get('last-network', false)
+ if (!lastNetwork) return NETWORK_TYPE_DEFAULT
+ return lastNetwork
+ }, [])
+
+ const [walletWeb3, setWalletWeb3] = useState(null)
+ const [disconnectedNetworkType, setDisconnectedNetworkType] = useState(
+ initialNetwork
+ )
+
+ const connected = useMemo(() => status === 'connected', [status])
+ const networkType = useMemo(() => {
+ const newNetwork = connected ? networkName : disconnectedNetworkType
+ LocalStorageWrapper.set('last-network', newNetwork, false)
+ return newNetwork
+ }, [connected, networkName, disconnectedNetworkType])
+
+ const changeNetworkTypeDisconnected = useCallback(
+ newNetworkType => {
+ if (status === 'disconnected') {
+ setDisconnectedNetworkType(newNetworkType)
+ }
+ },
+ [status]
+ )
+
+ // get web3 and set local storage prefix whenever networkType changes
+ useEffect(() => {
+ let cancel = false
+
+ if (!ethereum) {
+ return
+ }
+
+ const walletWeb3 = getWeb3(ethereum)
+ if (!cancel) {
+ setWalletWeb3(walletWeb3)
+ }
+
+ return () => {
+ cancel = true
+ setWalletWeb3(null)
+ }
+ }, [ethereum, networkType])
+
+ const wallet = useMemo(
+ () => ({
+ account,
+ balance: new BN(filterBalanceValue(balance)),
+ ethereum,
+ networkType,
+ providerInfo: providerInfo,
+ web3: walletWeb3,
+ status,
+ chainId: connected ? chainId : 1, // connect to mainnet if wallet is not connected
+ connected,
+ changeNetworkTypeDisconnected,
+ ...walletBaseRest,
+ }),
+ [
+ account,
+ balance,
+ ethereum,
+ networkType,
+ providerInfo,
+ status,
+ chainId,
+ walletBaseRest,
+ walletWeb3,
+ connected,
+ changeNetworkTypeDisconnected,
+ ]
+ )
+
+ const { apm } = useAPM()
+ useEffect(() => {
+ updateAPMContext(apm, wallet.networkType)
+ }, [apm, wallet.networkType])
+
+ return (
+ {children}
+ )
+}
+WalletContextProvider.propTypes = { children: PropTypes.node }
+
+export function WalletProvider({ children }) {
+ return (
+
+ {children}
+
+ )
+}
+WalletProvider.propTypes = { children: PropTypes.node }
+
+export function useWallet() {
+ return useContext(WalletContext)
+}
+
+export { ChainUnsupportedError }
diff --git a/src/environment.js b/src/environment.js
index af4fe5a13..d3da9a5eb 100644
--- a/src/environment.js
+++ b/src/environment.js
@@ -1,18 +1,10 @@
-import Web3 from 'web3'
import appIds from './known-app-ids'
import { parseAppLocator } from './app-locator'
-import {
- getAppLocator,
- getDefaultEthNode,
- getEthNetworkType,
- getIpfsGateway,
-} from './local-settings'
+import { getAppLocator, getDefaultEthNode } from './local-settings'
import { getNetworkConfig } from './network-config'
const appsOrder = ['TokenManager', 'Voting', 'Finance', 'Agent']
-const networkType = getEthNetworkType()
-
// Utility to sort a pair of apps (to be used with Array.prototype.sort)
export const sortAppsPair = (app1, app2) => {
const pairs = Object.entries(appIds)
@@ -65,39 +57,13 @@ export const appOverrides = {
[appIds['TokenManager']]: { name: 'Tokens' },
}
-export const appLocator = parseAppLocator(getAppLocator())
-
-export const ipfsDefaultConf = {
- gateway: getIpfsGateway(),
-}
-
-const networkConfig = getNetworkConfig(networkType)
-export const network = networkConfig.settings
-export const providers = networkConfig.providers
-export const connectGraphEndpoint = networkConfig.connectGraphEndpoint
-export const enableMigrateBanner = networkConfig.enableMigrateBanner || false
-
-export const contractAddresses = {
- ensRegistry: networkConfig.addresses.ensRegistry,
+export function getParsedAppLocator(networkType) {
+ return parseAppLocator(getAppLocator(networkType))
}
-if (process.env.NODE_ENV !== 'production') {
- if (Object.values(contractAddresses).some(address => !address)) {
- // Warn if any contracts are not given addresses in development
- console.error(
- 'Some contracts are missing addresses in your environment! You most likely need to specify them as environment variables.'
- )
- console.error('Current contract address configuration', contractAddresses)
- }
- if (network.type === 'unknown') {
- console.error(
- 'This app was configured to connect to an unsupported network. You most likely need to change your network environment variables.'
- )
- }
-}
-
-export const defaultEthNode =
- getDefaultEthNode() || networkConfig.nodes.defaultEth
-export const web3Providers = {
- default: new Web3.providers.WebsocketProvider(defaultEthNode),
+export const getEthNode = networkType => {
+ return (
+ getDefaultEthNode(networkType) ||
+ getNetworkConfig(networkType).nodes.defaultEth
+ )
}
diff --git a/src/errors.js b/src/errors.js
index 2eb97cbac..e2f5f62ed 100644
--- a/src/errors.js
+++ b/src/errors.js
@@ -9,6 +9,9 @@ export const extendError = (name, { defaultMessage }) =>
export const InvalidAddress = extendError('InvalidAddress', {
defaultMessage: 'The address is invalid',
})
+export const RequiredField = extendError('RequiredField', {
+ defaultMessage: 'This field is required.',
+})
export const InvalidNetworkType = extendError('InvalidNetworkType', {
defaultMessage: 'The network type is invalid',
})
diff --git a/src/ethereum-providers/connectors.js b/src/ethereum-providers/connectors.js
new file mode 100644
index 000000000..eea938f7f
--- /dev/null
+++ b/src/ethereum-providers/connectors.js
@@ -0,0 +1,56 @@
+import { getPortisDappId, getFortmaticApiKey } from '../local-settings'
+
+const FORMATIC_KEY = getFortmaticApiKey()
+const PORTIS_ID = getPortisDappId()
+
+export const connectors = [
+ {
+ id: 'injected',
+ properties: {
+ chainId: [
+ 1,
+ 5,
+ 10,
+ 11155420,
+ 137,
+ 80001,
+ 1666600000,
+ 1666700000,
+ 97,
+ 56,
+ 588,
+ 1088,
+ ], // add here to handle more injected chains
+ },
+ },
+ {
+ id: 'frame',
+ properties: {
+ chainId: 1,
+ },
+ },
+ FORMATIC_KEY
+ ? {
+ id: 'fortmatic',
+ properties: {
+ chainId: 1,
+ apiKey: FORMATIC_KEY,
+ },
+ }
+ : null,
+ PORTIS_ID
+ ? {
+ id: 'portis',
+ properties: {
+ dAppId: PORTIS_ID,
+ chainId: [1],
+ },
+ }
+ : null,
+].filter(p => p)
+
+// the final data that we pass to use-wallet package.
+export const useWalletConnectors = connectors.reduce((current, connector) => {
+ current[connector.id] = connector.properties || {}
+ return current
+}, {})
diff --git a/src/ethereum-providers/index.js b/src/ethereum-providers/index.js
deleted file mode 100644
index 440839c3f..000000000
--- a/src/ethereum-providers/index.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import { isElectron } from '../utils'
-
-import frame from './icons/Frame.png'
-import cipher from './icons/Cipher.png'
-import metamask from './icons/Metamask.png'
-import status from './icons/Status.png'
-import wallet from './icons/wallet.svg'
-import fortmatic from './icons/Fortmatic.svg'
-import portis from './icons/Portis.svg'
-
-// See the corresponding prop type, EthereumProviderType, in prop-types.js.
-const PROVIDERS = new Map(
- [
- {
- id: 'frame',
- name: 'Frame',
- type: 'Desktop',
- image: frame,
- strings: {
- 'your Ethereum wallet': 'Frame',
- },
- },
- {
- id: 'metamask',
- name: 'Metamask',
- type: 'Desktop',
- image: metamask,
- strings: {
- 'your Ethereum wallet': 'Metamask',
- },
- },
- {
- id: 'status',
- name: 'Status',
- type: 'Mobile',
- image: status,
- strings: {
- 'your Ethereum wallet': 'Status',
- },
- },
- {
- id: 'cipher',
- name: 'Cipher',
- type: 'Mobile',
- image: cipher,
- strings: {
- 'your Ethereum wallet': 'Cipher',
- },
- },
- {
- id: 'fortmatic',
- name: 'Fortmatic',
- type: 'Any',
- image: fortmatic,
- strings: {
- 'your Ethereum wallet': 'Fortmatic',
- },
- },
- {
- id: 'portis',
- name: 'Portis',
- type: 'Any',
- image: portis,
- strings: {
- 'your Ethereum wallet': 'Portis',
- },
- },
- {
- id: 'unknown',
- name: 'Unknown',
- type: 'Desktop',
- image: wallet,
- strings: {
- 'your Ethereum wallet': 'your wallet',
- },
- },
- ].map(provider => [provider.id, provider])
-)
-
-// Get a providers object for a given ID.
-function getProvider(providerId) {
- return PROVIDERS.get(providerId)
-}
-
-// Get a string that depends on the current Ethereum provider.
-// The default string is used as an identifier (à la gettext).
-function getProviderString(string, providerId = 'unknown') {
- const provider = getProvider(providerId)
- return (provider && provider.strings[string]) || string
-}
-
-// Get an identifier for the provider, if it can be detected.
-function identifyProvider(provider) {
- if (provider && isElectron()) {
- return 'frame'
- }
- if (provider && provider.isMetaMask) {
- return 'metamask'
- }
- return 'unknown'
-}
-
-// Get a provider from its useWallet() identifier.
-function getProviderFromUseWalletId(id) {
- if (id === 'provided') {
- return (
- getProvider(identifyProvider(window.ethereum)) || getProvider('unknown')
- )
- }
- return getProvider(id) || getProvider('unknown')
-}
-
-export {
- getProvider,
- identifyProvider,
- getProviderString,
- getProviderFromUseWalletId,
-}
-export default PROVIDERS
diff --git a/src/hooks.js b/src/hooks.js
index b432bb04c..438515987 100644
--- a/src/hooks.js
+++ b/src/hooks.js
@@ -12,8 +12,8 @@ import {
identityEventTypes,
} from './components/IdentityManager/IdentityManager'
import keycodes from './keycodes'
-import { log, removeStartingSlash } from './utils'
-import { addressesEqual } from './web3-utils'
+import { log, removeStartingSlash } from './util/utils'
+import { addressesEqual } from './util/web3'
// Update `now` at a given interval.
export function useNow(updateEvery = 1000) {
diff --git a/src/hooks/useDetectDao.js b/src/hooks/useDetectDao.js
new file mode 100644
index 000000000..495af72f3
--- /dev/null
+++ b/src/hooks/useDetectDao.js
@@ -0,0 +1,94 @@
+import { useState, useEffect, useMemo } from 'react'
+import { isEnsDomainAvailable } from '../aragonjs-wrapper'
+import { completeDomain } from '../check-domain'
+import { getActiveNetworks } from '../util/network'
+import { getWeb3Provider } from '../util/web3'
+
+/**
+ * This hook checks a list of networks for an ens domain. It returns all
+ * network names on which the domain is found.
+ *
+ * @param {string} domain domain to check
+ * @returns {{loading, networks}} object containing a loading state and a list of networks on which
+ * the domain was found
+ */
+export function useDetectDao(domain) {
+ const [networks, setNetworks] = useState([])
+ const [loading, setLoading] = useState(true)
+
+ useEffect(() => {
+ setLoading(true)
+
+ let cancelled = false
+
+ const promiseTimeout = function(ms, promise) {
+ // Create a promise that rejects in milliseconds
+ const timeout = new Promise((resolve, reject) => {
+ const id = setTimeout(() => {
+ clearTimeout(id)
+ // Ignore for prefer-promise-reject-errors
+ // eslint-disable-next-line
+ reject({reason: 'timed out'})
+ }, ms)
+ })
+
+ // Returns a race between our timeout and the passed in promise
+ return Promise.race([promise, timeout])
+ }
+
+ const checkWithProvider = async () => {
+ const networksToCheck = getActiveNetworks()
+ try {
+ const providers = networksToCheck.map(n => ({
+ network: n,
+ provider: getWeb3Provider(n),
+ }))
+ const availabilityPromise = providers.map(p => {
+ // Avoid bad web sockets to freeze the application looking forever
+ return promiseTimeout(
+ 3000,
+ isEnsDomainAvailable(p.network, p.provider, completeDomain(domain))
+ )
+ })
+
+ const availableNetworks = await Promise.allSettled(availabilityPromise)
+
+ // get only the ones promises that were fullfilled and ENS were not available (means DAO exists)
+ const daoExists = availableNetworks.map(a => !a.value && !a.reason)
+
+ const daoExistsOnNetworks = networksToCheck.filter(
+ (_, i) => daoExists[i]
+ )
+
+ // NOTE I can't seem to get the hook life cycle right, so I'm tearing
+ // the connections down immediately. [VR 22-09-2021]
+ providers.forEach(p => {
+ if (p.provider.disconnect) {
+ p.provider.disconnect()
+ } else {
+ // Older versions of web3's providers didn't expose a generic interface for disconnecting
+ p.provider.connection.close()
+ }
+ })
+
+ if (!cancelled) {
+ setLoading(false)
+ setNetworks(daoExistsOnNetworks)
+ }
+ } catch (err) {
+ console.error(err)
+ }
+ }
+
+ checkWithProvider()
+ return () => {
+ cancelled = true
+ }
+ }, [domain])
+
+ const data = useMemo(() => {
+ return { loading, networks }
+ }, [networks, loading])
+
+ return data
+}
diff --git a/src/index.html b/src/index.html
index 14d2141e2..336f91c88 100644
--- a/src/index.html
+++ b/src/index.html
@@ -26,22 +26,7 @@
You need to enable JavaScript to run this app.
-
-
-
-
-
+