Replace window.web3 with window.ethereum

pull/3450/head
Victor Baranov 4 years ago
parent 4e520f7ec3
commit 59d2f71f0c
  1. 1
      CHANGELOG.md
  2. 245
      apps/block_scout_web/assets/js/lib/smart_contract/functions.js
  3. 83
      apps/block_scout_web/assets/js/lib/smart_contract/write.js
  4. 3
      apps/block_scout_web/assets/js/pages/address/logs.js

@ -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

@ -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(`<a href='/address/${accounts[0]}'>${accounts[0]}</a>`)
showConnectedToElements($connect, $connectTo, $connectedTo, accounts[0])
}
})
hideConnectButton().then(({ shouldHide, account }) => {
if (shouldHide && account) {
$connectTo.addClass('hidden')
$connect.removeClass('hidden')
$connectedTo.removeClass('hidden')
$connectedToAddress.html(`<a href='/address/${account}'>${account}</a>`)
} 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(`<a href='/address/${account}'>${account}</a>`)
}
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 <a href="/tx/${txHash}">transaction</a> 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 <a href="/tx/${txHash}">transaction</a> 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

@ -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 })
}

@ -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' })

Loading…
Cancel
Save