parent
0523c9a489
commit
eb18edc987
@ -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