diff --git a/packages/boba/gateway/src/actions/setupAction.js b/packages/boba/gateway/src/actions/setupAction.js
index 9c4a8885f9..cc21373918 100644
--- a/packages/boba/gateway/src/actions/setupAction.js
+++ b/packages/boba/gateway/src/actions/setupAction.js
@@ -81,3 +81,15 @@ export function setWalletConnected( state ) {
return dispatch({ type: 'SETUP/WALLET_CONNECTED', payload: state })
}
}
+
+export function setChainIdChanged() {
+ return function (dispatch) {
+ return dispatch({ type: 'SETUP/CHAINIDCHANGED/SET' })
+ }
+}
+
+export function resetChainIdChanged() {
+ return function (dispatch) {
+ return dispatch({ type: 'SETUP/CHAINIDCHANGED/RESET' })
+ }
+}
diff --git a/packages/boba/gateway/src/actions/tokenAction.js b/packages/boba/gateway/src/actions/tokenAction.js
index e3627aec26..4154462e34 100644
--- a/packages/boba/gateway/src/actions/tokenAction.js
+++ b/packages/boba/gateway/src/actions/tokenAction.js
@@ -225,3 +225,9 @@ export async function addToken ( tokenContractAddressL1 ) {
return {currency: _tokenContractAddressL1, L1address: _tokenContractAddressL1, L2address: '', symbol: 'Not found', error: 'Not found'};
}
}
+
+export function restTokenList () {
+ return function (dispatch) {
+ return dispatch({ type: 'TOKEN/GET/RESET' });
+ }
+}
diff --git a/packages/boba/gateway/src/components/disconnect/Disconnect.js b/packages/boba/gateway/src/components/disconnect/Disconnect.js
new file mode 100644
index 0000000000..d98b11b429
--- /dev/null
+++ b/packages/boba/gateway/src/components/disconnect/Disconnect.js
@@ -0,0 +1,57 @@
+/*
+Copyright 2021-present Boba Network.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License. */
+
+import React from 'react';
+import { useDispatch } from 'react-redux';
+import { LoginOutlined } from '@mui/icons-material';
+import { IconButton, Tooltip } from '@mui/material';
+
+import {
+ setLayer,
+ setConnect,
+ setConnectBOBA,
+ setConnectETH,
+ setEnableAccount,
+ setWalletConnected
+ } from 'actions/setupAction';
+
+import networkService from 'services/networkService';
+
+function Disconnect () {
+
+ const dispatch = useDispatch();
+
+ const disconnect = async () => {
+ await networkService.walletService.disconnectWallet()
+ dispatch(setLayer(null))
+ dispatch(setConnect(false))
+ dispatch(setConnectBOBA(false))
+ dispatch(setConnectETH(false))
+ dispatch(setWalletConnected(false))
+ dispatch(setEnableAccount(false))
+ }
+
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+}
+
+export default React.memo(Disconnect);
diff --git a/packages/boba/gateway/src/components/mainMenu/layerSwitcher/LayerSwitcher.js b/packages/boba/gateway/src/components/mainMenu/layerSwitcher/LayerSwitcher.js
index a2535ec68e..3dd506578a 100644
--- a/packages/boba/gateway/src/components/mainMenu/layerSwitcher/LayerSwitcher.js
+++ b/packages/boba/gateway/src/components/mainMenu/layerSwitcher/LayerSwitcher.js
@@ -36,6 +36,7 @@ import {
selectConnectBOBA,
selectConnect,
selectWalletConnected,
+ selectChainIdChanged,
} from 'selectors/setupSelector'
import {
@@ -53,7 +54,7 @@ import { setEnableAccount, setWalletAddress } from 'actions/setupAction'
import { fetchTransactions, fetchBalances } from 'actions/networkAction'
-import { openModal } from 'actions/uiAction'
+import { closeModal, openModal } from 'actions/uiAction'
import Button from 'components/button/Button.js'
import { L1_ICONS, L2_ICONS } from 'util/network/network.util.js'
import { LAYER } from 'util/constant.js'
@@ -76,6 +77,7 @@ function LayerSwitcher({ visisble = true, isButton = false }) {
const connectBOBARequest = useSelector(selectConnectBOBA())
const connectRequest = useSelector(selectConnect())
const walletConnected = useSelector(selectWalletConnected())
+ const chainIdChanged = useSelector(selectChainIdChanged())
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('md'))
@@ -84,15 +86,8 @@ function LayerSwitcher({ visisble = true, isButton = false }) {
? truncate(networkService.account, 6, 4, '...')
: ''
- const chainChangedFromMM = JSON.parse(
- localStorage.getItem('chainChangedFromMM')
- )
- const wantChain = JSON.parse(localStorage.getItem('wantChain'))
- const chainChangedInit = JSON.parse(localStorage.getItem('chainChangedInit'))
-
const dispatchBootAccount = useCallback(() => {
-
- if (!accountEnabled && baseEnabled) initializeAccount()
+ if ((!accountEnabled && baseEnabled) || chainIdChanged) initializeAccount()
async function initializeAccount() {
@@ -112,6 +107,7 @@ function LayerSwitcher({ visisble = true, isButton = false }) {
return false
}
else if (initialized === LAYER.L1 || initialized === LAYER.L2) {
+ dispatch(closeModal('wrongNetworkModal'))
dispatch(setLayer(initialized))
dispatch(setEnableAccount(true))
dispatch(setWalletAddress(networkService.account))
@@ -122,25 +118,35 @@ function LayerSwitcher({ visisble = true, isButton = false }) {
return false
}
}
- }, [dispatch, accountEnabled, network, networkType, baseEnabled])
+ }, [dispatch, accountEnabled, network, networkType, baseEnabled, chainIdChanged])
const doConnectToLayer = useCallback((layer) => {
async function doConnect() {
try {
- localStorage.setItem('wantChain', JSON.stringify(layer))
- if (networkService.provider) {
- await networkService.switchChain(layer)
+ if (networkService.walletService.provider) {
+ const response = await networkService.switchChain(layer)
+ if (response) {
+ if (layer === 'L1') {
+ dispatch(setConnectBOBA(false))
+ } else {
+ dispatch(setConnectETH(false))
+ }
+ dispatchBootAccount()
+ } else {
+ dispatch(setConnectETH(false))
+ dispatch(setConnectBOBA(false))
+ }
} else {
dispatch(openModal('walletSelectorModal'))
}
} catch (err) {
console.log('ERROR', err)
- dispatch(setConnectETH(false));
- dispatch(setConnectBOBA(false));
+ dispatch(setConnectETH(false))
+ dispatch(setConnectBOBA(false))
}
}
doConnect();
- }, [dispatch])
+ }, [dispatch, dispatchBootAccount])
useEffect(() => {
if (walletConnected) {
@@ -150,30 +156,10 @@ function LayerSwitcher({ visisble = true, isButton = false }) {
useEffect(() => {
// detect mismatch and correct the mismatch
- if (wantChain === 'L1' && layer === 'L2') {
- dispatchBootAccount()
- } else if (wantChain === 'L2' && layer === 'L1') {
- dispatchBootAccount()
- }
- }, [wantChain, layer, dispatchBootAccount])
-
- useEffect(() => {
- // auto reconnect to MM if we just switched chains from
- // with the chain switcher, and then unset the flag.
- if (chainChangedInit) {
- dispatchBootAccount()
- localStorage.setItem('chainChangedInit', false)
- }
- }, [chainChangedInit, dispatchBootAccount])
-
- useEffect(() => {
- // auto reconnect to MM if we just switched chains from
- // inside MM, and then unset the flag.
- if (chainChangedFromMM) {
+ if (layer === 'L1' || layer === 'L2') {
dispatchBootAccount()
- localStorage.setItem('chainChangedFromMM', false)
}
- }, [chainChangedFromMM, dispatchBootAccount])
+ }, [layer, dispatchBootAccount])
// listening for l1 connection request
useEffect(() => {
@@ -190,7 +176,7 @@ function LayerSwitcher({ visisble = true, isButton = false }) {
}, [ connectBOBARequest, doConnectToLayer ])
useEffect(() => {
- if (connectRequest) {
+ if (connectRequest && !networkService.walletService.provider) {
dispatch(openModal('walletSelectorModal'))
}
}, [dispatch, connectRequest])
diff --git a/packages/boba/gateway/src/components/modal/Modal.js b/packages/boba/gateway/src/components/modal/Modal.js
index b842fcbea8..29be52615d 100644
--- a/packages/boba/gateway/src/components/modal/Modal.js
+++ b/packages/boba/gateway/src/components/modal/Modal.js
@@ -61,7 +61,7 @@ function _Modal({
{
!!newStyle ?
-
+
diff --git a/packages/boba/gateway/src/components/pageHeader/PageHeader.js b/packages/boba/gateway/src/components/pageHeader/PageHeader.js
index c36227d05c..83ba1e7b0a 100644
--- a/packages/boba/gateway/src/components/pageHeader/PageHeader.js
+++ b/packages/boba/gateway/src/components/pageHeader/PageHeader.js
@@ -22,6 +22,7 @@ import CloseIcon from 'components/icons/CloseIcon'
import networkService from 'services/networkService'
import { makeStyles } from '@mui/styles'
import Copy from 'components/copy/Copy'
+import Disconnect from 'components/disconnect/Disconnect'
import { useSelector } from 'react-redux'
import {
selectAccountEnabled,
@@ -169,7 +170,10 @@ const PageHeader = ({ maintenance }) => {
{!!accountEnabled ? (
-
+ <>
+
+
+ >
) : null}
diff --git a/packages/boba/gateway/src/containers/modals/walletSelector/WalletSelectorModal.js b/packages/boba/gateway/src/containers/modals/walletSelector/WalletSelectorModal.js
index b158ee3264..deef7e55f8 100644
--- a/packages/boba/gateway/src/containers/modals/walletSelector/WalletSelectorModal.js
+++ b/packages/boba/gateway/src/containers/modals/walletSelector/WalletSelectorModal.js
@@ -21,12 +21,7 @@ function WalletSelectorModal ({ open }) {
const connectToWallet = async (type) => {
try {
- if (type === 'metamask') {
- await networkService.walletService.connectMetaMask()
- }
- if (type === 'walletconnect') {
- await networkService.walletService.connectWalletConnect()
- }
+ await networkService.walletService.connectWallet(type)
dispatch(closeModal('walletSelectorModal'))
dispatch(setWalletConnected(true))
} catch (error) {
@@ -43,21 +38,25 @@ function WalletSelectorModal ({ open }) {
}
return (
-
+
-
- Connect to Wallet
-
connectToWallet('metamask')}>
-
+
MetaMask
connectToWallet('walletconnect')}>
-
+
WalletConnect
diff --git a/packages/boba/gateway/src/containers/modals/wrongNetwork/WrongNetworkModal.js b/packages/boba/gateway/src/containers/modals/wrongNetwork/WrongNetworkModal.js
index a124b69872..9738834722 100644
--- a/packages/boba/gateway/src/containers/modals/wrongNetwork/WrongNetworkModal.js
+++ b/packages/boba/gateway/src/containers/modals/wrongNetwork/WrongNetworkModal.js
@@ -4,15 +4,23 @@ import { closeModal } from 'actions/uiAction';
import Button from 'components/button/Button';
import Modal from 'components/modal/Modal';
-import React from 'react';
+import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { selectNetwork } from 'selectors/networkSelector';
+import { restTokenList } from 'actions/tokenAction';
function WrongNetworkModal({open}) {
const dispatch = useDispatch();
const network = useSelector(selectNetwork());
+ useEffect(() => {
+ if (open){
+ dispatch(restTokenList())
+ }
+ }, [dispatch, open])
+
+
function handleClose() {
dispatch(setConnect(false));
dispatch(closeModal('wrongNetworkModal'));
diff --git a/packages/boba/gateway/src/reducers/setupReducer.js b/packages/boba/gateway/src/reducers/setupReducer.js
index 1ce820ea21..2eb75cb8ad 100644
--- a/packages/boba/gateway/src/reducers/setupReducer.js
+++ b/packages/boba/gateway/src/reducers/setupReducer.js
@@ -31,6 +31,7 @@ const initialState = {
connectBOBA: false,
connect: false,
walletConnected: false,
+ chainIdChanged: false,
}
function setupReducer(state = initialState, action) {
@@ -101,6 +102,16 @@ function setupReducer(state = initialState, action) {
appChain: action.payload,
network: action.payload
}
+ case 'SETUP/CHAINIDCHANGED/SET':
+ return {
+ ...state,
+ chainIdChanged: true
+ }
+ case 'SETUP/CHAINIDCHANGED/RESET':
+ return {
+ ...state,
+ chainIdChanged: false
+ }
default:
return state
}
diff --git a/packages/boba/gateway/src/reducers/tokenReducer.js b/packages/boba/gateway/src/reducers/tokenReducer.js
index a3b6b40ee7..04a473a370 100644
--- a/packages/boba/gateway/src/reducers/tokenReducer.js
+++ b/packages/boba/gateway/src/reducers/tokenReducer.js
@@ -31,6 +31,9 @@ const initialState = {
function tokenReducer(state = initialState, action) {
switch (action.type) {
+ case 'TOKEN/GET/RESET':
+ state = initialState
+ return state
case 'TOKEN/GET/SUCCESS':
return {
...state,
diff --git a/packages/boba/gateway/src/selectors/setupSelector.js b/packages/boba/gateway/src/selectors/setupSelector.js
index 36ee043031..47b36934f9 100644
--- a/packages/boba/gateway/src/selectors/setupSelector.js
+++ b/packages/boba/gateway/src/selectors/setupSelector.js
@@ -92,3 +92,9 @@ export function selectWalletConnected () {
return state.setup['walletConnected']
}
}
+
+export function selectChainIdChanged () {
+ return function (state) {
+ return state.setup['chainIdChanged']
+ }
+}
diff --git a/packages/boba/gateway/src/services/networkService.js b/packages/boba/gateway/src/services/networkService.js
index 6d84b89bd6..0dba550707 100644
--- a/packages/boba/gateway/src/services/networkService.js
+++ b/packages/boba/gateway/src/services/networkService.js
@@ -672,6 +672,8 @@ class NetworkService {
const L1ChainId = networkDetail['L1']['chainId']
const L2ChainId = networkDetail['L2']['chainId']
+ this.walletService.bindProviderListeners()
+
// there are numerous possible chains we could be on also, either L1 or L2
// at this point, we only know whether we want to be on which network etc
@@ -680,11 +682,9 @@ class NetworkService {
} else if(!!NETWORK[ network ] && networkMM.chainId === L1ChainId) {
this.L1orL2 = 'L1';
} else {
- this.walletService.bindProviderListeners()
return 'wrongnetwork'
}
- this.walletService.bindProviderListeners()
// this should not do anything unless we changed chains
if (this.L1orL2 === 'L2') {
await this.getBobaFeeChoice()
@@ -700,6 +700,9 @@ class NetworkService {
async switchChain(targetLayer) {
+ // ignore request if we are already on the target layer
+ if (!targetLayer) { return false }
+
const networkDetail = getNetworkDetail({
network: this.networkGateway,
networkType: this.networkType
@@ -719,7 +722,7 @@ class NetworkService {
blockExplorerUrls: [networkDetail[targetLayer]?.blockExplorerUrl?.slice(0, -1)]
}
- await this.walletService.switchChain(targetIDHex, chainParam)
+ return await this.walletService.switchChain(targetIDHex, chainParam)
}
async getSevens() {
@@ -4016,9 +4019,13 @@ class NetworkService {
/***** L2 Fee *****/
/***********************************************/
async estimateL2Fee(payload=this.payloadForL1SecurityFee) {
- const l2GasPrice = await this.L2Provider.getGasPrice()
- const l2GasEstimate = await this.L2Provider.estimateGas(payload)
- return l2GasPrice.mul(l2GasEstimate).toNumber()
+ try {
+ const l2GasPrice = await this.L2Provider.getGasPrice()
+ const l2GasEstimate = await this.L2Provider.estimateGas(payload)
+ return l2GasPrice.mul(l2GasEstimate).toNumber()
+ } catch {
+ return 0
+ }
}
/***********************************************/
diff --git a/packages/boba/gateway/src/services/wallet.service.js b/packages/boba/gateway/src/services/wallet.service.js
index e7f0b868f6..6c73e3b71f 100644
--- a/packages/boba/gateway/src/services/wallet.service.js
+++ b/packages/boba/gateway/src/services/wallet.service.js
@@ -17,6 +17,7 @@ limitations under the License. */
import { providers } from "ethers"
import WalletConnectProvider from "@walletconnect/web3-provider"
import { rpcUrls } from 'util/network/network.util'
+import store from 'store'
class WalletService {
constructor() {
@@ -41,11 +42,10 @@ class WalletService {
async disconnectMetaMask() {
try {
await window.ethereum.request({ method: "eth_requestAccounts", params: [{ eth_accounts: {} }] })
- this.provider = null
- this.account = null
- this.walletType = null
+ return true
} catch (e) {
console.log(`Error disconnecting wallet: ${e}`)
+ return false
}
}
@@ -53,95 +53,105 @@ class WalletService {
window.ethereum.on('accountsChanged', () => {
window.location.reload()
})
- window.ethereum.on('chainChanged', () => {
- const chainChangedInit = JSON.parse(localStorage.getItem('chainChangedInit'))
- // do not reload window in the special case where the user
- // changed chains AND conncted at the same time
- // otherwise the user gets confused about why they are going through
- // two window reloads
- if(chainChangedInit) {
- localStorage.setItem('chainChangedInit', false)
- } else {
- localStorage.setItem('chainChangedFromMM', true)
- window.location.reload()
- }
+
+ window.ethereum.on('chainChanged', (chainId) => {
+ console.log(`MetaMask chain changed to ${chainId}`)
+ store.dispatch({ type: 'SETUP/CHAINIDCHANGED/SET' })
})
}
async connectWalletConnect() {
- console.log(rpcUrls)
this.walletConnectProvider = new WalletConnectProvider({
rpc: rpcUrls
})
- await this.walletConnectProvider.enable();
+ await this.walletConnectProvider.enable()
this.provider = new providers.Web3Provider(this.walletConnectProvider)
this.account = await this.provider.getSigner().getAddress()
this.walletType = 'walletconnect'
}
+ async disconnectWalletConnect() {
+ try {
+ await this.walletConnectProvider.disconnect()
+ return true
+ } catch (e) {
+ console.log(`Error disconnecting wallet: ${e}`)
+ return false
+ }
+ }
+
async listenWalletConnect() {
this.walletConnectProvider.on("accountsChanged", (accounts) => {
- window.location.reload()
- });
-
- this.walletConnectProvider.on("chainChanged", (chainId) => {
- const chainChangedInit = JSON.parse(localStorage.getItem('chainChangedInit'))
- // do not reload window in the special case where the user
- // changed chains AND conncted at the same time
- // otherwise the user gets confused about why they are going through
- // two window reloads
- if(chainChangedInit) {
- localStorage.setItem('chainChangedInit', false)
- } else {
- localStorage.setItem('chainChangedFromMM', true)
+ if (this.account !== accounts[0]) {
window.location.reload()
}
});
- this.walletConnectProvider.on("disconnect", (code, reason) => {
- console.log("WalletConnect disconnect: ", code, reason)
- window.location.reload()
+ this.walletConnectProvider.on("chainChanged", (chainId) => {
+ console.log(`walletconnect chain changed to: ${chainId}`)
+ store.dispatch({ type: 'SETUP/CHAINIDCHANGED/SET' })
});
}
async switchChain(chainId, chainInfo) {
const provider = this.walletType === 'metamask' ? window.ethereum : this.walletConnectProvider
try {
- if (this.walletType === 'walletconnect') {
+ await provider.request({
+ method: "wallet_switchEthereumChain",
+ params: [{ chainId }],
+ })
+ const chainIdChanged = await provider.request({ method: 'eth_chainId' })
+ // walletconnect does not return error code 4902 if the chain is not exist
+ // so we need to add the code of adding chain.
+ if (this.walletType === 'walletconnect' && chainIdChanged !== chainId) {
await provider.request({
method: "wallet_addEthereumChain",
params: [chainInfo, this.account],
})
}
- // walletconnect does not return error code 4902 if the chain is not exist
- // so we need to add the code of adding chain.
- await provider.request({
- method: "wallet_switchEthereumChain",
- params: [{ chainId }],
- })
+ await this.connectWallet(this.walletType)
+ return true
} catch (error) {
- await provider.request({
- method: "wallet_addEthereumChain",
- params: [chainInfo, this.account],
- })
if (error.code === 4902) {
try {
await provider.request({
method: "wallet_addEthereumChain",
params: [chainInfo, this.account],
})
- provider.on('chainChanged', this.handleChangeChainOnce)
+ await this.connectWallet(this.walletType)
+ return true
} catch (addError) {
console.log(`Error adding chain: ${addError}`)
- throw new Error(addError.code)
+ return false
}
} else {
- console.log(`Error switching chain: ${JSON.stringify(error)}`)
- throw new Error(error.code)
+ console.log(`Error switching chain: ${error?.message}`)
+ return false
}
}
}
-
+
+ async connectWallet(type) {
+ if (type === 'metamask') {
+ await this.connectMetaMask()
+ }
+ if (type === 'walletconnect') {
+ await this.connectWalletConnect()
+ }
+ }
+
+ async disconnectWallet() {
+ let result = false
+ if (this.walletType === 'metamask') {
+ result = await this.disconnectMetaMask()
+ }
+ if (this.walletType === 'walletconnect') {
+ result = await this.disconnectWalletConnect()
+ }
+ this.resetValues()
+ return result
+ }
+
bindProviderListeners() {
if (this.walletType === 'metamask') {
this.listenMetaMask()
@@ -151,13 +161,12 @@ class WalletService {
}
}
- handleChangeChainOnce(chainID_hex_string) {
-
- localStorage.setItem('chainChangedInit', true)
-
- localStorage.setItem('newChain', Number(chainID_hex_string))
- // and remove the listner
- window.ethereum.removeListener('chainChanged', this.handleChangeChainOnce)
+ resetValues() {
+ this.walletConnectProvider = null
+ this.provider = null
+ this.account = null
+ this.walletType = null
+ store.dispatch({ type: 'SETUP/CHAINIDCHANGED/RESET' })
}
}