Merge pull request #4931 from blockscout/vb-web3modal
Web3 modal with Wallet Connect for Write contract page and Staking Dapppull/4964/head
commit
b5bae303c2
@ -0,0 +1,177 @@ |
||||
import Web3 from 'web3' |
||||
import Web3Modal from 'web3modal' |
||||
import WalletConnectProvider from '@walletconnect/web3-provider' |
||||
import { compareChainIDs, formatError, showConnectElements, showConnectedToElements } from './common_helpers' |
||||
import { openWarningModal } from '../modals' |
||||
|
||||
const instanceChainId = process.env.CHAIN_ID ? parseInt(`${process.env.CHAIN_ID}`, 10) : 100 |
||||
const walletConnectOptions = { rpc: {}, chainId: instanceChainId } |
||||
walletConnectOptions.rpc[instanceChainId] = process.env.JSON_RPC ? process.env.JSON_RPC : 'https://dai.poa.network' |
||||
|
||||
// Chosen wallet provider given by the dialog window
|
||||
let provider |
||||
|
||||
// Web3modal instance
|
||||
let web3Modal |
||||
|
||||
/** |
||||
* Setup the orchestra |
||||
*/ |
||||
export async function web3ModalInit (connectToWallet, ...args) { |
||||
return new Promise((resolve) => { |
||||
// Tell Web3modal what providers we have available.
|
||||
// Built-in web browser provider (only one can exist as a time)
|
||||
// like MetaMask, Brave or Opera is added automatically by Web3modal
|
||||
const providerOptions = { |
||||
walletconnect: { |
||||
package: WalletConnectProvider, |
||||
options: walletConnectOptions |
||||
} |
||||
} |
||||
|
||||
web3Modal = new Web3Modal({ |
||||
cacheProvider: true, |
||||
providerOptions, |
||||
disableInjectedProvider: false |
||||
}) |
||||
|
||||
if (web3Modal.cachedProvider) { |
||||
connectToWallet(...args) |
||||
} |
||||
|
||||
resolve(web3Modal) |
||||
}) |
||||
} |
||||
|
||||
export const walletEnabled = () => { |
||||
return new Promise((resolve) => { |
||||
if (window.web3 && window.web3.currentProvider && window.web3.currentProvider.wc) { |
||||
resolve(true) |
||||
} else { |
||||
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) |
||||
resolve(true) |
||||
} else { |
||||
resolve(false) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
export async function disconnect () { |
||||
if (provider && provider.close) { |
||||
await provider.close() |
||||
} |
||||
|
||||
provider = null |
||||
|
||||
window.web3 = null |
||||
|
||||
// If the cached provider is not cleared,
|
||||
// WalletConnect will default to the existing session
|
||||
// and does not allow to re-scan the QR code with a new wallet.
|
||||
// Depending on your use case you may want or want not his behavir.
|
||||
await web3Modal.clearCachedProvider() |
||||
} |
||||
|
||||
/** |
||||
* Disconnect wallet button pressed. |
||||
*/ |
||||
export async function disconnectWallet () { |
||||
await disconnect() |
||||
|
||||
showConnectElements() |
||||
} |
||||
|
||||
export const connectToProvider = () => { |
||||
return new Promise((resolve, reject) => { |
||||
try { |
||||
web3Modal |
||||
.connect() |
||||
.then((connectedProvider) => { |
||||
provider = connectedProvider |
||||
window.web3 = new Web3(provider) |
||||
resolve(provider) |
||||
}) |
||||
} catch (e) { |
||||
reject(e) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
export const connectToWallet = async () => { |
||||
await connectToProvider() |
||||
|
||||
// Subscribe to accounts change
|
||||
provider.on('accountsChanged', async (accs) => { |
||||
const newAccount = accs && accs.length > 0 ? accs[0].toLowerCase() : null |
||||
|
||||
if (!newAccount) { |
||||
await disconnectWallet() |
||||
} |
||||
|
||||
fetchAccountData(showConnectedToElements, []) |
||||
}) |
||||
|
||||
// Subscribe to chainId change
|
||||
provider.on('chainChanged', (chainId) => { |
||||
compareChainIDs(instanceChainId, chainId) |
||||
.then(() => fetchAccountData(showConnectedToElements, [])) |
||||
.catch(error => { |
||||
openWarningModal('Unauthorized', formatError(error)) |
||||
}) |
||||
}) |
||||
|
||||
provider.on('disconnect', async () => { |
||||
await disconnectWallet() |
||||
}) |
||||
|
||||
await fetchAccountData(showConnectedToElements, []) |
||||
} |
||||
|
||||
export async function fetchAccountData (setAccount, args) { |
||||
// Get a Web3 instance for the wallet
|
||||
if (provider) { |
||||
window.web3 = new Web3(provider) |
||||
} |
||||
|
||||
// Get list of accounts of the connected wallet
|
||||
const accounts = window.web3 && await window.web3.eth.getAccounts() |
||||
|
||||
// MetaMask does not give you all accounts, only the selected account
|
||||
if (accounts && accounts.length > 0) { |
||||
const selectedAccount = accounts[0] |
||||
|
||||
setAccount(selectedAccount, ...args) |
||||
} |
||||
} |
@ -0,0 +1,116 @@ |
||||
import $ from 'jquery' |
||||
import { openErrorModal, openWarningModal, openSuccessModal, openModalWithMessage } from '../modals' |
||||
import { compareChainIDs, formatError, formatTitleAndError, getContractABI, getCurrentAccountPromise, getMethodInputs, prepareMethodArgs } from './common_helpers' |
||||
|
||||
export const queryMethod = (isWalletEnabled, url, $methodId, args, type, functionName, $responseContainer) => { |
||||
let data = { |
||||
function_name: functionName, |
||||
method_id: $methodId.val(), |
||||
type: type, |
||||
args |
||||
} |
||||
if (isWalletEnabled) { |
||||
getCurrentAccountPromise(window.web3 && window.web3.currentProvider) |
||||
.then((currentAccount) => { |
||||
data = { |
||||
function_name: functionName, |
||||
method_id: $methodId.val(), |
||||
type: type, |
||||
from: currentAccount, |
||||
args |
||||
} |
||||
$.get(url, data, response => $responseContainer.html(response)) |
||||
} |
||||
) |
||||
} else { |
||||
$.get(url, data, response => $responseContainer.html(response)) |
||||
} |
||||
} |
||||
|
||||
export const callMethod = (isWalletEnabled, $functionInputs, explorerChainId, $form, functionName, $element) => { |
||||
if (!isWalletEnabled) { |
||||
const warningMsg = 'Wallet is not connected.' |
||||
return openWarningModal('Unauthorized', warningMsg) |
||||
} |
||||
const contractAbi = getContractABI($form) |
||||
const inputs = getMethodInputs(contractAbi, functionName) |
||||
|
||||
const $functionInputsExceptTxValue = $functionInputs.filter(':not([tx-value])') |
||||
const args = prepareMethodArgs($functionInputsExceptTxValue, inputs) |
||||
|
||||
const txValue = getTxValue($functionInputs) |
||||
const contractAddress = $form.data('contract-address') |
||||
|
||||
window.web3.eth.getChainId() |
||||
.then((walletChainId) => { |
||||
compareChainIDs(explorerChainId, walletChainId) |
||||
.then(() => getCurrentAccountPromise(window.web3.currentProvider)) |
||||
.catch(error => { |
||||
openWarningModal('Unauthorized', formatError(error)) |
||||
}) |
||||
.then((currentAccount) => { |
||||
if (functionName) { |
||||
const TargetContract = new window.web3.eth.Contract(contractAbi, contractAddress) |
||||
const sendParams = { from: currentAccount, value: txValue || 0 } |
||||
const methodToCall = TargetContract.methods[functionName](...args).send(sendParams) |
||||
methodToCall |
||||
.on('error', function (error) { |
||||
const titleAndError = formatTitleAndError(error) |
||||
const message = titleAndError.message + (titleAndError.txHash ? `<br><a href="/tx/${titleAndError.txHash}">More info</a>` : '') |
||||
openErrorModal(titleAndError.title.length ? titleAndError.title : `Error in sending transaction for method "${functionName}"`, message, false) |
||||
}) |
||||
.on('transactionHash', function (txHash) { |
||||
onTransactionHash(txHash, $element, functionName) |
||||
}) |
||||
} else { |
||||
const txParams = { |
||||
from: currentAccount, |
||||
to: contractAddress, |
||||
value: txValue || 0 |
||||
} |
||||
window.ethereum.request({ |
||||
method: 'eth_sendTransaction', |
||||
params: [txParams] |
||||
}) |
||||
.then(function (txHash) { |
||||
onTransactionHash(txHash, $element, functionName) |
||||
}) |
||||
.catch(function (error) { |
||||
openErrorModal('Error in sending transaction for fallback method', formatError(error), false) |
||||
}) |
||||
} |
||||
}) |
||||
.catch(error => { |
||||
openWarningModal('Unauthorized', formatError(error)) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
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) |
||||
} |
||||
|
||||
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 |
||||
let txValueStr = txValue && txValue.toString(16) |
||||
if (!txValueStr) { |
||||
txValueStr = '0' |
||||
} |
||||
return '0x' + txValueStr |
||||
} |
@ -1,186 +0,0 @@ |
||||
import Web3 from 'web3' |
||||
import $ from 'jquery' |
||||
import { openErrorModal, openWarningModal, openSuccessModal, openModalWithMessage } from '../modals' |
||||
import { compareChainIDs, formatError, formatTitleAndError, getContractABI, getCurrentAccount, getMethodInputs, prepareMethodArgs } from './common_helpers' |
||||
|
||||
export const walletEnabled = () => { |
||||
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) |
||||
resolve(true) |
||||
} else { |
||||
resolve(false) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
export const connectToWallet = () => { |
||||
if (window.ethereum) { |
||||
if (window.ethereum.isNiftyWallet) { |
||||
window.ethereum.enable() |
||||
} else { |
||||
window.ethereum.request({ method: 'eth_requestAccounts' }) |
||||
} |
||||
} |
||||
} |
||||
|
||||
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.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 }) |
||||
} |
||||
} else { |
||||
resolve({ shouldHide: false }) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
export 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 contractAbi = getContractABI($form) |
||||
const inputs = getMethodInputs(contractAbi, functionName) |
||||
|
||||
const $functionInputsExceptTxValue = $functionInputs.filter(':not([tx-value])') |
||||
const args = prepareMethodArgs($functionInputsExceptTxValue, inputs) |
||||
|
||||
const txValue = getTxValue($functionInputs) |
||||
const contractAddress = $form.data('contract-address') |
||||
|
||||
const { chainId: walletChainIdHex } = window.ethereum |
||||
compareChainIDs(explorerChainId, walletChainIdHex) |
||||
.then(() => getCurrentAccount()) |
||||
.then(currentAccount => { |
||||
if (functionName) { |
||||
const TargetContract = new window.web3.eth.Contract(contractAbi, contractAddress) |
||||
const sendParams = { from: currentAccount, value: txValue || 0 } |
||||
const methodToCall = TargetContract.methods[functionName](...args).send(sendParams) |
||||
methodToCall |
||||
.on('error', function (error) { |
||||
var titleAndError = formatTitleAndError(error) |
||||
var message = titleAndError.message + (titleAndError.txHash ? `<br><a href="/tx/${titleAndError.txHash}">More info</a>` : '') |
||||
openErrorModal(titleAndError.title.length ? titleAndError.title : `Error in sending transaction for method "${functionName}"`, message, false) |
||||
}) |
||||
.on('transactionHash', function (txHash) { |
||||
onTransactionHash(txHash, $element, functionName) |
||||
}) |
||||
} else { |
||||
const txParams = { |
||||
from: currentAccount, |
||||
to: contractAddress, |
||||
value: txValue || 0 |
||||
} |
||||
window.ethereum.request({ |
||||
method: 'eth_sendTransaction', |
||||
params: [txParams] |
||||
}) |
||||
.then(function (txHash) { |
||||
onTransactionHash(txHash, $element, functionName) |
||||
}) |
||||
.catch(function (error) { |
||||
openErrorModal('Error in sending transaction for fallback method', formatError(error), false) |
||||
}) |
||||
} |
||||
}) |
||||
.catch(error => { |
||||
openWarningModal('Unauthorized', formatError(error)) |
||||
}) |
||||
} |
||||
|
||||
export function queryMethod (isWalletEnabled, url, $methodId, args, type, functionName, $responseContainer) { |
||||
var data = { |
||||
function_name: functionName, |
||||
method_id: $methodId.val(), |
||||
type: type, |
||||
args |
||||
} |
||||
if (isWalletEnabled) { |
||||
getCurrentAccount() |
||||
.then((currentAccount) => { |
||||
data = { |
||||
function_name: functionName, |
||||
method_id: $methodId.val(), |
||||
type: type, |
||||
from: currentAccount, |
||||
args |
||||
} |
||||
$.get(url, data, response => $responseContainer.html(response)) |
||||
} |
||||
) |
||||
} else { |
||||
$.get(url, data, response => $responseContainer.html(response)) |
||||
} |
||||
} |
||||
|
||||
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) |
||||
} |
||||
|
||||
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 |
||||
var txValueStr = txValue && txValue.toString(16) |
||||
if (!txValueStr) { |
||||
txValueStr = '0' |
||||
} |
||||
return '0x' + txValueStr |
||||
} |
@ -1 +0,0 @@ |
||||
import '../lib/smart_contract/write' |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue