Skip to content

Commit

Permalink
Merge pull request blockscout#3450 from poanetwork/vb-eliminate-windo…
Browse files Browse the repository at this point in the history
…w-web3

Replace window.web3 with window.ethereum
  • Loading branch information
vbaranov authored Nov 16, 2020
2 parents 4e520f7 + 59d2f71 commit e6fb89b
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 144 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
245 changes: 139 additions & 106 deletions apps/block_scout_web/assets/js/lib/smart_contract/functions.js
Original file line number Diff line number Diff line change
@@ -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')
Expand All @@ -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()
Expand All @@ -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]')
Expand All @@ -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
Expand Down
Loading

0 comments on commit e6fb89b

Please sign in to comment.