parent
8933cb1adf
commit
39e8f543a8
@ -1,423 +0,0 @@ |
||||
import publishToStorage from '../../../publishToStorage' |
||||
const yo = require('yo-yo') |
||||
const ethJSUtil = require('ethereumjs-util') |
||||
const css = require('../styles/run-tab-styles') |
||||
const modalDialogCustom = require('../../ui/modal-dialog-custom') |
||||
const remixLib = require('@remix-project/remix-lib') |
||||
const EventManager = remixLib.EventManager |
||||
const confirmDialog = require('../../ui/confirmDialog') |
||||
const modalDialog = require('../../ui/modaldialog') |
||||
const MultiParamManager = require('../../ui/multiParamManager') |
||||
const helper = require('../../../lib/helper') |
||||
const addTooltip = require('../../ui/tooltip') |
||||
const _paq = window._paq = window._paq || [] |
||||
|
||||
class ContractDropdownUI { |
||||
constructor (blockchain, dropdownLogic, logCallback, runView) { |
||||
this.blockchain = blockchain |
||||
this.dropdownLogic = dropdownLogic |
||||
this.logCallback = logCallback |
||||
this.runView = runView |
||||
this.event = new EventManager() |
||||
|
||||
this.listenToEvents() |
||||
this.ipfsCheckedState = false |
||||
this.exEnvironment = blockchain.getProvider() |
||||
this.listenToContextChange() |
||||
this.loadType = 'other' |
||||
} |
||||
|
||||
listenToEvents () { |
||||
this.dropdownLogic.event.register('newlyCompiled', (success, data, source, compiler, compilerFullName, file) => { |
||||
if (!this.selectContractNames) return |
||||
this.selectContractNames.innerHTML = '' |
||||
if (success) { |
||||
this.dropdownLogic.getCompiledContracts(compiler, compilerFullName).forEach((contract) => { |
||||
this.selectContractNames.appendChild(yo`<option value="${contract.name}" compiler="${compilerFullName}">${contract.name} - ${contract.file}</option>`) |
||||
}) |
||||
} |
||||
this.enableAtAddress(success) |
||||
this.enableContractNames(success) |
||||
this.setInputParamsPlaceHolder() |
||||
|
||||
if (success) { |
||||
this.compFails.style.display = 'none' |
||||
} else { |
||||
this.compFails.style.display = 'block' |
||||
} |
||||
}) |
||||
} |
||||
|
||||
listenToContextChange () { |
||||
this.blockchain.event.register('networkStatus', ({ error, network }) => { |
||||
if (error) { |
||||
console.log('can\'t detect network') |
||||
return |
||||
} |
||||
this.exEnvironment = this.blockchain.getProvider() |
||||
this.networkName = network.name |
||||
this.networkId = network.id |
||||
|
||||
const savedConfig = window.localStorage.getItem(`ipfs/${this.exEnvironment}/${this.networkName}`) |
||||
|
||||
// check if an already selected option exist else use default workflow
|
||||
if (savedConfig !== null) { |
||||
this.setCheckedState(savedConfig) |
||||
} else { |
||||
this.setCheckedState(this.networkName === 'Main') |
||||
} |
||||
}) |
||||
} |
||||
|
||||
setCheckedState (value) { |
||||
value = value === 'true' ? true : value === 'false' ? false : value |
||||
this.ipfsCheckedState = value |
||||
if (this.ipfsCheckbox) this.ipfsCheckbox.checked = value |
||||
} |
||||
|
||||
toggleCheckedState () { |
||||
if (this.exEnvironment === 'vm') this.networkName = 'VM' |
||||
this.ipfsCheckedState = !this.ipfsCheckedState |
||||
window.localStorage.setItem(`ipfs/${this.exEnvironment}/${this.networkName}`, this.ipfsCheckedState) |
||||
} |
||||
|
||||
enableContractNames (enable) { |
||||
if (enable) { |
||||
if (this.selectContractNames.value === '') return |
||||
this.selectContractNames.removeAttribute('disabled') |
||||
this.selectContractNames.setAttribute('title', 'Select contract for Deploy or At Address.') |
||||
} else { |
||||
this.selectContractNames.setAttribute('disabled', true) |
||||
if (this.loadType === 'sol') { |
||||
this.selectContractNames.setAttribute('title', '⚠ Select and compile *.sol file to deploy or access a contract.') |
||||
} else { |
||||
this.selectContractNames.setAttribute('title', '⚠ Selected *.abi file allows accessing contracts, select and compile *.sol file to deploy and access one.') |
||||
} |
||||
} |
||||
} |
||||
|
||||
enableAtAddress (enable) { |
||||
if (enable) { |
||||
const address = this.atAddressButtonInput.value |
||||
if (!address || !ethJSUtil.isValidAddress(address)) { |
||||
this.enableAtAddress(false) |
||||
return |
||||
} |
||||
this.atAddress.removeAttribute('disabled') |
||||
this.atAddress.setAttribute('title', 'Interact with the given contract.') |
||||
} else { |
||||
this.atAddress.setAttribute('disabled', true) |
||||
if (this.atAddressButtonInput.value === '') { |
||||
this.atAddress.setAttribute('title', '⚠ Compile *.sol file or select *.abi file & then enter the address of deployed contract.') |
||||
} else { |
||||
this.atAddress.setAttribute('title', '⚠ Compile *.sol file or select *.abi file.') |
||||
} |
||||
} |
||||
} |
||||
|
||||
render () { |
||||
this.compFails = yo`<i title="No contract compiled yet or compilation failed. Please check the compile tab for more information." class="m-2 ml-3 fas fa-times-circle ${css.errorIcon}" ></i>` |
||||
this.atAddress = yo`<button class="${css.atAddress} btn btn-sm btn-info" id="runAndDeployAtAdressButton" onclick=${this.loadFromAddress.bind(this)}>At Address</button>` |
||||
this.atAddressButtonInput = yo`<input class="${css.input} ${css.ataddressinput} ataddressinput form-control" placeholder="Load contract from Address" title="address of contract" oninput=${this.atAddressChanged.bind(this)} />` |
||||
this.selectContractNames = yo`<select class="${css.contractNames} custom-select" disabled title="Please compile *.sol file to deploy or access a contract"></select>` |
||||
this.abiLabel = yo`<span class="py-1">ABI file selected</span>` |
||||
if (this.exEnvironment === 'vm') this.networkName = 'VM' |
||||
this.enableAtAddress(false) |
||||
this.abiLabel.style.display = 'none' |
||||
|
||||
const savedConfig = window.localStorage.getItem(`ipfs/${this.exEnvironment}/${this.networkName}`) |
||||
this.ipfsCheckedState = savedConfig === 'true' ? true : false // eslint-disable-line
|
||||
|
||||
this.ipfsCheckbox = yo` |
||||
<input |
||||
id="deployAndRunPublishToIPFS" |
||||
data-id="contractDropdownIpfsCheckbox" |
||||
class="form-check-input custom-control-input" |
||||
type="checkbox" |
||||
onchange=${() => this.toggleCheckedState()} |
||||
> |
||||
` |
||||
if (this.ipfsCheckedState) this.ipfsCheckbox.checked = true |
||||
|
||||
this.deployCheckBox = yo` |
||||
<div class="d-flex py-1 align-items-center custom-control custom-checkbox"> |
||||
${this.ipfsCheckbox} |
||||
<label |
||||
for="deployAndRunPublishToIPFS" |
||||
data-id="contractDropdownIpfsCheckboxLabel" |
||||
class="m-0 form-check-label custom-control-label ${css.checkboxAlign}" |
||||
title="Publishing the source code and metadata to IPFS facilitates source code verification using Sourcify and will greatly foster contract adoption (auditing, debugging, calling it, etc...)" |
||||
> |
||||
Publish to IPFS |
||||
</label> |
||||
</div> |
||||
` |
||||
this.createPanel = yo`<div class="${css.deployDropdown}"></div>` |
||||
this.orLabel = yo`<div class="${css.orLabel} mt-2">or</div>` |
||||
|
||||
const contractNamesContainer = yo` |
||||
<div class="${css.container}" data-id="contractDropdownContainer"> |
||||
<label class="${css.settingsLabel}">Contract</label> |
||||
<div class="${css.subcontainer}"> |
||||
${this.selectContractNames} ${this.compFails} |
||||
${this.abiLabel} |
||||
</div> |
||||
<div> |
||||
${this.createPanel} |
||||
${this.orLabel} |
||||
<div class="${css.button} ${css.atAddressSect}"> |
||||
${this.atAddress} |
||||
${this.atAddressButtonInput} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
` |
||||
this.selectContractNames.addEventListener('change', this.setInputParamsPlaceHolder.bind(this)) |
||||
this.setInputParamsPlaceHolder() |
||||
if (!this.contractNamesContainer) { |
||||
this.contractNamesContainer = contractNamesContainer |
||||
} |
||||
return contractNamesContainer |
||||
} |
||||
|
||||
atAddressChanged (event) { |
||||
if (!this.atAddressButtonInput.value) { |
||||
this.enableAtAddress(false) |
||||
} else { |
||||
if ((this.selectContractNames && !this.selectContractNames.getAttribute('disabled') && this.loadType === 'sol') || |
||||
this.loadType === 'abi') { |
||||
this.enableAtAddress(true) |
||||
} else { |
||||
this.enableAtAddress(false) |
||||
} |
||||
} |
||||
} |
||||
|
||||
changeCurrentFile (currentFile) { |
||||
if (!this.selectContractNames) return |
||||
if (/.(.abi)$/.exec(currentFile)) { |
||||
this.createPanel.style.display = 'none' |
||||
this.orLabel.style.display = 'none' |
||||
this.compFails.style.display = 'none' |
||||
this.loadType = 'abi' |
||||
this.contractNamesContainer.style.display = 'block' |
||||
this.abiLabel.style.display = 'block' |
||||
this.abiLabel.innerHTML = currentFile |
||||
this.selectContractNames.style.display = 'none' |
||||
this.enableContractNames(true) |
||||
this.enableAtAddress(true) |
||||
} else if (/.(.sol)$/.exec(currentFile) || |
||||
/.(.vy)$/.exec(currentFile) || // vyper
|
||||
/.(.lex)$/.exec(currentFile) || // lexon
|
||||
/.(.contract)$/.exec(currentFile)) { |
||||
this.createPanel.style.display = 'block' |
||||
this.orLabel.style.display = 'block' |
||||
this.contractNamesContainer.style.display = 'block' |
||||
this.loadType = 'sol' |
||||
this.selectContractNames.style.display = 'block' |
||||
this.abiLabel.style.display = 'none' |
||||
if (this.selectContractNames.value === '') this.enableAtAddress(false) |
||||
} else { |
||||
this.loadType = 'other' |
||||
this.createPanel.style.display = 'block' |
||||
this.orLabel.style.display = 'block' |
||||
this.contractNamesContainer.style.display = 'block' |
||||
this.selectContractNames.style.display = 'block' |
||||
this.abiLabel.style.display = 'none' |
||||
if (this.selectContractNames.value === '') this.enableAtAddress(false) |
||||
} |
||||
} |
||||
|
||||
setInputParamsPlaceHolder () { |
||||
this.createPanel.innerHTML = '' |
||||
if (this.selectContractNames.selectedIndex < 0 || this.selectContractNames.children.length <= 0) { |
||||
this.createPanel.innerHTML = 'No compiled contracts' |
||||
return |
||||
} |
||||
|
||||
const selectedContract = this.getSelectedContract() |
||||
const clickCallback = async (valArray, inputsValues) => { |
||||
var selectedContract = this.getSelectedContract() |
||||
this.createInstance(selectedContract, inputsValues) |
||||
} |
||||
const createConstructorInstance = new MultiParamManager( |
||||
0, |
||||
selectedContract.getConstructorInterface(), |
||||
clickCallback, |
||||
selectedContract.getConstructorInputs(), |
||||
'Deploy', |
||||
selectedContract.bytecodeObject, |
||||
true |
||||
) |
||||
this.createPanel.appendChild(createConstructorInstance.render()) |
||||
this.createPanel.appendChild(this.deployCheckBox) |
||||
} |
||||
|
||||
getSelectedContract () { |
||||
var contract = this.selectContractNames.children[this.selectContractNames.selectedIndex] |
||||
var contractName = contract.getAttribute('value') |
||||
var compilerAtributeName = contract.getAttribute('compiler') |
||||
|
||||
return this.dropdownLogic.getSelectedContract(contractName, compilerAtributeName) |
||||
} |
||||
|
||||
async createInstance (selectedContract, args) { |
||||
if (selectedContract.bytecodeObject.length === 0) { |
||||
return modalDialogCustom.alert('This contract may be abstract, not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.') |
||||
} |
||||
|
||||
var continueCb = (error, continueTxExecution, cancelCb) => { |
||||
if (error) { |
||||
var 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() |
||||
} |
||||
} |
||||
|
||||
const self = this |
||||
|
||||
var promptCb = (okCb, cancelCb) => { |
||||
modalDialogCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb) |
||||
} |
||||
|
||||
var statusCb = (msg) => { |
||||
return this.logCallback(msg) |
||||
} |
||||
|
||||
var finalCb = (error, contractObject, address) => { |
||||
self.event.trigger('clearInstance') |
||||
|
||||
if (error) { |
||||
return this.logCallback(error) |
||||
} |
||||
self.event.trigger('newContractInstanceAdded', [contractObject, address, contractObject.name]) |
||||
|
||||
const data = self.runView.compilersArtefacts.getCompilerAbstract(contractObject.contract.file) |
||||
self.runView.compilersArtefacts.addResolvedContract(helper.addressToString(address), data) |
||||
if (self.ipfsCheckedState) { |
||||
_paq.push(['trackEvent', 'udapp', 'DeployAndPublish', this.networkName + '_' + this.networkId]) |
||||
publishToStorage('ipfs', self.runView.fileProvider, self.runView.fileManager, selectedContract) |
||||
} else { |
||||
_paq.push(['trackEvent', 'udapp', 'DeployOnly', this.networkName + '_' + this.networkId]) |
||||
} |
||||
} |
||||
|
||||
let contractMetadata |
||||
try { |
||||
contractMetadata = await this.runView.call('compilerMetadata', 'deployMetadataOf', selectedContract.name, selectedContract.contract.file) |
||||
} catch (error) { |
||||
return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`) |
||||
} |
||||
|
||||
const compilerContracts = this.dropdownLogic.getCompilerContracts() |
||||
const confirmationCb = this.getConfirmationCb(modalDialog, confirmDialog) |
||||
|
||||
if (selectedContract.isOverSizeLimit()) { |
||||
return modalDialog('Contract code size over limit', yo`<div>Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails. <br>
|
||||
More info: <a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-170.md" target="_blank">eip-170</a> |
||||
</div>`, |
||||
{ |
||||
label: 'Force Send', |
||||
fn: () => { |
||||
this.deployContract(selectedContract, args, contractMetadata, compilerContracts, { continueCb, promptCb, statusCb, finalCb }, confirmationCb) |
||||
} |
||||
}, { |
||||
label: 'Cancel', |
||||
fn: () => { |
||||
this.logCallback(`creation of ${selectedContract.name} canceled by user.`) |
||||
} |
||||
}) |
||||
} |
||||
this.deployContract(selectedContract, args, contractMetadata, compilerContracts, { continueCb, promptCb, statusCb, finalCb }, confirmationCb) |
||||
} |
||||
|
||||
deployContract (selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) { |
||||
_paq.push(['trackEvent', 'udapp', 'DeployContractTo', this.networkName + '_' + this.networkId]) |
||||
const { statusCb } = callbacks |
||||
if (!contractMetadata || (contractMetadata && contractMetadata.autoDeployLib)) { |
||||
return this.blockchain.deployContractAndLibraries(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) |
||||
} |
||||
if (Object.keys(selectedContract.bytecodeLinkReferences).length) statusCb(`linking ${JSON.stringify(selectedContract.bytecodeLinkReferences, null, '\t')} using ${JSON.stringify(contractMetadata.linkReferences, null, '\t')}`) |
||||
this.blockchain.deployContractWithLibrary(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) |
||||
} |
||||
|
||||
getConfirmationCb (modalDialog, confirmDialog) { |
||||
// this code is the same as in recorder.js. TODO need to be refactored out
|
||||
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, network, amount, gasEstimation, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice.bind(this.blockchain)) |
||||
|
||||
modalDialog('Confirm transaction', content, |
||||
{ |
||||
label: 'Confirm', |
||||
fn: () => { |
||||
this.blockchain.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 transaction fee is not correct') |
||||
} else { |
||||
continueTxExecution(content.txFee) |
||||
} |
||||
} |
||||
}, { |
||||
label: 'Cancel', |
||||
fn: () => { |
||||
return cancelCb('Transaction canceled by user.') |
||||
} |
||||
} |
||||
) |
||||
} |
||||
|
||||
return confirmationCb |
||||
} |
||||
|
||||
loadFromAddress () { |
||||
this.event.trigger('clearInstance') |
||||
|
||||
let address = this.atAddressButtonInput.value |
||||
if (!ethJSUtil.isValidChecksumAddress(address)) { |
||||
addTooltip(yo` |
||||
<span> |
||||
It seems you are not using a checksumed address. |
||||
<br>A checksummed address is an address that contains uppercase letters, as specified in <a href="https://eips.ethereum.org/EIPS/eip-55" target="_blank">EIP-55</a>. |
||||
<br>Checksummed addresses are meant to help prevent users from sending transactions to the wrong address. |
||||
</span>`) |
||||
address = ethJSUtil.toChecksumAddress(address) |
||||
} |
||||
this.dropdownLogic.loadContractFromAddress(address, |
||||
(cb) => { |
||||
modalDialogCustom.confirm('At Address', `Do you really want to interact with ${address} using the current ABI definition?`, cb) |
||||
}, |
||||
(error, loadType, abi) => { |
||||
if (error) { |
||||
return modalDialogCustom.alert(error) |
||||
} |
||||
if (loadType === 'abi') { |
||||
return this.event.trigger('newContractABIAdded', [abi, address]) |
||||
} |
||||
var selectedContract = this.getSelectedContract() |
||||
this.event.trigger('newContractInstanceAdded', [selectedContract.object, address, this.selectContractNames.value]) |
||||
} |
||||
) |
||||
} |
||||
} |
||||
|
||||
module.exports = ContractDropdownUI |
@ -1,108 +0,0 @@ |
||||
import { CompilerAbstract } from '@remix-project/remix-solidity' |
||||
const remixLib = require('@remix-project/remix-lib') |
||||
const txHelper = remixLib.execution.txHelper |
||||
const EventManager = remixLib.EventManager |
||||
const _paq = window._paq = window._paq || [] |
||||
|
||||
class DropdownLogic { |
||||
constructor (compilersArtefacts, config, editor, runView) { |
||||
this.compilersArtefacts = compilersArtefacts |
||||
this.config = config |
||||
this.editor = editor |
||||
this.runView = runView |
||||
|
||||
this.event = new EventManager() |
||||
|
||||
this.listenToCompilationEvents() |
||||
} |
||||
|
||||
// TODO: can be moved up; the event in contractDropdown will have to refactored a method instead
|
||||
listenToCompilationEvents () { |
||||
const broadcastCompilationResult = (file, source, languageVersion, data) => { |
||||
// TODO check whether the tab is configured
|
||||
const compiler = new CompilerAbstract(languageVersion, data, source) |
||||
this.compilersArtefacts[languageVersion] = compiler |
||||
this.compilersArtefacts.__last = compiler |
||||
this.event.trigger('newlyCompiled', [true, data, source, compiler, languageVersion, file]) |
||||
} |
||||
this.runView.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => |
||||
broadcastCompilationResult(file, source, languageVersion, data) |
||||
) |
||||
this.runView.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => |
||||
broadcastCompilationResult(file, source, languageVersion, data) |
||||
) |
||||
this.runView.on('lexon', 'compilationFinished', (file, source, languageVersion, data) => |
||||
broadcastCompilationResult(file, source, languageVersion, data) |
||||
) |
||||
this.runView.on('yulp', 'compilationFinished', (file, source, languageVersion, data) => |
||||
broadcastCompilationResult(file, source, languageVersion, data) |
||||
) |
||||
this.runView.on('optimism-compiler', 'compilationFinished', (file, source, languageVersion, data) => |
||||
broadcastCompilationResult(file, source, languageVersion, data) |
||||
) |
||||
} |
||||
|
||||
loadContractFromAddress (address, confirmCb, cb) { |
||||
if (/.(.abi)$/.exec(this.config.get('currentFile'))) { |
||||
confirmCb(() => { |
||||
var abi |
||||
try { |
||||
abi = JSON.parse(this.editor.currentContent()) |
||||
} catch (e) { |
||||
return cb('Failed to parse the current file as JSON ABI.') |
||||
} |
||||
_paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithABI']) |
||||
cb(null, 'abi', abi) |
||||
}) |
||||
} else { |
||||
_paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithArtifacts']) |
||||
cb(null, 'instance') |
||||
} |
||||
} |
||||
|
||||
getCompiledContracts (compiler, compilerFullName) { |
||||
var contracts = [] |
||||
compiler.visitContracts((contract) => { |
||||
contracts.push(contract) |
||||
}) |
||||
return contracts |
||||
} |
||||
|
||||
getSelectedContract (contractName, compilerAtributeName) { |
||||
if (!contractName) return null |
||||
|
||||
var compiler = this.compilersArtefacts[compilerAtributeName] |
||||
if (!compiler) return null |
||||
|
||||
var contract = compiler.getContract(contractName) |
||||
|
||||
return { |
||||
name: contractName, |
||||
contract: contract, |
||||
compiler: compiler, |
||||
abi: contract.object.abi, |
||||
bytecodeObject: contract.object.evm.bytecode.object, |
||||
bytecodeLinkReferences: contract.object.evm.bytecode.linkReferences, |
||||
object: contract.object, |
||||
deployedBytecode: contract.object.evm.deployedBytecode, |
||||
getConstructorInterface: () => { |
||||
return txHelper.getConstructorInterface(contract.object.abi) |
||||
}, |
||||
getConstructorInputs: () => { |
||||
var constructorInteface = txHelper.getConstructorInterface(contract.object.abi) |
||||
return txHelper.inputParametersDeclarationToString(constructorInteface.inputs) |
||||
}, |
||||
isOverSizeLimit: () => { |
||||
var deployedBytecode = contract.object.evm.deployedBytecode |
||||
return (deployedBytecode && deployedBytecode.object.length / 2 > 24576) |
||||
}, |
||||
metadata: contract.object.metadata |
||||
} |
||||
} |
||||
|
||||
getCompilerContracts () { |
||||
return this.compilersArtefacts.__last.getData().contracts |
||||
} |
||||
} |
||||
|
||||
module.exports = DropdownLogic |
@ -1,160 +0,0 @@ |
||||
import { Plugin } from '@remixproject/engine' |
||||
|
||||
import * as packageJson from '../../../../../../package.json' |
||||
var yo = require('yo-yo') |
||||
var remixLib = require('@remix-project/remix-lib') |
||||
var EventManager = remixLib.EventManager |
||||
var csjs = require('csjs-inject') |
||||
var css = require('../styles/run-tab-styles') |
||||
|
||||
var modalDialogCustom = require('../../ui/modal-dialog-custom') |
||||
var modalDialog = require('../../ui/modaldialog') |
||||
var confirmDialog = require('../../ui/confirmDialog') |
||||
|
||||
var helper = require('../../../lib/helper.js') |
||||
|
||||
const profile = { |
||||
name: 'recorder', |
||||
methods: ['runScenario'], |
||||
version: packageJson.version |
||||
} |
||||
|
||||
class RecorderUI extends Plugin { |
||||
constructor (blockchain, fileManager, recorder, logCallBack, config) { |
||||
super(profile) |
||||
this.fileManager = fileManager |
||||
this.blockchain = blockchain |
||||
this.recorder = recorder |
||||
this.logCallBack = logCallBack |
||||
this.config = config |
||||
this.event = new EventManager() |
||||
} |
||||
|
||||
render () { |
||||
var css2 = csjs` |
||||
.container {} |
||||
.runTxs {} |
||||
.recorder {} |
||||
` |
||||
|
||||
this.runButton = yo`<i class="fas fa-play runtransaction ${css2.runTxs} ${css.icon}" title="Run Transactions" aria-hidden="true"></i>` |
||||
this.recordButton = yo` |
||||
<i class="fas fa-save savetransaction ${css2.recorder} ${css.icon}" |
||||
onclick=${this.triggerRecordButton.bind(this)} title="Save Transactions" aria-hidden="true"> |
||||
</i>` |
||||
|
||||
this.runButton.onclick = () => { |
||||
const file = this.config.get('currentFile') |
||||
if (!file) return modalDialogCustom.alert('A scenario file has to be selected') |
||||
this.runScenario(file) |
||||
} |
||||
} |
||||
|
||||
runScenario (file) { |
||||
if (!file) return modalDialogCustom.alert('Unable to run scenerio, no specified scenario file') |
||||
var continueCb = (error, continueTxExecution, cancelCb) => { |
||||
if (error) { |
||||
var 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() |
||||
} |
||||
} |
||||
|
||||
var promptCb = (okCb, cancelCb) => { |
||||
modalDialogCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb) |
||||
} |
||||
|
||||
var alertCb = (msg) => { |
||||
modalDialogCustom.alert(msg) |
||||
} |
||||
|
||||
const confirmationCb = this.getConfirmationCb(modalDialog, confirmDialog) |
||||
|
||||
this.fileManager.readFile(file).then((json) => { |
||||
// TODO: there is still a UI dependency to remove here, it's still too coupled at this point to remove easily
|
||||
this.recorder.runScenario(json, continueCb, promptCb, alertCb, confirmationCb, this.logCallBack, (error, abi, address, contractName) => { |
||||
if (error) { |
||||
return modalDialogCustom.alert(error) |
||||
} |
||||
|
||||
this.event.trigger('newScenario', [abi, address, contractName]) |
||||
}) |
||||
}).catch((error) => modalDialogCustom.alert(error)) |
||||
} |
||||
|
||||
getConfirmationCb (modalDialog, confirmDialog) { |
||||
// this code is the same as in contractDropdown.js. TODO need to be refactored out
|
||||
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, network, amount, gasEstimation, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice.bind(this.blockchain)) |
||||
|
||||
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 transaction fee is not correct') |
||||
} else { |
||||
continueTxExecution(content.txFee) |
||||
} |
||||
} |
||||
}, { |
||||
label: 'Cancel', |
||||
fn: () => { |
||||
return cancelCb('Transaction canceled by user.') |
||||
} |
||||
} |
||||
) |
||||
} |
||||
|
||||
return confirmationCb |
||||
} |
||||
|
||||
triggerRecordButton () { |
||||
this.saveScenario( |
||||
(path, cb) => { |
||||
modalDialogCustom.prompt('Save transactions as scenario', 'Transactions will be saved in a file under ' + path, 'scenario.json', cb) |
||||
}, |
||||
(error) => { |
||||
if (error) return modalDialogCustom.alert(error) |
||||
} |
||||
) |
||||
} |
||||
|
||||
saveScenario (promptCb, cb) { |
||||
var txJSON = JSON.stringify(this.recorder.getAll(), null, 2) |
||||
var path = this.fileManager.currentPath() |
||||
promptCb(path, input => { |
||||
var fileProvider = this.fileManager.fileProviderOf(path) |
||||
if (!fileProvider) return |
||||
var newFile = path + '/' + input |
||||
helper.createNonClashingName(newFile, fileProvider, (error, newFile) => { |
||||
if (error) return cb('Failed to create file. ' + newFile + ' ' + error) |
||||
if (!fileProvider.set(newFile, txJSON)) return cb('Failed to create file ' + newFile) |
||||
this.fileManager.open(newFile) |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
module.exports = RecorderUI |
@ -1,449 +0,0 @@ |
||||
import { BN } from 'ethereumjs-util' |
||||
import Registry from '../../state/registry' |
||||
const $ = require('jquery') |
||||
const yo = require('yo-yo') |
||||
const remixLib = require('@remix-project/remix-lib') |
||||
const EventManager = remixLib.EventManager |
||||
const css = require('../styles/run-tab-styles') |
||||
const copyToClipboard = require('../../ui/copy-to-clipboard') |
||||
const modalDialogCustom = require('../../ui/modal-dialog-custom') |
||||
const addTooltip = require('../../ui/tooltip') |
||||
const helper = require('../../../lib/helper.js') |
||||
|
||||
class SettingsUI { |
||||
constructor (blockchain, networkModule) { |
||||
this.blockchain = blockchain |
||||
this.event = new EventManager() |
||||
this._components = {} |
||||
|
||||
this.blockchain.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => { |
||||
if (!lookupOnly) this.el.querySelector('#value').value = 0 |
||||
if (error) return |
||||
this.updateAccountBalances() |
||||
}) |
||||
this._components = { |
||||
registry: Registry.getInstance(), |
||||
networkModule: networkModule |
||||
} |
||||
this._components.registry = Registry.getInstance() |
||||
this._deps = { |
||||
config: this._components.registry.get('config').api |
||||
} |
||||
|
||||
this._deps.config.events.on('settings/personal-mode_changed', this.onPersonalChange.bind(this)) |
||||
|
||||
setInterval(() => { |
||||
this.updateAccountBalances() |
||||
}, 1000) |
||||
|
||||
this.accountListCallId = 0 |
||||
this.loadedAccounts = {} |
||||
} |
||||
|
||||
updateAccountBalances () { |
||||
if (!this.el) return |
||||
var accounts = $(this.el.querySelector('#txorigin')).children('option') |
||||
accounts.each((index, account) => { |
||||
this.blockchain.getBalanceInEther(account.value, (err, balance) => { |
||||
if (err) return |
||||
const updated = helper.shortenAddress(account.value, balance) |
||||
if (updated !== account.innerText) { // check if the balance has been updated and update UI accordingly.
|
||||
account.innerText = updated |
||||
} |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
validateInputKey (e) { |
||||
// preventing not numeric keys
|
||||
// preventing 000 case
|
||||
if (!helper.isNumeric(e.key) || |
||||
(e.key === '0' && !parseInt(this.el.querySelector('#value').value) && this.el.querySelector('#value').value.length > 0)) { |
||||
e.preventDefault() |
||||
e.stopImmediatePropagation() |
||||
} |
||||
} |
||||
|
||||
validateValue () { |
||||
const valueEl = this.el.querySelector('#value') |
||||
if (!valueEl.value) { |
||||
// assign 0 if given value is
|
||||
// - empty
|
||||
valueEl.value = 0 |
||||
return |
||||
} |
||||
|
||||
let v |
||||
try { |
||||
v = new BN(valueEl.value, 10) |
||||
valueEl.value = v.toString(10) |
||||
} catch (e) { |
||||
// assign 0 if given value is
|
||||
// - not valid (for ex 4345-54)
|
||||
// - contains only '0's (for ex 0000) copy past or edit
|
||||
valueEl.value = 0 |
||||
} |
||||
|
||||
// if giveen value is negative(possible with copy-pasting) set to 0
|
||||
if (v.lt(0)) valueEl.value = 0 |
||||
} |
||||
|
||||
render () { |
||||
this.netUI = yo`<span class="${css.network} badge badge-secondary"></span>` |
||||
|
||||
var environmentEl = yo` |
||||
<div class="${css.crow}"> |
||||
<label id="selectExEnv" class="${css.settingsLabel}"> |
||||
Environment |
||||
</label> |
||||
<div class="${css.environment}"> |
||||
<select id="selectExEnvOptions" data-id="settingsSelectEnvOptions" class="form-control ${css.select} custom-select"> |
||||
<option id="vm-mode-london" data-id="settingsVMLondonMode" |
||||
title="Execution environment does not connect to any node, everything is local and in memory only." |
||||
value="vm-london" name="executionContext" fork="london"> JavaScript VM (London) |
||||
</option> |
||||
<option id="vm-mode-berlin" data-id="settingsVMBerlinMode" |
||||
title="Execution environment does not connect to any node, everything is local and in memory only." |
||||
value="vm-berlin" name="executionContext" fork="berlin" > JavaScript VM (Berlin) |
||||
</option> |
||||
<option id="injected-mode" data-id="settingsInjectedMode" |
||||
title="Execution environment has been provided by Metamask or similar provider." |
||||
value="injected" name="executionContext"> Injected Web3 |
||||
</option> |
||||
<option id="web3-mode" data-id="settingsWeb3Mode" |
||||
title="Execution environment connects to node at localhost (or via IPC if available), transactions will be sent to the network and can cause loss of money or worse! |
||||
If this page is served via https and you access your node via http, it might not work. In this case, try cloning the repository and serving it via http." |
||||
value="web3" name="executionContext"> Web3 Provider |
||||
</option> |
||||
</select> |
||||
<a href="https://remix-ide.readthedocs.io/en/latest/run.html#run-setup" target="_blank"><i class="${css.infoDeployAction} ml-2 fas fa-info" title="check out docs to setup Environment"></i></a> |
||||
</div> |
||||
</div> |
||||
` |
||||
const networkEl = yo` |
||||
<div class="${css.crow}"> |
||||
<div class="${css.settingsLabel}"> |
||||
</div> |
||||
<div class="${css.environment}" data-id="settingsNetworkEnv"> |
||||
${this.netUI} |
||||
</div> |
||||
</div> |
||||
` |
||||
const accountEl = yo` |
||||
<div class="${css.crow}"> |
||||
<label class="${css.settingsLabel}"> |
||||
Account |
||||
<span id="remixRunPlusWraper" title="Create a new account" onload=${this.updatePlusButton.bind(this)}> |
||||
<i id="remixRunPlus" class="fas fa-plus-circle ${css.icon}" aria-hidden="true" onclick=${this.newAccount.bind(this)}"></i> |
||||
</span> |
||||
</label> |
||||
<div class="${css.account}"> |
||||
<select data-id="runTabSelectAccount" name="txorigin" class="form-control ${css.select} custom-select pr-4" id="txorigin"></select> |
||||
<div style="margin-left: -5px;">${copyToClipboard(() => document.querySelector('#runTabView #txorigin').value)}</div> |
||||
<i id="remixRunSignMsg" data-id="settingsRemixRunSignMsg" class="mx-1 fas fa-edit ${css.icon}" aria-hidden="true" onclick=${this.signMessage.bind(this)} title="Sign a message using this account key"></i> |
||||
</div> |
||||
</div> |
||||
` |
||||
|
||||
const gasPriceEl = yo` |
||||
<div class="${css.crow}"> |
||||
<label class="${css.settingsLabel}">Gas limit</label> |
||||
<input type="number" class="form-control ${css.gasNval} ${css.col2}" id="gasLimit" value="3000000"> |
||||
</div> |
||||
` |
||||
|
||||
const valueEl = yo` |
||||
<div class="${css.crow}"> |
||||
<label class="${css.settingsLabel}" data-id="remixDRValueLabel">Value</label> |
||||
<div class="${css.gasValueContainer}"> |
||||
<input |
||||
type="number" |
||||
min="0" |
||||
pattern="^[0-9]" |
||||
step="1" |
||||
class="form-control ${css.gasNval} ${css.col2}" |
||||
id="value" |
||||
data-id="dandrValue" |
||||
value="0" |
||||
title="Enter the value and choose the unit" |
||||
onkeypress=${(e) => this.validateInputKey(e)} |
||||
onchange=${() => this.validateValue()} |
||||
> |
||||
<select name="unit" class="form-control p-1 ${css.gasNvalUnit} ${css.col2_2} custom-select" id="unit"> |
||||
<option data-unit="wei">Wei</option> |
||||
<option data-unit="gwei">Gwei</option> |
||||
<option data-unit="finney">Finney</option> |
||||
<option data-unit="ether">Ether</option> |
||||
</select> |
||||
</div> |
||||
</div> |
||||
` |
||||
|
||||
const el = yo` |
||||
<div class="${css.settings}"> |
||||
${environmentEl} |
||||
${networkEl} |
||||
${accountEl} |
||||
${gasPriceEl} |
||||
${valueEl} |
||||
</div> |
||||
` |
||||
|
||||
var selectExEnv = environmentEl.querySelector('#selectExEnvOptions') |
||||
this.setDropdown(selectExEnv) |
||||
|
||||
this.blockchain.event.register('contextChanged', (context, silent) => { |
||||
this.setFinalContext() |
||||
}) |
||||
|
||||
this.blockchain.event.register('networkStatus', ({ error, network }) => { |
||||
if (error) { |
||||
this.netUI.innerHTML = 'can\'t detect network ' |
||||
return |
||||
} |
||||
const networkProvider = this._components.networkModule.getNetworkProvider.bind(this._components.networkModule) |
||||
this.netUI.innerHTML = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : '' |
||||
}) |
||||
|
||||
setInterval(() => { |
||||
this.fillAccountsList() |
||||
}, 1000) |
||||
|
||||
this.el = el |
||||
|
||||
this.fillAccountsList() |
||||
return el |
||||
} |
||||
|
||||
setDropdown (selectExEnv) { |
||||
this.selectExEnv = selectExEnv |
||||
|
||||
const addProvider = (network) => { |
||||
selectExEnv.appendChild(yo`<option
|
||||
title="provider name: ${network.name}" |
||||
value="${network.name}" |
||||
name="executionContext" |
||||
> |
||||
${network.name} |
||||
</option>`) |
||||
addTooltip(yo`<span><b>${network.name}</b> provider added</span>`) |
||||
} |
||||
|
||||
const removeProvider = (name) => { |
||||
var env = selectExEnv.querySelector(`option[value="${name}"]`) |
||||
if (env) { |
||||
selectExEnv.removeChild(env) |
||||
addTooltip(yo`<span><b>${name}</b> provider removed</span>`) |
||||
} |
||||
} |
||||
this.blockchain.event.register('addProvider', provider => addProvider(provider)) |
||||
this.blockchain.event.register('removeProvider', name => removeProvider(name)) |
||||
|
||||
selectExEnv.addEventListener('change', (event) => { |
||||
const provider = selectExEnv.options[selectExEnv.selectedIndex] |
||||
const fork = provider.getAttribute('fork') // can be undefined if connected to an external source (web3 provider / injected)
|
||||
let context = provider.value |
||||
context = context.startsWith('vm') ? 'vm' : context // context has to be 'vm', 'web3' or 'injected'
|
||||
this.setExecutionContext({ context, fork }) |
||||
}) |
||||
|
||||
selectExEnv.value = this._getProviderDropdownValue() |
||||
} |
||||
|
||||
setExecutionContext (context) { |
||||
this.blockchain.changeExecutionContext(context, () => { |
||||
modalDialogCustom.prompt('External node request', this.web3ProviderDialogBody(), 'http://127.0.0.1:8545', (target) => { |
||||
this.blockchain.setProviderFromEndpoint(target, context, (alertMsg) => { |
||||
if (alertMsg) addTooltip(alertMsg) |
||||
this.setFinalContext() |
||||
}) |
||||
}, this.setFinalContext.bind(this)) |
||||
}, (alertMsg) => { |
||||
addTooltip(alertMsg) |
||||
}, this.setFinalContext.bind(this)) |
||||
} |
||||
|
||||
web3ProviderDialogBody () { |
||||
const thePath = '<path/to/local/folder/for/test/chain>' |
||||
|
||||
return yo` |
||||
<div class=""> |
||||
Note: To use Geth & https://remix.ethereum.org, configure it to allow requests from Remix:(see <a href="https://geth.ethereum.org/docs/rpc/server" target="_blank">Geth Docs on rpc server</a>)
|
||||
<div class="border p-1">geth --http --http.corsdomain https://remix.ethereum.org</div>
|
||||
<br> |
||||
To run Remix & a local Geth test node, use this command: (see <a href="https://geth.ethereum.org/getting-started/dev-mode" target="_blank">Geth Docs on Dev mode</a>) |
||||
<div class="border p-1">geth --http --http.corsdomain="${window.origin}" --http.api web3,eth,debug,personal,net --vmdebug --datadir ${thePath} --dev console</div> |
||||
<br> |
||||
<br>
|
||||
<b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b> |
||||
<br> |
||||
<br>For more info: <a href="https://remix-ide.readthedocs.io/en/latest/run.html#more-about-web3-provider" target="_blank">Remix Docs on Web3 Provider</a> |
||||
<br> |
||||
<br>
|
||||
Web3 Provider Endpoint |
||||
</div> |
||||
` |
||||
} |
||||
|
||||
/** |
||||
* generate a value used by the env dropdown list. |
||||
* @return {String} - can return 'vm-berlin, 'vm-london', 'injected' or 'web3' |
||||
*/ |
||||
_getProviderDropdownValue () { |
||||
const provider = this.blockchain.getProvider() |
||||
const fork = this.blockchain.getCurrentFork() |
||||
return provider === 'vm' ? provider + '-' + fork : provider |
||||
} |
||||
|
||||
setFinalContext () { |
||||
// set the final context. Cause it is possible that this is not the one we've originaly selected
|
||||
this.selectExEnv.value = this._getProviderDropdownValue() |
||||
this.event.trigger('clearInstance', []) |
||||
this.updatePlusButton() |
||||
} |
||||
|
||||
updatePlusButton () { |
||||
// enable/disable + button
|
||||
const plusBtn = document.getElementById('remixRunPlus') |
||||
const plusTitle = document.getElementById('remixRunPlusWraper') |
||||
switch (this.selectExEnv.value) { |
||||
case 'injected': |
||||
plusBtn.classList.add(css.disableMouseEvents) |
||||
plusTitle.title = "Unfortunately it's not possible to create an account using injected web3. Please create the account directly from your provider (i.e metamask or other of the same type)." |
||||
|
||||
break |
||||
case 'vm': |
||||
plusBtn.classList.remove(css.disableMouseEvents) |
||||
plusTitle.title = 'Create a new account' |
||||
|
||||
break |
||||
|
||||
case 'web3': |
||||
this.onPersonalChange() |
||||
|
||||
break |
||||
default: { |
||||
plusBtn.classList.add(css.disableMouseEvents) |
||||
plusTitle.title = `Unfortunately it's not possible to create an account using an external wallet (${this.selectExEnv.value}).` |
||||
} |
||||
} |
||||
} |
||||
|
||||
onPersonalChange () { |
||||
const plusBtn = document.getElementById('remixRunPlus') |
||||
const plusTitle = document.getElementById('remixRunPlusWraper') |
||||
if (!this._deps.config.get('settings/personal-mode')) { |
||||
plusBtn.classList.add(css.disableMouseEvents) |
||||
plusTitle.title = 'Creating an account is possible only in Personal mode. Please go to Settings to enable it.' |
||||
} else { |
||||
plusBtn.classList.remove(css.disableMouseEvents) |
||||
plusTitle.title = 'Create a new account' |
||||
} |
||||
} |
||||
|
||||
newAccount () { |
||||
this.blockchain.newAccount( |
||||
'', |
||||
(cb) => { |
||||
modalDialogCustom.promptPassphraseCreation((error, passphrase) => { |
||||
if (error) { |
||||
return modalDialogCustom.alert(error) |
||||
} |
||||
cb(passphrase) |
||||
}, () => {}) |
||||
}, |
||||
(error, address) => { |
||||
if (error) { |
||||
return addTooltip('Cannot create an account: ' + error) |
||||
} |
||||
addTooltip(`account ${address} created`) |
||||
} |
||||
) |
||||
} |
||||
|
||||
getSelectedAccount () { |
||||
return this.el.querySelector('#txorigin').selectedOptions[0].value |
||||
} |
||||
|
||||
getEnvironment () { |
||||
return this.blockchain.getProvider() |
||||
} |
||||
|
||||
signMessage () { |
||||
this.blockchain.getAccounts((err, accounts) => { |
||||
if (err) { |
||||
return addTooltip(`Cannot get account list: ${err}`) |
||||
} |
||||
|
||||
var signMessageDialog = { title: 'Sign a message', text: 'Enter a message to sign', inputvalue: 'Message to sign' } |
||||
var $txOrigin = this.el.querySelector('#txorigin') |
||||
if (!$txOrigin.selectedOptions[0] && (this.blockchain.isInjectedWeb3() || this.blockchain.isWeb3Provider())) { |
||||
return addTooltip('Account list is empty, please make sure the current provider is properly connected to remix') |
||||
} |
||||
|
||||
var account = $txOrigin.selectedOptions[0].value |
||||
|
||||
var promptCb = (passphrase) => { |
||||
const modal = modalDialogCustom.promptMulti(signMessageDialog, (message) => { |
||||
this.blockchain.signMessage(message, account, passphrase, (err, msgHash, signedData) => { |
||||
if (err) { |
||||
return addTooltip(err) |
||||
} |
||||
modal.hide() |
||||
modalDialogCustom.alert(yo` |
||||
<div> |
||||
<b>hash:</b><br> |
||||
<span id="remixRunSignMsgHash" data-id="settingsRemixRunSignMsgHash">${msgHash}</span> |
||||
<br><b>signature:</b><br> |
||||
<span id="remixRunSignMsgSignature" data-id="settingsRemixRunSignMsgSignature">${signedData}</span> |
||||
</div> |
||||
`)
|
||||
}) |
||||
}, false) |
||||
} |
||||
|
||||
if (this.blockchain.isWeb3Provider()) { |
||||
return modalDialogCustom.promptPassphrase( |
||||
'Passphrase to sign a message', |
||||
'Enter your passphrase for this account to sign the message', |
||||
'', |
||||
promptCb, |
||||
false |
||||
) |
||||
} |
||||
promptCb() |
||||
}) |
||||
} |
||||
|
||||
// TODO: unclear what's the goal of accountListCallId, feels like it can be simplified
|
||||
async fillAccountsList () { |
||||
this.accountListCallId++ |
||||
const callid = this.accountListCallId |
||||
const txOrigin = this.el.querySelector('#txorigin') |
||||
let accounts = [] |
||||
try { |
||||
accounts = await this.blockchain.getAccounts() |
||||
} catch (e) { |
||||
addTooltip(`Cannot get account list: ${e}`) |
||||
} |
||||
if (!accounts) accounts = [] |
||||
if (this.accountListCallId > callid) return |
||||
this.accountListCallId++ |
||||
for (const loadedaddress in this.loadedAccounts) { |
||||
if (accounts.indexOf(loadedaddress) === -1) { |
||||
txOrigin.removeChild(txOrigin.querySelector('option[value="' + loadedaddress + '"]')) |
||||
delete this.loadedAccounts[loadedaddress] |
||||
} |
||||
} |
||||
for (const i in accounts) { |
||||
const address = accounts[i] |
||||
if (!this.loadedAccounts[address]) { |
||||
txOrigin.appendChild(yo`<option value="${address}" >${address}</option>`) |
||||
this.loadedAccounts[address] = 1 |
||||
} |
||||
} |
||||
txOrigin.setAttribute('value', accounts[0]) |
||||
} |
||||
} |
||||
|
||||
module.exports = SettingsUI |
@ -1,225 +0,0 @@ |
||||
var csjs = require('csjs-inject') |
||||
|
||||
var css = csjs` |
||||
.runTabView { |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
.runTabView::-webkit-scrollbar { |
||||
display: none; |
||||
} |
||||
.settings { |
||||
padding: 0 24px 16px; |
||||
} |
||||
.crow { |
||||
display: block; |
||||
margin-top: 8px; |
||||
} |
||||
.col1 { |
||||
width: 30%; |
||||
float: left; |
||||
align-self: center; |
||||
} |
||||
.settingsLabel { |
||||
font-size: 11px; |
||||
margin-bottom: 4px; |
||||
text-transform: uppercase; |
||||
} |
||||
.environment { |
||||
display: flex; |
||||
align-items: center; |
||||
position: relative; |
||||
width: 100%; |
||||
} |
||||
.environment a { |
||||
margin-left: 7px; |
||||
} |
||||
.account { |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
.account i { |
||||
margin-left: 12px; |
||||
} |
||||
.col2 { |
||||
border-radius: 3px; |
||||
} |
||||
.col2_1 { |
||||
width: 164px; |
||||
min-width: 164px; |
||||
} |
||||
.col2_2 { |
||||
} |
||||
.select { |
||||
font-weight: normal; |
||||
width: 100%; |
||||
overflow: hidden; |
||||
} |
||||
.instanceContainer { |
||||
display: flex; |
||||
flex-direction: column; |
||||
margin-bottom: 2%; |
||||
border: none; |
||||
text-align: center; |
||||
padding: 0 14px 16px; |
||||
} |
||||
.pendingTxsContainer { |
||||
display: flex; |
||||
flex-direction: column; |
||||
margin-top: 2%; |
||||
border: none; |
||||
text-align: center; |
||||
} |
||||
.container { |
||||
padding: 0 24px 16px; |
||||
} |
||||
.recorderDescription { |
||||
margin: 0 15px 15px 0; |
||||
} |
||||
.contractNames { |
||||
width: 100%; |
||||
border: 1px solid |
||||
} |
||||
.subcontainer { |
||||
display: flex; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
margin-bottom: 8px; |
||||
} |
||||
.subcontainer i { |
||||
width: 16px; |
||||
display: flex; |
||||
justify-content: center; |
||||
margin-left: 1px; |
||||
} |
||||
.button button{ |
||||
flex: none; |
||||
} |
||||
.button { |
||||
display: flex; |
||||
align-items: center; |
||||
margin-top: 13px; |
||||
} |
||||
.transaction { |
||||
} |
||||
.atAddress { |
||||
margin: 0; |
||||
min-width: 100px; |
||||
width: 100px; |
||||
height: 100%; |
||||
word-break: inherit; |
||||
border-top-right-radius: 0; |
||||
border-bottom-right-radius: 0; |
||||
border-right: 0; |
||||
} |
||||
.atAddressSect { |
||||
margin-top: 8px; |
||||
height: 32px; |
||||
} |
||||
.atAddressSect input { |
||||
height: 32px; |
||||
border-top-left-radius: 0 !important; |
||||
border-bottom-left-radius: 0 !important; |
||||
} |
||||
.ataddressinput { |
||||
padding: .25rem; |
||||
} |
||||
.create { |
||||
} |
||||
.input { |
||||
font-size: 10px !important; |
||||
} |
||||
.noInstancesText { |
||||
font-style: italic; |
||||
text-align: left; |
||||
padding-left: 15px; |
||||
} |
||||
.pendingTxsText { |
||||
font-style: italic; |
||||
display: flex; |
||||
justify-content: space-evenly; |
||||
align-items: center; |
||||
flex-wrap: wrap; |
||||
} |
||||
.item { |
||||
margin-right: 1em; |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
.pendingContainer { |
||||
display: flex; |
||||
align-items: baseline; |
||||
} |
||||
.pending { |
||||
height: 25px; |
||||
text-align: center; |
||||
padding-left: 10px; |
||||
border-radius: 3px; |
||||
margin-left: 5px; |
||||
} |
||||
.disableMouseEvents { |
||||
pointer-events: none; |
||||
} |
||||
.icon { |
||||
cursor: pointer; |
||||
font-size: 12px; |
||||
cursor: pointer; |
||||
margin-left: 5px; |
||||
} |
||||
.icon:hover { |
||||
font-size: 12px; |
||||
color: var(--warning); |
||||
} |
||||
.errorIcon { |
||||
color: var(--warning); |
||||
margin-left: 15px; |
||||
} |
||||
.failDesc { |
||||
color: var(--warning); |
||||
padding-left: 10px; |
||||
display: inline; |
||||
} |
||||
.network { |
||||
margin-left: 8px; |
||||
pointer-events: none; |
||||
} |
||||
.networkItem { |
||||
margin-right: 5px; |
||||
} |
||||
.transactionActions { |
||||
display: flex; |
||||
justify-content: space-evenly; |
||||
width: 145px; |
||||
} |
||||
.orLabel { |
||||
text-align: center; |
||||
text-transform: uppercase; |
||||
} |
||||
.infoDeployAction { |
||||
margin-left: 1px; |
||||
font-size: 13px; |
||||
color: var(--info); |
||||
} |
||||
.gasValueContainer { |
||||
flex-direction: row; |
||||
display: flex; |
||||
} |
||||
.gasNval { |
||||
width: 55%; |
||||
font-size: 0.8rem; |
||||
} |
||||
.gasNvalUnit { |
||||
width: 41%; |
||||
margin-left: 10px; |
||||
font-size: 0.8rem; |
||||
} |
||||
.deployDropdown { |
||||
text-align: center; |
||||
text-transform: uppercase; |
||||
} |
||||
.checkboxAlign { |
||||
padding-top: 2px; |
||||
} |
||||
` |
||||
|
||||
module.exports = css |
@ -1,212 +0,0 @@ |
||||
'use strict' |
||||
var yo = require('yo-yo') |
||||
var csjs = require('csjs-inject') |
||||
var css = csjs` |
||||
.li_tv { |
||||
list-style-type: none; |
||||
-webkit-margin-before: 0px; |
||||
-webkit-margin-after: 0px; |
||||
-webkit-margin-start: 0px; |
||||
-webkit-margin-end: 0px; |
||||
-webkit-padding-start: 0px; |
||||
} |
||||
.ul_tv { |
||||
list-style-type: none; |
||||
-webkit-margin-before: 0px; |
||||
-webkit-margin-after: 0px; |
||||
-webkit-margin-start: 0px; |
||||
-webkit-margin-end: 0px; |
||||
-webkit-padding-start: 0px; |
||||
} |
||||
.caret_tv { |
||||
width: 10px; |
||||
flex-shrink: 0; |
||||
padding-right: 5px; |
||||
} |
||||
.label_item { |
||||
word-break: break-all; |
||||
} |
||||
.label_key { |
||||
min-width: max-content; |
||||
max-width: 80%; |
||||
word-break: break-word; |
||||
} |
||||
.label_value { |
||||
min-width: 10%; |
||||
} |
||||
.cursor_pointer { |
||||
cursor: pointer; |
||||
} |
||||
` |
||||
|
||||
var EventManager = require('../../lib/events') |
||||
|
||||
/** |
||||
* TreeView |
||||
* - extendable by specifying custom `extractData` and `formatSelf` function |
||||
* - trigger `nodeClick` and `leafClick` |
||||
*/ |
||||
class TreeView { |
||||
constructor (opts) { |
||||
this.event = new EventManager() |
||||
this.extractData = opts.extractData || this.extractDataDefault |
||||
this.formatSelf = opts.formatSelf || this.formatSelfDefault |
||||
this.loadMore = opts.loadMore |
||||
this.view = null |
||||
this.expandPath = [] |
||||
} |
||||
|
||||
render (json, expand) { |
||||
var view = this.renderProperties(json, expand) |
||||
if (!this.view) { |
||||
this.view = view |
||||
} |
||||
return view |
||||
} |
||||
|
||||
update (json) { |
||||
if (this.view) { |
||||
yo.update(this.view, this.render(json)) |
||||
} |
||||
} |
||||
|
||||
renderObject (item, parent, key, expand, keyPath) { |
||||
var data = this.extractData(item, parent, key) |
||||
var children = (data.children || []).map((child, index) => { |
||||
return this.renderObject(child.value, data, child.key, expand, keyPath + '/' + child.key) |
||||
}) |
||||
return this.formatData(key, data, children, expand, keyPath) |
||||
} |
||||
|
||||
renderProperties (json, expand, key) { |
||||
key = key || '' |
||||
var children = Object.keys(json).map((innerkey) => { |
||||
return this.renderObject(json[innerkey], json, innerkey, expand, innerkey) |
||||
}) |
||||
return yo`<ul key=${key} data-id="treeViewUl${key}" class="${css.ul_tv} ml-0 px-2">${children}</ul>` |
||||
} |
||||
|
||||
formatData (key, data, children, expand, keyPath) { |
||||
var self = this |
||||
var li = yo`<li key=${keyPath} data-id="treeViewLi${keyPath}" class=${css.li_tv}></li>` |
||||
var caret = yo`<div class="px-1 fas fa-caret-right caret ${css.caret_tv}"></div>` |
||||
var label = yo` |
||||
<div key=${keyPath} data-id="treeViewDiv${keyPath}" class="d-flex flex-row align-items-center"> |
||||
${caret} |
||||
<span class="w-100">${self.formatSelf(key, data, li)}</span> |
||||
</div>` |
||||
const expanded = self.expandPath.includes(keyPath) |
||||
li.appendChild(label) |
||||
if (data.children) { |
||||
var list = yo`<ul key=${keyPath} data-id="treeViewUlList${keyPath}" class="pl-2 ${css.ul_tv}">${children}</ul>` |
||||
list.style.display = expanded ? 'block' : 'none' |
||||
caret.className = list.style.display === 'none' ? `fas fa-caret-right caret ${css.caret_tv}` : `fas fa-caret-down caret ${css.caret_tv}` |
||||
caret.setAttribute('data-id', `treeViewToggle${keyPath}`) |
||||
label.onclick = function () { |
||||
self.expand(keyPath) |
||||
if (self.isExpanded(keyPath)) { |
||||
if (!self.expandPath.includes(keyPath)) self.expandPath.push(keyPath) |
||||
} else { |
||||
self.expandPath = self.expandPath.filter(path => !path.startsWith(keyPath)) |
||||
} |
||||
} |
||||
label.oncontextmenu = function (event) { |
||||
self.event.trigger('nodeRightClick', [keyPath, data, label, event]) |
||||
} |
||||
li.appendChild(list) |
||||
if (data.hasNext) { |
||||
list.appendChild(yo`<li><span class="w-100 text-primary ${css.cursor_pointer}" data-id="treeViewLoadMore" onclick="${() => self.loadMore(data.cursor)}">Load more</span></li>`) |
||||
} |
||||
} else { |
||||
caret.style.visibility = 'hidden' |
||||
label.oncontextmenu = function (event) { |
||||
self.event.trigger('leafRightClick', [keyPath, data, label, event]) |
||||
} |
||||
label.onclick = function (event) { |
||||
self.event.trigger('leafClick', [keyPath, data, label, event]) |
||||
} |
||||
} |
||||
return li |
||||
} |
||||
|
||||
isExpanded (path) { |
||||
var current = this.nodeAt(path) |
||||
if (current) { |
||||
return current.style.display !== 'none' |
||||
} |
||||
return false |
||||
} |
||||
|
||||
expand (path) { |
||||
var caret = this.caretAt(path) |
||||
var node = this.nodeAt(path) |
||||
if (node) { |
||||
node.style.display = node.style.display === 'none' ? 'block' : 'none' |
||||
caret.className = node.style.display === 'none' ? `fas fa-caret-right caret ${css.caret_tv}` : `fas fa-caret-down caret ${css.caret_tv}` |
||||
this.event.trigger('nodeClick', [path, node]) |
||||
} |
||||
} |
||||
|
||||
caretAt (path) { |
||||
var label = this.labelAt(path) |
||||
if (label) { |
||||
return label.querySelector('.caret') |
||||
} |
||||
} |
||||
|
||||
itemAt (path) { |
||||
return this.view.querySelector(`li[key="${path}"]`) |
||||
} |
||||
|
||||
labelAt (path) { |
||||
return this.view.querySelector(`div[key="${path}"]`) |
||||
} |
||||
|
||||
nodeAt (path) { |
||||
return this.view.querySelector(`ul[key="${path}"]`) |
||||
} |
||||
|
||||
updateNodeFromJSON (path, jsonTree, expand) { |
||||
var newTree = this.renderProperties(jsonTree, expand, path) |
||||
var current = this.nodeAt(path) |
||||
if (current && current.parentElement) { |
||||
current.parentElement.replaceChild(newTree, current) |
||||
} |
||||
} |
||||
|
||||
formatSelfDefault (key, data) { |
||||
return yo` |
||||
<div class="d-flex mt-2 flex-row ${css.label_item}"> |
||||
<label class="small font-weight-bold pr-1 ${css.label_key}">${key}:</label> |
||||
<label class="m-0 ${css.label_value}">${data.self}</label> |
||||
</div> |
||||
` |
||||
} |
||||
|
||||
extractDataDefault (item, parent, key) { |
||||
var ret = {} |
||||
if (item instanceof Array) { |
||||
ret.children = item.map((item, index) => { |
||||
return { key: index, value: item } |
||||
}) |
||||
ret.self = 'Array' |
||||
ret.isNode = true |
||||
ret.isLeaf = false |
||||
} else if (item instanceof Object) { |
||||
ret.children = Object.keys(item).map((key) => { |
||||
return { key: key, value: item[key] } |
||||
}) |
||||
ret.self = 'Object' |
||||
ret.isNode = true |
||||
ret.isLeaf = false |
||||
} else { |
||||
ret.self = item |
||||
ret.children = null |
||||
ret.isNode = false |
||||
ret.isLeaf = true |
||||
} |
||||
return ret |
||||
} |
||||
} |
||||
|
||||
module.exports = TreeView |
@ -1,142 +0,0 @@ |
||||
var yo = require('yo-yo') |
||||
var csjs = require('csjs-inject') |
||||
const copyToClipboard = require('./copy-to-clipboard') |
||||
const Web3 = require('web3') |
||||
|
||||
var css = csjs` |
||||
#confirmsetting { |
||||
z-index: 1; |
||||
} |
||||
.txInfoBox { |
||||
} |
||||
.wrapword { |
||||
white-space: pre-wrap; /* Since CSS 2.1 */ |
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ |
||||
white-space: -pre-wrap; /* Opera 4-6 */ |
||||
white-space: -o-pre-wrap; /* Opera 7 */ |
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */ |
||||
} |
||||
` |
||||
|
||||
function confirmDialog (tx, network, amount, gasEstimation, newGasPriceCb, initialParamsCb) { |
||||
const onGasPriceChange = function () { |
||||
var gasPrice = el.querySelector('#gasprice').value |
||||
newGasPriceCb(gasPrice, (txFeeText, priceStatus) => { |
||||
el.querySelector('#txfee').innerHTML = txFeeText |
||||
el.gasPriceStatus = priceStatus |
||||
el.txFee = { gasPrice } |
||||
}) |
||||
} |
||||
|
||||
const onMaxFeeChange = function () { |
||||
var maxFee = el.querySelector('#maxfee').value |
||||
var confirmBtn = document.querySelector('#modal-footer-ok') |
||||
var maxPriorityFee = el.querySelector('#maxpriorityfee').value |
||||
if (parseInt(network.lastBlock.baseFeePerGas, 16) > Web3.utils.toWei(maxFee, 'Gwei')) { |
||||
el.querySelector('#txfee').innerHTML = 'Transaction is invalid. Max fee should not be less than Base fee' |
||||
el.gasPriceStatus = false |
||||
confirmBtn.hidden = true |
||||
return |
||||
} else { |
||||
el.gasPriceStatus = true |
||||
confirmBtn.hidden = false |
||||
} |
||||
|
||||
newGasPriceCb(maxFee, (txFeeText, priceStatus) => { |
||||
el.querySelector('#txfee').innerHTML = txFeeText |
||||
if (priceStatus) { |
||||
confirmBtn.hidden = false |
||||
} else { |
||||
confirmBtn.hidden = true |
||||
} |
||||
el.gasPriceStatus = priceStatus |
||||
el.txFee = { maxFee, maxPriorityFee, baseFeePerGas: network.lastBlock.baseFeePerGas } |
||||
}) |
||||
} |
||||
|
||||
const el = yo` |
||||
<div> |
||||
<div class="text-dark">You are about to create a transaction on ${network.name} Network. Confirm the details to send the info to your provider. |
||||
<br>The provider for many users is MetaMask. The provider will ask you to sign the transaction before it is sent to ${network.name} Network. |
||||
</div> |
||||
<div class="mt-3 ${css.txInfoBox}"> |
||||
<div> |
||||
<span class="text-dark mr-2">From:</span> |
||||
<span>${tx.from}</span> |
||||
</div> |
||||
<div> |
||||
<span class="text-dark mr-2">To:</span> |
||||
<span>${tx.to ? tx.to : '(Contract Creation)'}</span> |
||||
</div> |
||||
<div class="d-flex align-items-center"> |
||||
<span class="text-dark mr-2">Data:</span> |
||||
<pre class="${css.wrapword} mb-0">${tx.data && tx.data.length > 50 ? tx.data.substring(0, 49) + '...' : tx.data} ${copyToClipboard(() => { return tx.data })}</pre> |
||||
</div> |
||||
<div class="mb-3"> |
||||
<span class="text-dark mr-2">Amount:</span> |
||||
<span>${amount} Ether</span> |
||||
</div> |
||||
<div> |
||||
<span class="text-dark mr-2">Gas estimation:</span> |
||||
<span>${gasEstimation}</span> |
||||
</div> |
||||
<div> |
||||
<span class="text-dark mr-2">Gas limit:</span> |
||||
<span>${tx.gas}</span> |
||||
</div> |
||||
${ |
||||
network.lastBlock.baseFeePerGas ? yo` |
||||
<div class="align-items-center my-1" title="Represents the part of the tx fee that goes to the miner."> |
||||
<div class='d-flex'> |
||||
<span class="text-dark mr-2 text-nowrap">Max Priority fee:</span> |
||||
<input class="form-control mr-1 text-right" style='height: 1.2rem; width: 6rem;' value="1" id='maxpriorityfee' /> |
||||
<span title="visit https://ethgasstation.info for current gas price info.">Gwei</span> |
||||
</div> |
||||
</div> |
||||
<div class="align-items-center my-1" title="Represents the maximum amount of fee that you will pay for this transaction. The minimun needs to be set to base fee."> |
||||
<div class='d-flex'> |
||||
<span class="text-dark mr-2 text-nowrap">Max fee (Not less than base fee ${Web3.utils.fromWei(Web3.utils.toBN(parseInt(network.lastBlock.baseFeePerGas, 16)), 'Gwei')} Gwei):</span> |
||||
<input class="form-control mr-1 text-right" style='height: 1.2rem; width: 6rem;' id='maxfee' oninput=${onMaxFeeChange} /> |
||||
<span>Gwei</span> |
||||
<span class="text-dark ml-2"></span> |
||||
</div> |
||||
</div>` |
||||
: yo`<div class="d-flex align-items-center my-1">
|
||||
<span class="text-dark mr-2 text-nowrap">Gas price:</span> |
||||
<input class="form-control mr-1 text-right" style='width: 40px; height: 28px;' id='gasprice' oninput=${onGasPriceChange} /> |
||||
<span>Gwei (visit <a target='_blank' href='https://ethgasstation.info'>ethgasstation.info</a> for current gas price info.)</span> |
||||
</div>` |
||||
} |
||||
<div class="mb-3"> |
||||
<span class="text-dark mr-2">Max transaction fee:</span> |
||||
<span class="text-warning" id='txfee'></span> |
||||
</div> |
||||
</div> |
||||
<div class="d-flex py-1 align-items-center custom-control custom-checkbox ${css.checkbox}"> |
||||
<input class="form-check-input custom-control-input" id="confirmsetting" type="checkbox"> |
||||
<label class="m-0 form-check-label custom-control-label" for="confirmsetting">Do not show this warning again.</label> |
||||
</div> |
||||
</div> |
||||
` |
||||
|
||||
initialParamsCb((txFeeText, gasPriceValue, gasPriceStatus) => { |
||||
if (txFeeText) { |
||||
el.querySelector('#txfee').innerHTML = txFeeText |
||||
} |
||||
if (el.querySelector('#gasprice') && gasPriceValue) { |
||||
el.querySelector('#gasprice').value = gasPriceValue |
||||
onGasPriceChange() |
||||
} |
||||
if (el.querySelector('#maxfee') && network && network.lastBlock && network.lastBlock.baseFeePerGas) { |
||||
el.querySelector('#maxfee').value = Web3.utils.fromWei(Web3.utils.toBN(parseInt(network.lastBlock.baseFeePerGas, 16)), 'Gwei') |
||||
onMaxFeeChange() |
||||
} |
||||
if (gasPriceStatus !== undefined) { |
||||
el.gasPriceStatus = gasPriceStatus |
||||
} |
||||
}) |
||||
|
||||
return el |
||||
} |
||||
|
||||
module.exports = confirmDialog |
@ -1,228 +0,0 @@ |
||||
'use strict' |
||||
|
||||
var yo = require('yo-yo') |
||||
var css = require('../../universal-dapp-styles') |
||||
var copyToClipboard = require('./copy-to-clipboard') |
||||
var remixLib = require('@remix-project/remix-lib') |
||||
var txFormat = remixLib.execution.txFormat |
||||
|
||||
class MultiParamManager { |
||||
/** |
||||
* |
||||
* @param {bool} lookupOnly |
||||
* @param {Object} funABI |
||||
* @param {Function} clickMultiCallBack |
||||
* @param {string} inputs |
||||
* @param {string} title |
||||
* @param {string} evmBC |
||||
* |
||||
*/ |
||||
constructor (lookupOnly, funABI, clickCallBack, inputs, title, evmBC, isDeploy) { |
||||
this.lookupOnly = lookupOnly |
||||
this.funABI = funABI |
||||
this.clickCallBack = clickCallBack |
||||
this.inputs = inputs |
||||
this.title = title |
||||
this.evmBC = evmBC |
||||
this.basicInputField = null |
||||
this.multiFields = null |
||||
this.isDeploy = isDeploy |
||||
} |
||||
|
||||
switchMethodViewOn () { |
||||
this.contractActionsContainerSingle.style.display = 'none' |
||||
this.contractActionsContainerMulti.style.display = 'flex' |
||||
this.makeMultiVal() |
||||
} |
||||
|
||||
switchMethodViewOff () { |
||||
this.contractActionsContainerSingle.style.display = 'flex' |
||||
this.contractActionsContainerMulti.style.display = 'none' |
||||
var multiValString = this.getMultiValsString() |
||||
if (multiValString) this.basicInputField.value = multiValString |
||||
} |
||||
|
||||
getValue (item, index) { |
||||
var valStr = item.value.join('') |
||||
return valStr |
||||
} |
||||
|
||||
getMultiValsString () { |
||||
var valArray = this.multiFields.querySelectorAll('input') |
||||
var ret = '' |
||||
var valArrayTest = [] |
||||
|
||||
for (var j = 0; j < valArray.length; j++) { |
||||
if (ret !== '') ret += ',' |
||||
var elVal = valArray[j].value |
||||
valArrayTest.push(elVal) |
||||
elVal = elVal.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number
|
||||
elVal = elVal.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string
|
||||
try { |
||||
JSON.parse(elVal) |
||||
} catch (e) { |
||||
elVal = '"' + elVal + '"' |
||||
} |
||||
ret += elVal |
||||
} |
||||
var valStringTest = valArrayTest.join('') |
||||
if (valStringTest) { |
||||
return ret |
||||
} else { |
||||
return '' |
||||
} |
||||
} |
||||
|
||||
emptyInputs () { |
||||
var valArray = this.multiFields.querySelectorAll('input') |
||||
for (var k = 0; k < valArray.length; k++) { |
||||
valArray[k].value = '' |
||||
} |
||||
this.basicInputField.value = '' |
||||
} |
||||
|
||||
makeMultiVal () { |
||||
var inputString = this.basicInputField.value |
||||
if (inputString) { |
||||
inputString = inputString.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number
|
||||
inputString = inputString.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string
|
||||
var inputJSON = JSON.parse('[' + inputString + ']') |
||||
var multiInputs = this.multiFields.querySelectorAll('input') |
||||
for (var k = 0; k < multiInputs.length; k++) { |
||||
if (inputJSON[k]) { |
||||
multiInputs[k].value = JSON.stringify(inputJSON[k]) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
createMultiFields () { |
||||
if (this.funABI.inputs) { |
||||
return yo`<div>
|
||||
${this.funABI.inputs.map(function (inp) { |
||||
return yo`<div class="${css.multiArg}"><label for="${inp.name}"> ${inp.name}: </label><input class="form-control" placeholder="${inp.type}" title="${inp.name}" data-id="multiParamManagerInput${inp.name}"></div>` |
||||
})} |
||||
</div>` |
||||
} |
||||
} |
||||
|
||||
render () { |
||||
var title |
||||
if (this.title) { |
||||
title = this.title |
||||
} else if (this.funABI.name) { |
||||
title = this.funABI.name |
||||
} else { |
||||
title = this.funABI.type === 'receive' ? '(receive)' : '(fallback)' |
||||
} |
||||
|
||||
this.basicInputField = yo`<input class="form-control" data-id="multiParamManagerBasicInputField"></input>` |
||||
this.basicInputField.setAttribute('placeholder', this.inputs) |
||||
this.basicInputField.setAttribute('title', this.inputs) |
||||
this.basicInputField.setAttribute('data-id', this.inputs) |
||||
|
||||
var onClick = () => { |
||||
this.clickCallBack(this.funABI.inputs, this.basicInputField.value) |
||||
} |
||||
const width = this.isDeploy ? '' : 'w-50' |
||||
const funcButton = yo`<button onclick=${() => onClick()} class="${css.instanceButton} ${width} btn btn-sm" data-id="multiParamManagerFuncButton">${title}</button>` |
||||
this.contractActionsContainerSingle = yo` |
||||
<div class="${css.contractActionsContainerSingle} pt-2"> |
||||
${funcButton} |
||||
${this.basicInputField} |
||||
<i class="fas fa-angle-down ${css.methCaret}" onclick=${() => this.switchMethodViewOn()} title=${title} ></i> |
||||
</div>` |
||||
|
||||
this.multiFields = this.createMultiFields() |
||||
|
||||
var multiOnClick = () => { |
||||
var valsString = this.getMultiValsString() |
||||
if (valsString) { |
||||
this.clickCallBack(this.funABI.inputs, valsString) |
||||
} else { |
||||
this.clickCallBack(this.funABI.inputs, '') |
||||
} |
||||
} |
||||
|
||||
var expandedButton = yo`<button onclick=${() => { multiOnClick() }} class="${css.instanceButton}" data-id="multiParamManagerExpandedButton"></button>` |
||||
|
||||
this.contractActionsContainerMulti = yo`<div class="${css.contractActionsContainerMulti}" >
|
||||
<div class="${css.contractActionsContainerMultiInner} text-dark" > |
||||
<div onclick=${() => { this.switchMethodViewOff() }} class="${css.multiHeader}"> |
||||
<div class="${css.multiTitle} run-instance-multi-title">${title}</div> |
||||
<i class='fas fa-angle-up ${css.methCaret}'></i> |
||||
</div> |
||||
${this.multiFields} |
||||
<div class="${css.group} ${css.multiArg}" > |
||||
${copyToClipboard( |
||||
() => { |
||||
var multiString = this.getMultiValsString() |
||||
var multiJSON = JSON.parse('[' + multiString + ']') |
||||
var encodeObj |
||||
if (this.evmBC) { |
||||
encodeObj = txFormat.encodeData(this.funABI, multiJSON, this.evmBC) |
||||
} else { |
||||
encodeObj = txFormat.encodeData(this.funABI, multiJSON) |
||||
} |
||||
if (encodeObj.error) { |
||||
throw new Error(encodeObj.error) |
||||
} else { |
||||
return encodeObj.data |
||||
} |
||||
}, 'Encode values of input fields & copy to clipboard', 'fa-clipboard')} |
||||
${expandedButton} |
||||
</div> |
||||
</div> |
||||
</div>` |
||||
|
||||
var contractProperty = yo` |
||||
<div class="${css.contractProperty}"> |
||||
${this.contractActionsContainerSingle} ${this.contractActionsContainerMulti} |
||||
</div> |
||||
` |
||||
if (this.lookupOnly) { |
||||
// call. stateMutability is either pure or view
|
||||
expandedButton.setAttribute('title', (title + ' - call')) |
||||
expandedButton.innerHTML = 'call' |
||||
expandedButton.classList.add('btn-info') |
||||
expandedButton.setAttribute('data-id', (title + ' - call')) |
||||
funcButton.setAttribute('title', (title + ' - call')) |
||||
funcButton.classList.add('btn-info') |
||||
funcButton.setAttribute('data-id', (title + ' - call')) |
||||
} else if (this.funABI.stateMutability === 'payable' || this.funABI.payable) { |
||||
// transact. stateMutability = payable
|
||||
expandedButton.setAttribute('title', (title + ' - transact (payable)')) |
||||
expandedButton.innerHTML = 'transact' |
||||
expandedButton.classList.add('btn-danger') |
||||
expandedButton.setAttribute('data-id', (title + ' - transact (payable)')) |
||||
funcButton.setAttribute('title', (title + ' - transact (payable)')) |
||||
funcButton.classList.add('btn-danger') |
||||
funcButton.setAttribute('data-id', (title + ' - transact (payable)')) |
||||
} else { |
||||
// transact. stateMutability = nonpayable
|
||||
expandedButton.setAttribute('title', (title + ' - transact (not payable)')) |
||||
expandedButton.innerHTML = 'transact' |
||||
expandedButton.classList.add('btn-warning') |
||||
expandedButton.setAttribute('data-id', (title + ' - transact (not payable)')) |
||||
funcButton.classList.add('btn-warning') |
||||
funcButton.setAttribute('title', (title + ' - transact (not payable)')) |
||||
funcButton.setAttribute('data-id', (title + ' - transact (not payable)')) |
||||
} |
||||
|
||||
if (this.funABI.inputs && this.funABI.inputs.length > 0) { |
||||
contractProperty.classList.add(css.hasArgs) |
||||
} else if (this.funABI.type === 'fallback' || this.funABI.type === 'receive') { |
||||
contractProperty.classList.add(css.hasArgs) |
||||
this.basicInputField.setAttribute('title', `'(${this.funABI.type}')`) // probably should pass name instead
|
||||
this.contractActionsContainerSingle.querySelector('i').style.visibility = 'hidden' |
||||
this.basicInputField.setAttribute('data-id', `'(${this.funABI.type}')`) |
||||
} else { |
||||
this.contractActionsContainerSingle.querySelector('i').style.visibility = 'hidden' |
||||
this.basicInputField.style.visibility = 'hidden' |
||||
} |
||||
|
||||
return contractProperty |
||||
} |
||||
} |
||||
|
||||
module.exports = MultiParamManager |
@ -1,272 +0,0 @@ |
||||
/* 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-project/remix-lib') |
||||
var txFormat = remixLib.execution.txFormat |
||||
const txHelper = remixLib.execution.txHelper |
||||
var TreeView = require('./TreeView') |
||||
var txCallBacks = require('./sendTxCallbacks') |
||||
const _paq = window._paq = window._paq || [] |
||||
|
||||
function UniversalDAppUI (blockchain, logCallback) { |
||||
this.blockchain = blockchain |
||||
this.logCallback = logCallback |
||||
this.compilerData = { contractsDetails: {} } |
||||
} |
||||
|
||||
function decodeResponseToTreeView (response, fnabi) { |
||||
var treeView = new TreeView({ |
||||
extractData: (item, parent, key) => { |
||||
var ret = {} |
||||
if (BN.isBN(item)) { |
||||
ret.self = item.toString(10) |
||||
ret.children = [] |
||||
} else { |
||||
ret = treeView.extractDataDefault(item, parent, key) |
||||
} |
||||
return ret |
||||
} |
||||
}) |
||||
return treeView.render(txFormat.decodeResponse(response, fnabi)) |
||||
} |
||||
|
||||
UniversalDAppUI.prototype.renderInstance = function (contract, address, contractName) { |
||||
var noInstances = document.querySelector('[data-id="deployAndRunNoInstanceText"]') |
||||
if (noInstances) { |
||||
noInstances.parentNode.removeChild(noInstances) |
||||
} |
||||
const abi = txHelper.sortAbiFunction(contract.abi) |
||||
return this.renderInstanceFromABI(abi, address, contractName, contract) |
||||
} |
||||
|
||||
// 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, contract) { |
||||
const self = this |
||||
address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex') |
||||
address = ethJSUtil.toChecksumAddress(address) |
||||
var instance = yo`<div class="instance run-instance border-dark ${css.instance} ${css.hidesub}" id="instance${address}" data-shared="universalDappUiInstance"></div>` |
||||
const context = this.blockchain.context() |
||||
|
||||
var shortAddress = helper.shortenAddress(address) |
||||
var title = yo` |
||||
<div class="${css.title} alert alert-secondary"> |
||||
<button data-id="universalDappUiTitleExpander" class="btn ${css.titleExpander}" onclick="${(e) => { toggleClass(e) }}"> |
||||
<i class="fas fa-angle-right" aria-hidden="true"></i> |
||||
</button> |
||||
<div class="input-group ${css.nameNbuts}"> |
||||
<div class="${css.titleText} input-group-prepend"> |
||||
<span class="input-group-text ${css.spanTitleText}"> |
||||
${contractName} at ${shortAddress} (${context}) |
||||
</span> |
||||
</div> |
||||
<div class="btn-group"> |
||||
<button class="btn p-1 btn-secondary">${copyToClipboard(() => address)}</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
` |
||||
|
||||
var close = yo` |
||||
<button |
||||
class="${css.udappClose} mr-1 p-1 btn btn-secondary align-items-center" |
||||
data-id="universalDappUiUdappClose" |
||||
onclick=${remove} |
||||
title="Remove from the list" |
||||
> |
||||
<i class="${css.closeIcon} fas fa-times" aria-hidden="true"></i> |
||||
</button>` |
||||
title.querySelector('.btn-group').appendChild(close) |
||||
|
||||
var contractActionsWrapper = yo` |
||||
<div class="${css.cActionsWrapper}" data-id="universalDappUiContractActionWrapper"> |
||||
</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} bg-light`) |
||||
// 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) |
||||
|
||||
$.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, |
||||
contract |
||||
})) |
||||
}) |
||||
|
||||
const calldataInput = yo` |
||||
<input id="deployAndRunLLTxCalldata" class="${css.calldataInput} form-control" title="The Calldata to send to fallback function of the contract."> |
||||
` |
||||
const llIError = yo` |
||||
<label id="deployAndRunLLTxError" class="text-danger my-2"></label> |
||||
` |
||||
// constract LLInteractions elements
|
||||
const lowLevelInteracions = yo` |
||||
<div class="d-flex flex-column"> |
||||
<div class="d-flex flex-row justify-content-between mt-2"> |
||||
<div class="py-2 border-top d-flex justify-content-start flex-grow-1"> |
||||
Low level interactions |
||||
</div> |
||||
<a |
||||
href="https://solidity.readthedocs.io/en/v0.6.2/contracts.html#receive-ether-function" |
||||
title="check out docs for using 'receive'/'fallback'" |
||||
target="_blank" |
||||
> |
||||
<i aria-hidden="true" class="fas fa-info my-2 mr-1"></i> |
||||
</a> |
||||
</div> |
||||
<div class="d-flex flex-column align-items-start"> |
||||
<label class="">CALLDATA</label> |
||||
<div class="d-flex justify-content-end w-100 align-items-center"> |
||||
${calldataInput} |
||||
<button id="deployAndRunLLTxSendTransaction" data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction" class="${css.instanceButton} p-0 w-50 btn border-warning text-warning" title="Send data to contract." onclick=${() => sendData()}>Transact</button> |
||||
</div> |
||||
</div> |
||||
<div> |
||||
${llIError} |
||||
</div> |
||||
</div> |
||||
` |
||||
|
||||
function sendData () { |
||||
function setLLIError (text) { |
||||
llIError.innerText = text |
||||
} |
||||
|
||||
setLLIError('') |
||||
const fallback = txHelper.getFallbackInterface(contractABI) |
||||
const receive = txHelper.getReceiveInterface(contractABI) |
||||
const args = { |
||||
funABI: fallback || receive, |
||||
address: address, |
||||
contractName: contractName, |
||||
contractABI: contractABI |
||||
} |
||||
const amount = document.querySelector('#value').value |
||||
if (amount !== '0') { |
||||
// check for numeric and receive/fallback
|
||||
if (!helper.isNumeric(amount)) { |
||||
return setLLIError('Value to send should be a number') |
||||
} else if (!receive && !(fallback && fallback.stateMutability === 'payable')) { |
||||
return setLLIError("In order to receive Ether transfer the contract should have either 'receive' or payable 'fallback' function") |
||||
} |
||||
} |
||||
let calldata = calldataInput.value |
||||
if (calldata) { |
||||
if (calldata.length < 4 && helper.is0XPrefixed(calldata)) { |
||||
return setLLIError('The calldata should be a valid hexadecimal value with size of at least one byte.') |
||||
} else { |
||||
if (helper.is0XPrefixed(calldata)) { |
||||
calldata = calldata.substr(2, calldata.length) |
||||
} |
||||
if (!helper.isHexadecimal(calldata)) { |
||||
return setLLIError('The calldata should be a valid hexadecimal value.') |
||||
} |
||||
} |
||||
if (!fallback) { |
||||
return setLLIError("'Fallback' function is not defined") |
||||
} |
||||
} |
||||
|
||||
if (!receive && !fallback) return setLLIError('Both \'receive\' and \'fallback\' functions are not defined') |
||||
|
||||
// we have to put the right function ABI:
|
||||
// if receive is defined and that there is no calldata => receive function is called
|
||||
// if fallback is defined => fallback function is called
|
||||
if (receive && !calldata) args.funABI = receive |
||||
else if (fallback) args.funABI = fallback |
||||
|
||||
if (!args.funABI) return setLLIError('Please define a \'Fallback\' function to send calldata and a either \'Receive\' or payable \'Fallback\' to send ethers') |
||||
self.runTransaction(false, args, null, calldataInput.value, null) |
||||
} |
||||
|
||||
contractActionsWrapper.appendChild(lowLevelInteracions) |
||||
return instance |
||||
} |
||||
|
||||
// TODO this is used by renderInstance when a new instance is displayed.
|
||||
// this returns a DOM element.
|
||||
UniversalDAppUI.prototype.getCallButton = function (args) { |
||||
const self = this |
||||
var outputOverride = yo`<div class=${css.value}></div>` // show return value
|
||||
const isConstant = args.funABI.constant !== undefined ? args.funABI.constant : false |
||||
const lookupOnly = args.funABI.stateMutability === 'view' || args.funABI.stateMutability === 'pure' || isConstant |
||||
const multiParamManager = new MultiParamManager( |
||||
lookupOnly, |
||||
args.funABI, |
||||
(valArray, inputsValues) => self.runTransaction(lookupOnly, args, valArray, inputsValues, outputOverride), |
||||
self.blockchain.getInputs(args.funABI) |
||||
) |
||||
|
||||
const contractActionsContainer = yo`<div class="${css.contractActionsContainer}" >${multiParamManager.render()}</div>` |
||||
contractActionsContainer.appendChild(outputOverride) |
||||
|
||||
return contractActionsContainer |
||||
} |
||||
|
||||
UniversalDAppUI.prototype.runTransaction = function (lookupOnly, args, valArr, inputsValues, outputOverride) { |
||||
const functionName = args.funABI.type === 'function' ? args.funABI.name : `(${args.funABI.type})` |
||||
const logMsg = `${lookupOnly ? 'call' : 'transact'} to ${args.contractName}.${functionName}` |
||||
|
||||
const callbacksInContext = txCallBacks.getCallBacksWithContext(this, this.blockchain) |
||||
|
||||
const outputCb = (returnValue) => { |
||||
if (outputOverride) { |
||||
const decoded = decodeResponseToTreeView(returnValue, args.funABI) |
||||
outputOverride.innerHTML = '' |
||||
outputOverride.appendChild(decoded) |
||||
} |
||||
} |
||||
let callinfo = '' |
||||
if (lookupOnly) callinfo = 'call' |
||||
else if (args.funABI.type === 'fallback' || args.funABI.type === 'receive') callinfo = 'lowLevelInteracions' |
||||
else callinfo = 'transact' |
||||
|
||||
_paq.push(['trackEvent', 'udapp', callinfo, this.blockchain.getCurrentNetworkStatus().network.name]) |
||||
const params = args.funABI.type !== 'fallback' ? inputsValues : '' |
||||
this.blockchain.runOrCallContractMethod( |
||||
args.contractName, |
||||
args.contractABI, |
||||
args.funABI, |
||||
args.contract, |
||||
inputsValues, |
||||
args.address, |
||||
params, |
||||
lookupOnly, |
||||
logMsg, |
||||
this.logCallback, |
||||
outputCb, |
||||
callbacksInContext.confirmationCb.bind(callbacksInContext), |
||||
callbacksInContext.continueCb.bind(callbacksInContext), |
||||
callbacksInContext.promptCb.bind(callbacksInContext)) |
||||
} |
||||
|
||||
module.exports = UniversalDAppUI |
@ -1,121 +0,0 @@ |
||||
'use strict' |
||||
|
||||
const async = require('async') |
||||
const IpfsClient = require('ipfs-mini') |
||||
|
||||
const ipfsNodes = [ |
||||
new IpfsClient({ host: 'ipfs.remixproject.org', port: 443, protocol: 'https' }), |
||||
new IpfsClient({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' }), |
||||
new IpfsClient({ host: '127.0.0.1', port: 5001, protocol: 'http' }) |
||||
] |
||||
|
||||
module.exports = (contract, fileManager, cb, ipfsVerifiedPublishCallBack) => { |
||||
// gather list of files to publish
|
||||
var sources = [] |
||||
|
||||
var metadata |
||||
|
||||
try { |
||||
metadata = JSON.parse(contract.metadata) |
||||
} catch (e) { |
||||
return cb(e) |
||||
} |
||||
|
||||
if (metadata === undefined) { |
||||
return cb('No metadata') |
||||
} |
||||
|
||||
async.eachSeries(Object.keys(metadata.sources), function (fileName, cb) { |
||||
// find hash
|
||||
let hash = null |
||||
try { |
||||
// we try extract the hash defined in the metadata.json
|
||||
// in order to check if the hash that we get after publishing is the same as the one located in metadata.json
|
||||
// if it's not the same, we throw "hash mismatch between solidity bytecode and uploaded content"
|
||||
// if we don't find the hash in the metadata.json, the check is not done.
|
||||
//
|
||||
// TODO: refactor this with publishOnSwarm
|
||||
if (metadata.sources[fileName].urls) { |
||||
metadata.sources[fileName].urls.forEach(url => { |
||||
if (url.includes('ipfs')) hash = url.match('dweb:/ipfs/(.+)')[1] |
||||
}) |
||||
} |
||||
} catch (e) { |
||||
return cb('Error while extracting the hash from metadata.json') |
||||
} |
||||
|
||||
fileManager.fileProviderOf(fileName).get(fileName, (error, content) => { |
||||
if (error) { |
||||
console.log(error) |
||||
} else { |
||||
sources.push({ |
||||
content: content, |
||||
hash: hash, |
||||
filename: fileName |
||||
}) |
||||
} |
||||
cb() |
||||
}) |
||||
}, function (error) { |
||||
if (error) { |
||||
cb(error) |
||||
} else { |
||||
// publish the list of sources in order, fail if any failed
|
||||
var uploaded = [] |
||||
async.eachSeries(sources, function (item, cb) { |
||||
ipfsVerifiedPublish(item.content, item.hash, (error, result) => { |
||||
try { |
||||
item.hash = result.url.match('dweb:/ipfs/(.+)')[1] |
||||
} catch (e) { |
||||
item.hash = '<Metadata inconsistency> - ' + item.fileName |
||||
} |
||||
if (!error && ipfsVerifiedPublishCallBack) ipfsVerifiedPublishCallBack(item) |
||||
item.output = result |
||||
uploaded.push(item) |
||||
cb(error) |
||||
}) |
||||
}, () => { |
||||
const metadataContent = JSON.stringify(metadata) |
||||
ipfsVerifiedPublish(metadataContent, '', (error, result) => { |
||||
try { |
||||
contract.metadataHash = result.url.match('dweb:/ipfs/(.+)')[1] |
||||
} catch (e) { |
||||
contract.metadataHash = '<Metadata inconsistency> - metadata.json' |
||||
} |
||||
if (!error && ipfsVerifiedPublishCallBack) { |
||||
ipfsVerifiedPublishCallBack({ |
||||
content: metadataContent, |
||||
hash: contract.metadataHash |
||||
}) |
||||
} |
||||
uploaded.push({ |
||||
content: contract.metadata, |
||||
hash: contract.metadataHash, |
||||
filename: 'metadata.json', |
||||
output: result |
||||
}) |
||||
cb(error, uploaded) |
||||
}) |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
async function ipfsVerifiedPublish (content, expectedHash, cb) { |
||||
try { |
||||
const results = await severalGatewaysPush(content) |
||||
if (expectedHash && results !== expectedHash) { |
||||
cb(null, { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'dweb:/ipfs/' + results, hash: results }) |
||||
} else { |
||||
cb(null, { message: 'ok', url: 'dweb:/ipfs/' + results, hash: results }) |
||||
} |
||||
} catch (error) { |
||||
cb(error) |
||||
} |
||||
} |
||||
|
||||
function severalGatewaysPush (content) { |
||||
const invert = p => new Promise((resolve, reject) => p.then(reject).catch(resolve)) // Invert res and rej
|
||||
const promises = ipfsNodes.map((node) => invert(node.add(content))) |
||||
return invert(Promise.all(promises)) |
||||
} |
@ -1,109 +0,0 @@ |
||||
'use strict' |
||||
|
||||
var async = require('async') |
||||
var swarmgw = require('swarmgw')() |
||||
|
||||
module.exports = (contract, fileManager, cb, swarmVerifiedPublishCallBack) => { |
||||
// gather list of files to publish
|
||||
var sources = [] |
||||
|
||||
var metadata |
||||
try { |
||||
metadata = JSON.parse(contract.metadata) |
||||
} catch (e) { |
||||
return cb(e) |
||||
} |
||||
|
||||
if (metadata === undefined) { |
||||
return cb('No metadata') |
||||
} |
||||
|
||||
async.eachSeries(Object.keys(metadata.sources), function (fileName, cb) { |
||||
// find hash
|
||||
let hash = null |
||||
try { |
||||
// we try extract the hash defined in the metadata.json
|
||||
// in order to check if the hash that we get after publishing is the same as the one located in metadata.json
|
||||
// if it's not the same, we throw "hash mismatch between solidity bytecode and uploaded content"
|
||||
// if we don't find the hash in the metadata.json, the check is not done.
|
||||
//
|
||||
// TODO: refactor this with publishOnIpfs
|
||||
if (metadata.sources[fileName].urls) { |
||||
metadata.sources[fileName].urls.forEach(url => { |
||||
if (url.includes('bzz')) hash = url.match('(bzzr|bzz-raw)://(.+)')[1] |
||||
}) |
||||
} |
||||
} catch (e) { |
||||
return cb('Error while extracting the hash from metadata.json') |
||||
} |
||||
|
||||
fileManager.fileProviderOf(fileName).get(fileName, (error, content) => { |
||||
if (error) { |
||||
console.log(error) |
||||
} else { |
||||
sources.push({ |
||||
content: content, |
||||
hash: hash, |
||||
filename: fileName |
||||
}) |
||||
} |
||||
cb() |
||||
}) |
||||
}, function (error) { |
||||
if (error) { |
||||
cb(error) |
||||
} else { |
||||
// publish the list of sources in order, fail if any failed
|
||||
var uploaded = [] |
||||
async.eachSeries(sources, function (item, cb) { |
||||
swarmVerifiedPublish(item.content, item.hash, (error, result) => { |
||||
try { |
||||
item.hash = result.url.match('bzz-raw://(.+)')[1] |
||||
} catch (e) { |
||||
item.hash = '<Metadata inconsistency> - ' + item.fileName |
||||
} |
||||
if (!error && swarmVerifiedPublishCallBack) swarmVerifiedPublishCallBack(item) |
||||
item.output = result |
||||
uploaded.push(item) |
||||
// TODO this is a fix cause Solidity metadata does not contain the right swarm hash (poc 0.3)
|
||||
metadata.sources[item.filename].urls[0] = result.url |
||||
cb(error) |
||||
}) |
||||
}, () => { |
||||
const metadataContent = JSON.stringify(metadata) |
||||
swarmVerifiedPublish(metadataContent, '', (error, result) => { |
||||
try { |
||||
contract.metadataHash = result.url.match('bzz-raw://(.+)')[1] |
||||
} catch (e) { |
||||
contract.metadataHash = '<Metadata inconsistency> - metadata.json' |
||||
} |
||||
if (!error && swarmVerifiedPublishCallBack) { |
||||
swarmVerifiedPublishCallBack({ |
||||
content: metadataContent, |
||||
hash: contract.metadataHash |
||||
}) |
||||
} |
||||
uploaded.push({ |
||||
content: contract.metadata, |
||||
hash: contract.metadataHash, |
||||
filename: 'metadata.json', |
||||
output: result |
||||
}) |
||||
cb(error, uploaded) |
||||
}) |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
function swarmVerifiedPublish (content, expectedHash, cb) { |
||||
swarmgw.put(content, function (err, ret) { |
||||
if (err) { |
||||
cb(err) |
||||
} else if (expectedHash && ret !== expectedHash) { |
||||
cb(null, { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'bzz-raw://' + ret, hash: ret }) |
||||
} else { |
||||
cb(null, { message: 'ok', url: 'bzz-raw://' + ret, hash: ret }) |
||||
} |
||||
}) |
||||
} |
@ -1,47 +0,0 @@ |
||||
const yo = require('yo-yo') |
||||
const publishOnSwarm = require('./lib/publishOnSwarm') |
||||
const publishOnIpfs = require('./lib/publishOnIpfs') |
||||
const modalDialogCustom = require('./app/ui/modal-dialog-custom') |
||||
|
||||
export default function publish (storage, fileProvider, fileManager, contract) { |
||||
if (contract) { |
||||
if (contract.metadata === undefined || contract.metadata.length === 0) { |
||||
modalDialogCustom.alert('This contract may be abstract, may not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.') |
||||
} else { |
||||
if (storage === 'swarm') { |
||||
publishOnSwarm(contract, fileManager, function (err, uploaded) { |
||||
if (err) { |
||||
try { |
||||
err = JSON.stringify(err) |
||||
} catch (e) {} |
||||
console.log(`Failed to publish metadata file to swarm, please check the Swarm gateways is available ( swarm-gateways.net ) ${err}`) |
||||
} else { |
||||
var result = yo`<div>${uploaded.map((value) => { |
||||
return yo`<div><b>${value.filename}</b> : <pre>${value.output.url}</pre></div>` |
||||
})}</div>` |
||||
modalDialogCustom.alert(`Published ${contract.name}'s Metadata`, yo`<span>Metadata of "${contract.name.toLowerCase()}" was published successfully.<br> <pre>${result}</pre> </span>`) |
||||
} |
||||
}, (item) => { // triggered each time there's a new verified publish (means hash correspond)
|
||||
fileProvider.addExternal('swarm/' + item.hash, item.content) |
||||
}) |
||||
} else { |
||||
publishOnIpfs(contract, fileManager, function (err, uploaded) { |
||||
if (err) { |
||||
try { |
||||
err = JSON.stringify(err) |
||||
} catch (e) {} |
||||
modalDialogCustom.alert(yo`<span>Failed to publish metadata file to ${storage}, please check the ${storage} gateways is available.<br />
|
||||
${err}</span>`) |
||||
} else { |
||||
var result = yo`<div>${uploaded.map((value) => { |
||||
return yo`<div><b>${value.filename}</b> : <pre>${value.output.url.replace('dweb:/ipfs/', 'ipfs://')}</pre></div>` |
||||
})}</div>` |
||||
modalDialogCustom.alert(`Published ${contract.name}'s Metadata`, yo`<span>Metadata of "${contract.name.toLowerCase()}" was published successfully.<br> <pre>${result}</pre> </span>`) |
||||
} |
||||
}, (item) => { // triggered each time there's a new verified publish (means hash correspond)
|
||||
fileProvider.addExternal('ipfs/' + item.hash, item.content) |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,285 +0,0 @@ |
||||
const csjs = require('csjs-inject') |
||||
|
||||
var css = csjs` |
||||
.instanceTitleContainer { |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
.calldataInput{ |
||||
height: 32px; |
||||
} |
||||
.title { |
||||
display: flex; |
||||
justify-content: space-between; |
||||
font-size: 11px; |
||||
width: 100%; |
||||
overflow: hidden; |
||||
word-break: break-word; |
||||
line-height: initial; |
||||
overflow: visible; |
||||
padding: 0 0 8px; |
||||
margin: 0; |
||||
background: none; |
||||
border: none; |
||||
} |
||||
.title button { |
||||
background: none; |
||||
border: none; |
||||
} |
||||
.titleLine { |
||||
display: flex; |
||||
align-items: baseline; |
||||
} |
||||
.titleText { |
||||
word-break: break-word; |
||||
width: 100%; |
||||
border: none; |
||||
overflow: hidden; |
||||
} |
||||
.spanTitleText { |
||||
line-height: 12px; |
||||
padding: 0; |
||||
font-size: 11px; |
||||
width:100%; |
||||
border: none; |
||||
background: none; |
||||
text-transform: uppercase; |
||||
overflow: hidden; |
||||
} |
||||
.inputGroupText { |
||||
width: 100%; |
||||
} |
||||
.title .copy { |
||||
color: var(--primary); |
||||
} |
||||
.titleExpander { |
||||
padding: 5px 7px; |
||||
} |
||||
.nameNbuts { |
||||
display: contents; |
||||
flex-wrap: nowrap; |
||||
width: 100%; |
||||
} |
||||
.instance { |
||||
display: block; |
||||
flex-direction: column; |
||||
margin-bottom: 12px; |
||||
background: none; |
||||
border-radius: 2px; |
||||
} |
||||
.instance.hidesub { |
||||
border-bottom: 1px solid; |
||||
} |
||||
.instance.hidesub .title { |
||||
display: flex; |
||||
} |
||||
.instance.hidesub .udappClose { |
||||
display: flex; |
||||
} |
||||
.instance.hidesub > * { |
||||
display: none; |
||||
} |
||||
.methCaret { |
||||
min-width: 12px; |
||||
width: 12px; |
||||
margin-left: 4px; |
||||
cursor: pointer; |
||||
font-size: 16px; |
||||
line-height: 0.6; |
||||
vertical-align: middle; |
||||
padding: 0; |
||||
} |
||||
.cActionsWrapper { |
||||
border-top-left-radius: 0; |
||||
border-bottom-left-radius: 0.25rem; |
||||
border-top-rightt-radius: 0; |
||||
border-bottom-right-radius: 0.25rem; |
||||
padding: 8px 10px 7px; |
||||
} |
||||
.group:after { |
||||
content: ""; |
||||
display: table; |
||||
clear: both; |
||||
} |
||||
.buttonsContainer { |
||||
margin-top: 2%; |
||||
display: flex; |
||||
overflow: hidden; |
||||
} |
||||
.instanceButton { |
||||
height: 32px; |
||||
border-radius: 3px; |
||||
white-space: nowrap; |
||||
font-size: 11px; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
} |
||||
.closeIcon { |
||||
font-size: 12px; |
||||
cursor: pointer; |
||||
margin-left: 5px; |
||||
} |
||||
.udappClose { |
||||
display: flex; |
||||
justify-content: flex-end; |
||||
} |
||||
.contractProperty { |
||||
width:100%; |
||||
} |
||||
.contractProperty.hasArgs input { |
||||
padding: .36em; |
||||
border-radius: 5px; |
||||
} |
||||
.contractProperty .contractActionsContainerSingle input{ |
||||
border-top-left-radius: 0; |
||||
border-bottom-left-radius: 0; |
||||
} |
||||
.contractProperty button { |
||||
min-width: 100px; |
||||
width: 100px; |
||||
margin:0; |
||||
word-break: inherit; |
||||
} |
||||
.contractProperty button:disabled { |
||||
cursor: not-allowed; |
||||
background-color: white; |
||||
border-color: lightgray; |
||||
} |
||||
.contractProperty.constant button { |
||||
min-width: 100px; |
||||
width: 100px; |
||||
margin:0; |
||||
word-break: inherit; |
||||
outline: none; |
||||
white-space: nowrap; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
} |
||||
.contractProperty > .value { |
||||
box-sizing: border-box; |
||||
float: left; |
||||
align-self: center; |
||||
margin-left: 4px; |
||||
} |
||||
.contractActionsContainer { |
||||
width: 100%; |
||||
margin-bottom: 8px; |
||||
} |
||||
.contractActionsContainerSingle { |
||||
display: flex; |
||||
width: 100%; |
||||
} |
||||
.contractActionsContainerSingle i { |
||||
line-height: 2; |
||||
} |
||||
.contractActionsContainerMulti { |
||||
display:none; |
||||
width: 100%; |
||||
} |
||||
.contractActionsContainerMultiInner { |
||||
width: 100%; |
||||
padding: 16px 8px 16px 14px; |
||||
border-radius: 3px; |
||||
margin-bottom: 8px; |
||||
} |
||||
.multiHeader { |
||||
display: flex; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
margin-bottom: 8px; |
||||
text-align: left; |
||||
font-size: 10px; |
||||
font-weight: bold; |
||||
} |
||||
.contractActionsContainerMultiInner .multiTitle { |
||||
padding-left: 10px; |
||||
} |
||||
.contractProperty .multiTitle { |
||||
padding: 0; |
||||
line-height: 16px; |
||||
display: inline-block; |
||||
font-size: 12px; |
||||
font-weight: bold; |
||||
cursor: default; |
||||
} |
||||
.contractProperty .contractActionsContainerMultiInner .multiArg label{ |
||||
text-align: right; |
||||
} |
||||
.multiHeader .methCaret { |
||||
float: right; |
||||
margin-right: 0; |
||||
} |
||||
.contractProperty.constant .multiTitle { |
||||
display: inline-block; |
||||
width: 90%; |
||||
/* font-size: 10px; */ |
||||
height: 25px; |
||||
padding-left: 20px; |
||||
font-weight: bold; |
||||
line-height: 25px; |
||||
cursor: default; |
||||
} |
||||
.multiArg { |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: flex-end; |
||||
margin-top: 4px; |
||||
} |
||||
.multiArg input{ |
||||
padding: 5px; |
||||
} |
||||
.multiArg label { |
||||
width: auto; |
||||
padding: 0; |
||||
margin: 0 4px 0 0; |
||||
font-size: 10px; |
||||
line-height: 12px; |
||||
text-align: right; |
||||
word-break: initial; |
||||
} |
||||
.multiArg button { |
||||
max-width: 100px; |
||||
border-radius: 3px; |
||||
border-width: 1px; |
||||
width: inherit; |
||||
} |
||||
.multiHeader button { |
||||
display: inline-block; |
||||
width: 94%; |
||||
} |
||||
.hasArgs .multiArg input { |
||||
border-left: 1px solid #dddddd; |
||||
width: 67%; |
||||
} |
||||
.hasArgs input { |
||||
display: block; |
||||
height: 32px; |
||||
border: 1px solid #dddddd; |
||||
padding: .36em; |
||||
border-left: none; |
||||
padding: 8px 8px 8px 10px; |
||||
font-size: 10px !important; |
||||
} |
||||
.hasArgs button { |
||||
border-top-right-radius: 0; |
||||
border-bottom-right-radius: 0; |
||||
border-right: 0; |
||||
white-space: nowrap; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
font-size: 11px; |
||||
} |
||||
.hasArgs .contractActionsContainerMulti button { |
||||
border-radius: 3px; |
||||
} |
||||
.contractActionsContainerMultiInner .multiArg i { |
||||
padding-right: 10px; |
||||
} |
||||
.hideWarningsContainer { |
||||
display: flex; |
||||
align-items: center; |
||||
margin-left: 2% |
||||
} |
||||
` |
||||
|
||||
module.exports = css |
Loading…
Reference in new issue