From 59d2f71f0c9c85de9af5563b1d59661d0b21f3b6 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 12 Nov 2020 17:12:04 +0300 Subject: [PATCH] Replace window.web3 with window.ethereum --- CHANGELOG.md | 1 + .../assets/js/lib/smart_contract/functions.js | 245 ++++++++++-------- .../assets/js/lib/smart_contract/write.js | 83 +++--- .../assets/js/pages/address/logs.js | 3 +- 4 files changed, 188 insertions(+), 144 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7644fc807..3ecb7b1d8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ - [#3335](https://github.com/poanetwork/blockscout/pull/3335) - MarketCap calculation: check that ETS tables exist before inserting new data or lookup from the table ### Chore +- [#3450](https://github.com/poanetwork/blockscout/pull/3450) - Replace window.web3 with window.ethereum - [#3446](https://github.com/poanetwork/blockscout/pull/3446), [#3448](https://github.com/poanetwork/blockscout/pull/3448) - Set infinity timeout and increase cache invalidation period for counters - [#3431](https://github.com/poanetwork/blockscout/pull/3431) - Standardize token name definition, if name is empty - [#3421](https://github.com/poanetwork/blockscout/pull/3421) - Functions to enable GnosisSafe app link diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/functions.js b/apps/block_scout_web/assets/js/lib/smart_contract/functions.js index ecb59940bc..6f860c02a5 100644 --- a/apps/block_scout_web/assets/js/lib/smart_contract/functions.js +++ b/apps/block_scout_web/assets/js/lib/smart_contract/functions.js @@ -1,11 +1,9 @@ import $ from 'jquery' -import ethNetProps from 'eth-net-props' -import { walletEnabled, connectToWallet, getCurrentAccount, hideConnectButton } from './write.js' +import { props } from 'eth-net-props' +import { walletEnabled, connectToWallet, getCurrentAccount, shouldHideConnectButton } from './write.js' import { openErrorModal, openWarningModal, openSuccessModal, openModalWithMessage } from '../modals.js' import '../../pages/address' -const WEI_MULTIPLIER = 10 ** 18 - const loadFunctions = (element) => { const $element = $(element) const url = $element.data('url') @@ -19,41 +17,29 @@ const loadFunctions = (element) => { response => $element.html(response) ) .done(function () { - const $connectTo = $('[connect-to]') const $connect = $('[connect-metamask]') + const $connectTo = $('[connect-to]') const $connectedTo = $('[connected-to]') const $reconnect = $('[re-connect-metamask]') - const $connectedToAddress = $('[connected-to-address]') window.ethereum && window.ethereum.on('accountsChanged', function (accounts) { if (accounts.length === 0) { - $connectTo.removeClass('hidden') - $connect.removeClass('hidden') - $connectedTo.addClass('hidden') + showConnectElements($connect, $connectTo, $connectedTo) } else { - $connectTo.addClass('hidden') - $connect.removeClass('hidden') - $connectedTo.removeClass('hidden') - $connectedToAddress.html(`${accounts[0]}`) + showConnectedToElements($connect, $connectTo, $connectedTo, accounts[0]) } }) - hideConnectButton().then(({ shouldHide, account }) => { - if (shouldHide && account) { - $connectTo.addClass('hidden') - $connect.removeClass('hidden') - $connectedTo.removeClass('hidden') - $connectedToAddress.html(`${account}`) - } else if (shouldHide) { - $connectTo.removeClass('hidden') - $connect.addClass('hidden') - $connectedTo.addClass('hidden') - } else { - $connectTo.removeClass('hidden') - $connect.removeClass('hidden') - $connectedTo.addClass('hidden') - } - }) + shouldHideConnectButton() + .then(({ shouldHide, account }) => { + if (shouldHide && account) { + showConnectedToElements($connect, $connectTo, $connectedTo, account) + } else if (shouldHide) { + hideConnectButton($connect, $connectTo, $connectedTo) + } else { + showConnectElements($connect, $connectTo, $connectedTo) + } + }) $connect.on('click', () => { connectToWallet() @@ -72,6 +58,30 @@ const loadFunctions = (element) => { }) } +function showConnectedToElements ($connect, $connectTo, $connectedTo, account) { + $connectTo.addClass('hidden') + $connect.removeClass('hidden') + $connectedTo.removeClass('hidden') + setConnectToAddress(account) +} + +function setConnectToAddress (account) { + const $connectedToAddress = $('[connected-to-address]') + $connectedToAddress.html(`${account}`) +} + +function showConnectElements ($connect, $connectTo, $connectedTo) { + $connectTo.removeClass('hidden') + $connect.removeClass('hidden') + $connectedTo.addClass('hidden') +} + +function hideConnectButton ($connect, $connectTo, $connectedTo) { + $connectTo.removeClass('hidden') + $connect.addClass('hidden') + $connectedTo.addClass('hidden') +} + const readWriteFunction = (element) => { const $element = $(element) const $form = $element.find('[data-function-form]') @@ -82,99 +92,122 @@ const readWriteFunction = (element) => { const action = $form.data('action') event.preventDefault() + const $functionInputs = $form.find('input[name=function_input]') + const $functionName = $form.find('input[name=function_name]') + const functionName = $functionName && $functionName.val() + if (action === 'read') { const url = $form.data('url') - const $functionName = $form.find('input[name=function_name]') - const $methodId = $form.find('input[name=method_id]') - const $functionInputs = $form.find('input[name=function_input]') - const args = $.map($functionInputs, element => { - return $(element).val() - }) + const $methodId = $form.find('input[name=method_id]') + const args = $.map($functionInputs, element => $(element).val()) const data = { - function_name: $functionName.val(), + function_name: functionName, method_id: $methodId.val(), args } $.get(url, data, response => $responseContainer.html(response)) } else if (action === 'write') { - const chainId = $form.data('chainId') + const explorerChainId = $form.data('chainId') walletEnabled() - .then((isWalletEnabled) => { - if (isWalletEnabled) { - const functionName = $form.find('input[name=function_name]').val() - - const $functionInputs = $form.find('input[name=function_input]') - const $functionInputsExceptTxValue = $functionInputs.filter(':not([tx-value])') - const args = $.map($functionInputsExceptTxValue, element => $(element).val()) - - const $txValue = $functionInputs.filter('[tx-value]:first') - - const txValue = $txValue && $txValue.val() && parseFloat($txValue.val()) * WEI_MULTIPLIER - - const contractAddress = $form.data('contract-address') - const implementationAbi = $form.data('implementation-abi') - const parentAbi = $form.data('contract-abi') - const $parent = $('[data-smart-contract-functions]') - const contractType = $parent.data('type') - const contractAbi = contractType === 'proxy' ? implementationAbi : parentAbi - - window.web3.eth.getChainId() - .then(chainIdFromWallet => { - if (chainId !== chainIdFromWallet) { - const networkDisplayNameFromWallet = ethNetProps.props.getNetworkDisplayName(chainIdFromWallet) - const networkDisplayName = ethNetProps.props.getNetworkDisplayName(chainId) - return Promise.reject(new Error(`You connected to ${networkDisplayNameFromWallet} chain in the wallet, but the current instance of Blockscout is for ${networkDisplayName} chain`)) - } else { - return getCurrentAccount() - } - }) - .then(currentAccount => { - let methodToCall - - if (functionName) { - const TargetContract = new window.web3.eth.Contract(contractAbi, contractAddress) - methodToCall = TargetContract.methods[functionName](...args).send({ from: currentAccount, value: txValue || 0 }) - } else { - const txParams = { - from: currentAccount, - to: contractAddress, - value: txValue || 0 - } - methodToCall = window.web3.eth.sendTransaction(txParams) - } - - methodToCall - .on('error', function (error) { - openErrorModal(`Error in sending transaction for method "${functionName}"`, formatError(error), false) - }) - .on('transactionHash', function (txHash) { - openModalWithMessage($element.find('#pending-contract-write'), true, txHash) - const getTxReceipt = (txHash) => { - window.web3.eth.getTransactionReceipt(txHash) - .then(txReceipt => { - if (txReceipt) { - openSuccessModal('Success', `Successfully sent transaction for method "${functionName}"`) - clearInterval(txReceiptPollingIntervalId) - } - }) - } - const txReceiptPollingIntervalId = setInterval(() => { getTxReceipt(txHash) }, 5 * 1000) - }) - }) - .catch(error => { - openWarningModal('Unauthorized', formatError(error)) - }) - } else { - openWarningModal('Unauthorized', 'You haven\'t approved the reading of account list from your MetaMask or MetaMask/Nifty wallet is locked or is not installed.') - } - }) + .then((isWalletEnabled) => callMethod(isWalletEnabled, $functionInputs, explorerChainId, $form, functionName, $element)) } }) } +function callMethod (isWalletEnabled, $functionInputs, explorerChainId, $form, functionName, $element) { + if (!isWalletEnabled) { + const warningMsg = 'You haven\'t approved the reading of account list from your MetaMask or MetaMask/Nifty wallet is locked or is not installed.' + return openWarningModal('Unauthorized', warningMsg) + } + + const $functionInputsExceptTxValue = $functionInputs.filter(':not([tx-value])') + const args = $.map($functionInputsExceptTxValue, element => $(element).val()) + + const txValue = getTxValue($functionInputs) + const contractAddress = $form.data('contract-address') + const contractAbi = getContractABI($form) + + const { chainId: walletChainIdHex } = window.ethereum + compareChainIDs(explorerChainId, walletChainIdHex) + .then(currentAccount => { + let methodToCall + + if (functionName) { + const TargetContract = new window.web3.eth.Contract(contractAbi, contractAddress) + methodToCall = TargetContract.methods[functionName](...args).send({ from: currentAccount, value: txValue || 0 }) + } else { + const txParams = { + from: currentAccount, + to: contractAddress, + value: txValue || 0 + } + methodToCall = window.ethereum.request({ + method: 'eth_sendTransaction', + params: [txParams] + }) + } + + methodToCall + .on('error', function (error) { + openErrorModal(`Error in sending transaction for method "${functionName}"`, formatError(error), false) + }) + .on('transactionHash', function (txHash) { + onTransactionHash(txHash, $element, functionName) + }) + }) + .catch(error => { + openWarningModal('Unauthorized', formatError(error)) + }) +} + +function getTxValue ($functionInputs) { + const WEI_MULTIPLIER = 10 ** 18 + const $txValue = $functionInputs.filter('[tx-value]:first') + const txValue = $txValue && $txValue.val() && parseFloat($txValue.val()) * WEI_MULTIPLIER + return txValue +} + +function getContractABI ($form) { + const implementationAbi = $form.data('implementation-abi') + const parentAbi = $form.data('contract-abi') + const $parent = $('[data-smart-contract-functions]') + const contractType = $parent.data('type') + const contractAbi = contractType === 'proxy' ? implementationAbi : parentAbi + return contractAbi +} + +function compareChainIDs (explorerChainId, walletChainIdHex) { + if (explorerChainId !== parseInt(walletChainIdHex)) { + const networkDisplayNameFromWallet = props.getNetworkDisplayName(walletChainIdHex) + const networkDisplayName = props.getNetworkDisplayName(explorerChainId) + const errorMsg = `You connected to ${networkDisplayNameFromWallet} chain in the wallet, but the current instance of Blockscout is for ${networkDisplayName} chain` + return Promise.reject(new Error(errorMsg)) + } else { + return getCurrentAccount() + } +} + +function onTransactionHash (txHash, $element, functionName) { + openModalWithMessage($element.find('#pending-contract-write'), true, txHash) + const getTxReceipt = (txHash) => { + window.ethereum.request({ + method: 'eth_getTransactionReceipt', + params: [txHash] + }) + .then(txReceipt => { + if (txReceipt) { + const successMsg = `Successfully sent transaction for method "${functionName}"` + openSuccessModal('Success', successMsg) + clearInterval(txReceiptPollingIntervalId) + } + }) + } + const txReceiptPollingIntervalId = setInterval(() => { getTxReceipt(txHash) }, 5 * 1000) +} + const formatError = (error) => { let { message } = error message = message && message.split('Error: ').length > 1 ? message.split('Error: ')[1] : message diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/write.js b/apps/block_scout_web/assets/js/lib/smart_contract/write.js index 4c2b75abe9..73e50a6f2e 100644 --- a/apps/block_scout_web/assets/js/lib/smart_contract/write.js +++ b/apps/block_scout_web/assets/js/lib/smart_contract/write.js @@ -1,68 +1,77 @@ import Web3 from 'web3' export const walletEnabled = () => { - if (window.ethereum) { - window.web3 = new Web3(window.ethereum) - if (window.ethereum.isUnlocked && window.ethereum.isNiftyWallet) { // Nifty Wallet + return new Promise((resolve) => { + if (window.ethereum) { + window.web3 = new Web3(window.ethereum) + window.ethereum._metamask.isUnlocked() + .then(isUnlocked => { + if (isUnlocked && window.ethereum.isNiftyWallet) { // Nifty Wallet + window.web3 = new Web3(window.web3.currentProvider) + resolve(true) + } else if (isUnlocked === false && window.ethereum.isNiftyWallet) { // Nifty Wallet + window.ethereum.enable() + resolve(false) + } else { + if (window.ethereum.isNiftyWallet) { + window.ethereum.enable() + window.web3 = new Web3(window.web3.currentProvider) + resolve(true) + } else { + return window.ethereum.request({ method: 'eth_requestAccounts' }) + .then((_res) => { + window.web3 = new Web3(window.web3.currentProvider) + resolve(true) + }) + .catch(_error => { + resolve(false) + }) + } + } + }) + .catch(_error => { + resolve(false) + }) + } else if (window.web3) { window.web3 = new Web3(window.web3.currentProvider) - return Promise.resolve(true) - } else if (window.ethereum.isUnlocked === false && window.ethereum.isNiftyWallet) { // Nifty Wallet - return Promise.resolve(false) + resolve(true) } else { - if (window.ethereum.request) { - return window.ethereum.request({ method: 'eth_requestAccounts' }) - .then((_res) => { - window.web3 = new Web3(window.web3.currentProvider) - return Promise.resolve(true) - }) - } else { - window.ethereum.enable() - window.web3 = new Web3(window.web3.currentProvider) - return Promise.resolve(true) - } + resolve(false) } - } else if (window.web3) { - window.web3 = new Web3(window.web3.currentProvider) - return Promise.resolve(true) - } else { - return Promise.resolve(false) - } + }) } export const connectToWallet = () => { if (window.ethereum) { - if (window.ethereum.request) { - window.ethereum.request({ method: 'eth_requestAccounts' }) - } else { + if (window.ethereum.isNiftyWallet) { window.ethereum.enable() + } else { + window.ethereum.request({ method: 'eth_requestAccounts' }) } } } export const getCurrentAccount = async () => { - const accounts = await window.web3.eth.getAccounts() + const accounts = await window.ethereum.request({ method: 'eth_accounts' }) const account = accounts[0] ? accounts[0].toLowerCase() : null return account } -export const hideConnectButton = () => { +export const shouldHideConnectButton = () => { return new Promise((resolve) => { if (window.ethereum) { window.web3 = new Web3(window.ethereum) if (window.ethereum.isNiftyWallet) { resolve({ shouldHide: true, account: window.ethereum.selectedAddress }) } else if (window.ethereum.isMetaMask) { - window.ethereum.sendAsync({ method: 'eth_accounts' }, function (error, resp) { - if (error) { - resolve({ shouldHide: false }) - } - - if (resp) { - const { result: accounts } = resp + window.ethereum.request({ method: 'eth_accounts' }) + .then(accounts => { accounts.length > 0 ? resolve({ shouldHide: true, account: accounts[0] }) : resolve({ shouldHide: false }) - } - }) + }) + .catch(_error => { + resolve({ shouldHide: false }) + }) } else { resolve({ shouldHide: true, account: window.ethereum.selectedAddress }) } diff --git a/apps/block_scout_web/assets/js/pages/address/logs.js b/apps/block_scout_web/assets/js/pages/address/logs.js index 41faace88e..42478ccbf1 100644 --- a/apps/block_scout_web/assets/js/pages/address/logs.js +++ b/apps/block_scout_web/assets/js/pages/address/logs.js @@ -4,6 +4,7 @@ import humps from 'humps' import { connectElements } from '../../lib/redux_helpers.js' import { createAsyncLoadStore } from '../../lib/async_listing_load' import '../address' +import { utils } from 'web3' export const initialState = { addressHash: null, @@ -74,7 +75,7 @@ if ($('[data-page="address-logs"]').length) { }) const topic = $('[data-search-field]').val() const addressHashPlain = store.getState().addressHash - const addressHashChecksum = addressHashPlain && window.web3.toChecksumAddress(addressHashPlain) + const addressHashChecksum = addressHashPlain && utils.toChecksumAddress(addressHashPlain) const path = '/search-logs?topic=' + topic + '&address_id=' + addressHashChecksum store.dispatch({ type: 'START_REQUEST' }) $.getJSON(path, { type: 'JSON' })