|
|
|
@ -1,33 +1,27 @@ |
|
|
|
|
/* global */ |
|
|
|
|
'use strict' |
|
|
|
|
|
|
|
|
|
var $ = require('jquery') |
|
|
|
|
var yo = require('yo-yo') |
|
|
|
|
var ethJSUtil = require('ethereumjs-util') |
|
|
|
|
var BN = ethJSUtil.BN |
|
|
|
|
var helper = require('../../lib/helper') |
|
|
|
|
var copyToClipboard = require('./copy-to-clipboard') |
|
|
|
|
var css = require('../../universal-dapp-styles') |
|
|
|
|
var MultiParamManager = require('./multiParamManager') |
|
|
|
|
var remixLib = require('remix-lib') |
|
|
|
|
var txFormat = remixLib.execution.txFormat |
|
|
|
|
var txHelper = remixLib.execution.txHelper |
|
|
|
|
|
|
|
|
|
var confirmDialog = require('./confirmDialog') |
|
|
|
|
var modalCustom = require('./modal-dialog-custom') |
|
|
|
|
var modalDialog = require('./modaldialog') |
|
|
|
|
var TreeView = require('./TreeView') |
|
|
|
|
|
|
|
|
|
function UniversalDAppUI (blockchain, logCallback) { |
|
|
|
|
this.blockchain = blockchain |
|
|
|
|
this.logCallback = logCallback |
|
|
|
|
this.compilerData = {contractsDetails: {}} |
|
|
|
|
} |
|
|
|
|
const $ = require('jquery') |
|
|
|
|
const yo = require('yo-yo') |
|
|
|
|
const ethJSUtil = require('ethereumjs-util') |
|
|
|
|
const BN = ethJSUtil.BN |
|
|
|
|
const helper = require('../../lib/helper') |
|
|
|
|
const copyToClipboard = require('./copy-to-clipboard') |
|
|
|
|
const css = require('../../universal-dapp-styles') |
|
|
|
|
const MultiParamManager = require('./multiParamManager') |
|
|
|
|
const remixLib = require('remix-lib') |
|
|
|
|
const txFormat = remixLib.execution.txFormat |
|
|
|
|
const txHelper = remixLib.execution.txHelper |
|
|
|
|
|
|
|
|
|
const confirmDialog = require('./confirmDialog') |
|
|
|
|
const modalCustom = require('./modal-dialog-custom') |
|
|
|
|
const modalDialog = require('./modaldialog') |
|
|
|
|
const TreeView = require('./TreeView') |
|
|
|
|
|
|
|
|
|
function decodeResponseToTreeView (response, fnabi) { |
|
|
|
|
var treeView = new TreeView({ |
|
|
|
|
const treeView = new TreeView({ |
|
|
|
|
extractData: (item, parent, key) => { |
|
|
|
|
var ret = {} |
|
|
|
|
let ret = {} |
|
|
|
|
if (BN.isBN(item)) { |
|
|
|
|
ret.self = item.toString(10) |
|
|
|
|
ret.children = [] |
|
|
|
@ -40,27 +34,42 @@ function decodeResponseToTreeView (response, fnabi) { |
|
|
|
|
return treeView.render(txFormat.decodeResponse(response, fnabi)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
UniversalDAppUI.prototype.renderInstance = function (contract, address, contractName) { |
|
|
|
|
var noInstances = document.querySelector('[class^="noInstancesText"]') |
|
|
|
|
if (noInstances) { |
|
|
|
|
noInstances.parentNode.removeChild(noInstances) |
|
|
|
|
class UniversalDAppUI { |
|
|
|
|
|
|
|
|
|
constructor (blockchain, logCallback) { |
|
|
|
|
this.blockchain = blockchain |
|
|
|
|
this.logCallback = logCallback |
|
|
|
|
this.compilerData = { contractsDetails: {} } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
renderInstance (contract, address, contractName) { |
|
|
|
|
const noInstances = document.querySelector('[class^="noInstancesText"]') |
|
|
|
|
if (noInstances) { |
|
|
|
|
noInstances.parentNode.removeChild(noInstances) |
|
|
|
|
} |
|
|
|
|
const abi = txHelper.sortAbiFunction(contract.abi) |
|
|
|
|
return this.renderInstanceFromABI(abi, address, contractName) |
|
|
|
|
} |
|
|
|
|
const abi = txHelper.sortAbiFunction(contract.abi) |
|
|
|
|
return this.renderInstanceFromABI(abi, address, contractName) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TODO this function was named before "appendChild".
|
|
|
|
|
// this will render an instance: contract name, contract address, and all the public functions
|
|
|
|
|
// basically this has to be called for the "atAddress" (line 393) and when a contract creation succeed
|
|
|
|
|
// this returns a DOM element
|
|
|
|
|
UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address, contractName) { |
|
|
|
|
address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex') |
|
|
|
|
address = ethJSUtil.toChecksumAddress(address) |
|
|
|
|
var instance = yo`<div class="instance ${css.instance} ${css.hidesub}" id="instance${address}"></div>` |
|
|
|
|
const context = this.blockchain.context() |
|
|
|
|
|
|
|
|
|
var shortAddress = helper.shortenAddress(address) |
|
|
|
|
var title = yo` |
|
|
|
|
// TODO this function was named before "appendChild".
|
|
|
|
|
// this will render an instance: contract name, contract address, and all the public functions
|
|
|
|
|
// basically this has to be called for the "atAddress" (line 393) and when a contract creation succeed
|
|
|
|
|
// this returns a DOM element
|
|
|
|
|
renderInstanceFromABI (contractABI, address, contractName) { |
|
|
|
|
address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex') |
|
|
|
|
address = ethJSUtil.toChecksumAddress(address) |
|
|
|
|
const instance = yo`<div class="instance ${css.instance} ${css.hidesub}" id="instance${address}"></div>` |
|
|
|
|
const context = this.blockchain.context() |
|
|
|
|
|
|
|
|
|
function toggleClass (e) { |
|
|
|
|
$(instance).toggleClass(`${css.hidesub}`) |
|
|
|
|
// e.currentTarget.querySelector('i')
|
|
|
|
|
e.currentTarget.querySelector('i').classList.toggle(`fa-angle-right`) |
|
|
|
|
e.currentTarget.querySelector('i').classList.toggle(`fa-angle-down`) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const shortAddress = helper.shortenAddress(address) |
|
|
|
|
const title = yo` |
|
|
|
|
<div class="${css.title} alert alert-secondary p-2"> |
|
|
|
|
<button class="btn ${css.titleExpander}" onclick="${(e) => { toggleClass(e) }}"> |
|
|
|
|
<i class="fas fa-angle-right" aria-hidden="true"></i> |
|
|
|
@ -78,166 +87,155 @@ UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address |
|
|
|
|
</div> |
|
|
|
|
` |
|
|
|
|
|
|
|
|
|
var close = yo` |
|
|
|
|
const close = yo` |
|
|
|
|
<button |
|
|
|
|
class="${css.udappClose} p-1 btn btn-secondary" |
|
|
|
|
onclick=${remove} |
|
|
|
|
onclick=${() => { instance.remove() }} |
|
|
|
|
title="Remove from the list" |
|
|
|
|
> |
|
|
|
|
<i class="${css.closeIcon} fas fa-times" aria-hidden="true"></i> |
|
|
|
|
</button>` |
|
|
|
|
title.querySelector('.btn-group').appendChild(close) |
|
|
|
|
title.querySelector('.btn-group').appendChild(close) |
|
|
|
|
|
|
|
|
|
var contractActionsWrapper = yo` |
|
|
|
|
const contractActionsWrapper = yo` |
|
|
|
|
<div class="${css.cActionsWrapper}"> |
|
|
|
|
</div> |
|
|
|
|
` |
|
|
|
|
|
|
|
|
|
function remove () { |
|
|
|
|
instance.remove() |
|
|
|
|
// @TODO perhaps add a callack here to warn the caller that the instance has been removed
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function toggleClass (e) { |
|
|
|
|
$(instance).toggleClass(`${css.hidesub}`) |
|
|
|
|
// e.currentTarget.querySelector('i')
|
|
|
|
|
e.currentTarget.querySelector('i').classList.toggle(`fa-angle-right`) |
|
|
|
|
e.currentTarget.querySelector('i').classList.toggle(`fa-angle-down`) |
|
|
|
|
} |
|
|
|
|
instance.appendChild(title) |
|
|
|
|
instance.appendChild(contractActionsWrapper) |
|
|
|
|
|
|
|
|
|
instance.appendChild(title) |
|
|
|
|
instance.appendChild(contractActionsWrapper) |
|
|
|
|
// Add the fallback function
|
|
|
|
|
const fallback = txHelper.getFallbackInterface(contractABI) |
|
|
|
|
|
|
|
|
|
// Add the fallback function
|
|
|
|
|
const fallback = txHelper.getFallbackInterface(contractABI) |
|
|
|
|
|
|
|
|
|
if (fallback) { |
|
|
|
|
contractActionsWrapper.appendChild(this.getCallButton({ |
|
|
|
|
funABI: fallback, |
|
|
|
|
address: address, |
|
|
|
|
contractAbi: contractABI, |
|
|
|
|
contractName: contractName |
|
|
|
|
})) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$.each(contractABI, (i, funABI) => { |
|
|
|
|
if (funABI.type !== 'function') { |
|
|
|
|
return |
|
|
|
|
if (fallback) { |
|
|
|
|
contractActionsWrapper.appendChild(this.getCallButton({ |
|
|
|
|
funABI: fallback, |
|
|
|
|
address: address, |
|
|
|
|
contractAbi: contractABI, |
|
|
|
|
contractName: contractName |
|
|
|
|
})) |
|
|
|
|
} |
|
|
|
|
// @todo getData cannot be used with overloaded functions
|
|
|
|
|
contractActionsWrapper.appendChild(this.getCallButton({ |
|
|
|
|
funABI: funABI, |
|
|
|
|
address: address, |
|
|
|
|
contractAbi: contractABI, |
|
|
|
|
contractName: contractName |
|
|
|
|
})) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
return instance |
|
|
|
|
} |
|
|
|
|
$.each(contractABI, (i, funABI) => { |
|
|
|
|
if (funABI.type !== 'function') { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
// @todo getData cannot be used with overloaded functions
|
|
|
|
|
contractActionsWrapper.appendChild(this.getCallButton({ |
|
|
|
|
funABI: funABI, |
|
|
|
|
address: address, |
|
|
|
|
contractAbi: contractABI, |
|
|
|
|
contractName: contractName |
|
|
|
|
})) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
return instance |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
UniversalDAppUI.prototype.getConfirmationCb = function (modalDialog, confirmDialog) { |
|
|
|
|
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { |
|
|
|
|
if (network.name !== 'Main') { |
|
|
|
|
return continueTxExecution(null) |
|
|
|
|
} |
|
|
|
|
const amount = this.blockchain.fromWei(tx.value, true, 'ether') |
|
|
|
|
const content = confirmDialog(tx, amount, gasEstimation, null, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice) |
|
|
|
|
|
|
|
|
|
modalDialog('Confirm transaction', content, |
|
|
|
|
{ |
|
|
|
|
label: 'Confirm', |
|
|
|
|
fn: () => { |
|
|
|
|
this.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked) |
|
|
|
|
// TODO: check if this is check is still valid given the refactor
|
|
|
|
|
if (!content.gasPriceStatus) { |
|
|
|
|
cancelCb('Given gas price is not correct') |
|
|
|
|
} else { |
|
|
|
|
var gasPrice = this.blockchain.toWei(content.querySelector('#gasprice').value, 'gwei') |
|
|
|
|
continueTxExecution(gasPrice) |
|
|
|
|
getConfirmationCb (modalDialog, confirmDialog) { |
|
|
|
|
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { |
|
|
|
|
if (network.name !== 'Main') { |
|
|
|
|
return continueTxExecution(null) |
|
|
|
|
} |
|
|
|
|
const amount = this.blockchain.fromWei(tx.value, true, 'ether') |
|
|
|
|
const content = confirmDialog(tx, amount, gasEstimation, null, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice) |
|
|
|
|
|
|
|
|
|
modalDialog('Confirm transaction', content, |
|
|
|
|
{ |
|
|
|
|
label: 'Confirm', |
|
|
|
|
fn: () => { |
|
|
|
|
this.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked) |
|
|
|
|
// TODO: check if this is check is still valid given the refactor
|
|
|
|
|
if (!content.gasPriceStatus) { |
|
|
|
|
cancelCb('Given gas price is not correct') |
|
|
|
|
} else { |
|
|
|
|
const gasPrice = this.blockchain.toWei(content.querySelector('#gasprice').value, 'gwei') |
|
|
|
|
continueTxExecution(gasPrice) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, { |
|
|
|
|
label: 'Cancel', |
|
|
|
|
fn: () => { |
|
|
|
|
return cancelCb('Transaction canceled by user.') |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, { |
|
|
|
|
label: 'Cancel', |
|
|
|
|
fn: () => { |
|
|
|
|
return cancelCb('Transaction canceled by user.') |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return confirmationCb |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return confirmationCb |
|
|
|
|
} |
|
|
|
|
// TODO this is used by renderInstance when a new instance is displayed.
|
|
|
|
|
// this returns a DOM element.
|
|
|
|
|
getCallButton (args) { |
|
|
|
|
// args.funABI, args.address [fun only]
|
|
|
|
|
// args.contractName [constr only]
|
|
|
|
|
const lookupOnly = args.funABI.stateMutability === 'view' || args.funABI.stateMutability === 'pure' || args.funABI.constant |
|
|
|
|
|
|
|
|
|
// TODO this is used by renderInstance when a new instance is displayed.
|
|
|
|
|
// this returns a DOM element.
|
|
|
|
|
UniversalDAppUI.prototype.getCallButton = function (args) { |
|
|
|
|
let self = this |
|
|
|
|
// args.funABI, args.address [fun only]
|
|
|
|
|
// args.contractName [constr only]
|
|
|
|
|
const lookupOnly = args.funABI.stateMutability === 'view' || args.funABI.stateMutability === 'pure' || args.funABI.constant |
|
|
|
|
|
|
|
|
|
var outputOverride = yo`<div class=${css.value}></div>` // show return value
|
|
|
|
|
|
|
|
|
|
function clickButton (valArr, inputsValues) { |
|
|
|
|
let logMsg |
|
|
|
|
if (!lookupOnly) { |
|
|
|
|
logMsg = `call to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}` |
|
|
|
|
} else { |
|
|
|
|
logMsg = `transact to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}` |
|
|
|
|
} |
|
|
|
|
const outputOverride = yo`<div class=${css.value}></div>` // show return value
|
|
|
|
|
|
|
|
|
|
const confirmationCb = self.getConfirmationCb(modalDialog, confirmDialog) |
|
|
|
|
const continueCb = (error, continueTxExecution, cancelCb) => { |
|
|
|
|
if (error) { |
|
|
|
|
const msg = typeof error !== 'string' ? error.message : error |
|
|
|
|
modalDialog( |
|
|
|
|
'Gas estimation failed', |
|
|
|
|
yo` |
|
|
|
|
const clickButton = (valArr, inputsValues) => { |
|
|
|
|
let logMsg |
|
|
|
|
if (!lookupOnly) { |
|
|
|
|
logMsg = `call to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}` |
|
|
|
|
} else { |
|
|
|
|
logMsg = `transact to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const confirmationCb = this.getConfirmationCb(modalDialog, confirmDialog) |
|
|
|
|
const continueCb = (error, continueTxExecution, cancelCb) => { |
|
|
|
|
if (error) { |
|
|
|
|
const msg = typeof error !== 'string' ? error.message : error |
|
|
|
|
modalDialog( |
|
|
|
|
'Gas estimation failed', |
|
|
|
|
yo` |
|
|
|
|
<div>Gas estimation errored with the following message (see below). |
|
|
|
|
The transaction execution will likely fail. Do you want to force sending? <br>${msg}</div> |
|
|
|
|
`,
|
|
|
|
|
{ |
|
|
|
|
label: 'Send Transaction', |
|
|
|
|
fn: () => continueTxExecution() |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
label: 'Cancel Transaction', |
|
|
|
|
fn: () => cancelCb() |
|
|
|
|
} |
|
|
|
|
) |
|
|
|
|
} else { |
|
|
|
|
continueTxExecution() |
|
|
|
|
{ |
|
|
|
|
label: 'Send Transaction', |
|
|
|
|
fn: () => continueTxExecution() |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
label: 'Cancel Transaction', |
|
|
|
|
fn: () => cancelCb() |
|
|
|
|
} |
|
|
|
|
) |
|
|
|
|
} else { |
|
|
|
|
continueTxExecution() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const outputCb = (returnValue) => { |
|
|
|
|
const decoded = decodeResponseToTreeView(returnValue, args.funABI) |
|
|
|
|
outputOverride.innerHTML = '' |
|
|
|
|
outputOverride.appendChild(decoded) |
|
|
|
|
} |
|
|
|
|
const outputCb = (returnValue) => { |
|
|
|
|
const decoded = decodeResponseToTreeView(returnValue, args.funABI) |
|
|
|
|
outputOverride.innerHTML = '' |
|
|
|
|
outputOverride.appendChild(decoded) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const promptCb = (okCb, cancelCb) => { |
|
|
|
|
modalCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const promptCb = (okCb, cancelCb) => { |
|
|
|
|
modalCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb) |
|
|
|
|
const callType = args.funABI.type !== 'fallback' ? inputsValues : '' |
|
|
|
|
this.blockchain.runOrCallContractMethod(args.contractName, args.contractAbi, args.funABI, inputsValues, args.address, callType, lookupOnly, logMsg, this.logCallback, outputCb, confirmationCb, continueCb, promptCb) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const callType = args.funABI.type !== 'fallback' ? inputsValues : '' |
|
|
|
|
self.blockchain.runOrCallContractMethod(args.contractName, args.contractAbi, args.funABI, inputsValues, args.address, callType, lookupOnly, logMsg, self.logCallback, outputCb, confirmationCb, continueCb, promptCb) |
|
|
|
|
} |
|
|
|
|
let inputs = '' |
|
|
|
|
if (args.funABI.inputs) { |
|
|
|
|
inputs = txHelper.inputParametersDeclarationToString(args.funABI.inputs) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let inputs = '' |
|
|
|
|
if (args.funABI.inputs) { |
|
|
|
|
inputs = txHelper.inputParametersDeclarationToString(args.funABI.inputs) |
|
|
|
|
} |
|
|
|
|
const multiParamManager = new MultiParamManager(lookupOnly, args.funABI, (valArray, inputsValues, domEl) => { |
|
|
|
|
clickButton(valArray, inputsValues, domEl) |
|
|
|
|
}, inputs) |
|
|
|
|
|
|
|
|
|
const multiParamManager = new MultiParamManager(lookupOnly, args.funABI, (valArray, inputsValues, domEl) => { |
|
|
|
|
clickButton(valArray, inputsValues, domEl) |
|
|
|
|
}, inputs) |
|
|
|
|
const contractActionsContainer = yo`<div class="${css.contractActionsContainer}" >${multiParamManager.render()}</div>` |
|
|
|
|
contractActionsContainer.appendChild(outputOverride) |
|
|
|
|
|
|
|
|
|
const contractActionsContainer = yo`<div class="${css.contractActionsContainer}" >${multiParamManager.render()}</div>` |
|
|
|
|
contractActionsContainer.appendChild(outputOverride) |
|
|
|
|
return contractActionsContainer |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return contractActionsContainer |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
module.exports = UniversalDAppUI |
|
|
|
|