+
+
+ WARNING: It is not safe to use the --http.corsdomain flag with a wildcard: --http.corsdomain *
+
+ For more info: Remix Docs on Web3 Provider
+
+
+ External HTTP Provider Endpoint
+
+ >
+ )
+ }
+}
\ No newline at end of file
diff --git a/apps/remix-ide/src/app/tabs/injected-arbitrum-one-provider.tsx b/apps/remix-ide/src/app/tabs/injected-arbitrum-one-provider.tsx
new file mode 100644
index 0000000000..283dacdf24
--- /dev/null
+++ b/apps/remix-ide/src/app/tabs/injected-arbitrum-one-provider.tsx
@@ -0,0 +1,21 @@
+import * as packageJson from '../../../../../package.json'
+import { InjectedProvider } from './injected-provider'
+
+const profile = {
+ name: 'injected-arbitrum-one-provider',
+ displayName: 'Injected Arbitrum One Provider',
+ kind: 'provider',
+ description: 'injected Arbitrum One Provider',
+ methods: ['sendAsync'],
+ version: packageJson.version
+}
+
+export class InjectedArbitrumOneProvider extends InjectedProvider {
+
+ constructor () {
+ super(profile)
+ this.chainName = 'Arbitrum One'
+ this.chainId = '0xa4b1'
+ this.rpcUrls = ['https://arb1.arbitrum.io/rpc']
+ }
+}
\ No newline at end of file
diff --git a/apps/remix-ide/src/app/tabs/injected-optimism-provider.tsx b/apps/remix-ide/src/app/tabs/injected-optimism-provider.tsx
new file mode 100644
index 0000000000..64c8c4e91a
--- /dev/null
+++ b/apps/remix-ide/src/app/tabs/injected-optimism-provider.tsx
@@ -0,0 +1,21 @@
+import * as packageJson from '../../../../../package.json'
+import { InjectedProvider } from './injected-provider'
+
+const profile = {
+ name: 'injected-optimism-provider',
+ displayName: 'Injected Optimism Provider',
+ kind: 'provider',
+ description: 'injected Optimism Provider',
+ methods: ['sendAsync'],
+ version: packageJson.version
+}
+
+export class Injected0ptimismProvider extends InjectedProvider {
+
+ constructor () {
+ super(profile)
+ this.chainName = 'Optimism'
+ this.chainId = '0xa'
+ this.rpcUrls = ['https://mainnet.optimism.io']
+ }
+}
\ No newline at end of file
diff --git a/apps/remix-ide/src/app/tabs/injected-provider.tsx b/apps/remix-ide/src/app/tabs/injected-provider.tsx
new file mode 100644
index 0000000000..038919206c
--- /dev/null
+++ b/apps/remix-ide/src/app/tabs/injected-provider.tsx
@@ -0,0 +1,75 @@
+import { Plugin } from '@remixproject/engine'
+import { JsonDataRequest, RejectRequest, SuccessRequest } from './abstract-provider'
+import { ethers } from 'ethers'
+import Web3 from 'web3'
+
+export class InjectedProvider extends Plugin {
+ provider: any
+ chainName: string
+ chainId: string
+ rpcUrls: Array
+
+ constructor (profile) {
+ super(profile)
+ if ((window as any).ethereum) {
+ this.provider = new Web3((window as any).ethereum)
+ }
+ }
+
+ sendAsync (data: JsonDataRequest): Promise {
+ return new Promise((resolve, reject) => {
+ this.sendAsyncInternal(data, resolve, reject)
+ })
+ }
+
+ private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise {
+ // Check the case where current environment is VM on UI and it still sends RPC requests
+ // This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!'
+ if (!this.provider) {
+ this.call('notification', 'toast', 'No injected provider (e.g Metamask) has been found.')
+ return reject(new Error('no injected provider found.'))
+ }
+ try {
+ if ((window as any) && typeof (window as any).ethereum.enable === 'function') (window as any).ethereum.enable()
+ if (!await (window as any).ethereum._metamask.isUnlocked()) this.call('notification', 'toast', 'Please make sure the injected provider is unlocked (e.g Metamask).')
+ await addL2Network(this.chainName, this.chainId, this.rpcUrls)
+ const resultData = await this.provider.currentProvider.send(data.method, data.params)
+ resolve({ jsonrpc: '2.0', result: resultData.result, id: data.id })
+ } catch (error) {
+ reject(error)
+ }
+ }
+}
+
+export const addL2Network = async (chainName: string, chainId: string, rpcUrls: Array) => {
+ try {
+ await (window as any).ethereum.request({
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: chainId }],
+ });
+ } catch (switchError) {
+ // This error code indicates that the chain has not been added to MetaMask.
+ if (switchError.code === 4902) {
+ try {
+ await (window as any).ethereum.request({
+ method: 'wallet_addEthereumChain',
+ params: [
+ {
+ chainId: chainId,
+ chainName: chainName,
+ rpcUrls: rpcUrls,
+ },
+ ],
+ });
+
+ await (window as any).ethereum.request({
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: chainId }],
+ });
+ } catch (addError) {
+ // handle "add" error
+ }
+ }
+ // handle other "switch" errors
+ }
+}
\ No newline at end of file
diff --git a/apps/remix-ide/src/app/tabs/runTab/model/recorder.js b/apps/remix-ide/src/app/tabs/runTab/model/recorder.js
index 6178063843..caa758b180 100644
--- a/apps/remix-ide/src/app/tabs/runTab/model/recorder.js
+++ b/apps/remix-ide/src/app/tabs/runTab/model/recorder.js
@@ -1,16 +1,28 @@
var async = require('async')
var ethutil = require('ethereumjs-util')
var remixLib = require('@remix-project/remix-lib')
+import { Plugin } from '@remixproject/engine'
+import * as packageJson from '../../../../.././../../package.json'
var EventManager = remixLib.EventManager
var format = remixLib.execution.txFormat
var txHelper = remixLib.execution.txHelper
const helper = require('../../../../lib/helper')
+const _paq = window._paq = window._paq || [] //eslint-disable-line
+
+const profile = {
+ name: 'recorder',
+ displayName: 'Recorder',
+ description: '',
+ version: packageJson.version,
+ methods: [ ]
+}
/**
* Record transaction as long as the user create them.
*/
-class Recorder {
+class Recorder extends Plugin {
constructor (blockchain) {
+ super(profile)
this.event = new EventManager()
this.blockchain = blockchain
this.data = { _listen: true, _replay: false, journal: [], _createdContracts: {}, _createdContractsReverse: {}, _usedAccounts: {}, _abis: {}, _contractABIReferences: {}, _linkReferences: {} }
@@ -21,7 +33,13 @@ class Recorder {
// convert to and from to tokens
if (this.data._listen) {
- var record = { value, parameters: payLoad.funArgs }
+ var record = {
+ value,
+ inputs: txHelper.serializeInputs(payLoad.funAbi),
+ parameters: payLoad.funArgs,
+ name: payLoad.funAbi.name,
+ type: payLoad.funAbi.type
+ }
if (!to) {
var abi = payLoad.contractABI
var keccak = ethutil.bufferToHex(ethutil.keccakFromString(JSON.stringify(abi)))
@@ -43,10 +61,7 @@ class Recorder {
var creationTimestamp = this.data._createdContracts[to]
record.to = `created{${creationTimestamp}}`
record.abi = this.data._contractABIReferences[creationTimestamp]
- }
- record.name = payLoad.funAbi.name
- record.inputs = txHelper.serializeInputs(payLoad.funAbi)
- record.type = payLoad.funAbi.type
+ }
for (var p in record.parameters) {
var thisarg = record.parameters[p]
var thistimestamp = this.data._createdContracts[thisarg]
@@ -169,16 +184,30 @@ class Recorder {
/**
* run the list of records
*
+ * @param {Object} records
* @param {Object} accounts
* @param {Object} options
* @param {Object} abis
+ * @param {Object} linkReferences
+ * @param {Function} confirmationCb
+ * @param {Function} continueCb
+ * @param {Function} promptCb
+ * @param {Function} alertCb
+ * @param {Function} logCallBack
+ * @param {Function} liveMode
* @param {Function} newContractFn
*
*/
- run (records, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, newContractFn) {
+ run (records, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, liveMode, newContractFn) {
this.setListen(false)
- logCallBack(`Running ${records.length} transaction(s) ...`)
- async.eachOfSeries(records, (tx, index, cb) => {
+ const liveMsg = liveMode ? ' in live mode' : ''
+ logCallBack(`Running ${records.length} transaction(s)${liveMsg} ...`)
+ async.eachOfSeries(records, async (tx, index, cb) => {
+ if (liveMode && tx.record.type === 'constructor') {
+ // resolve the bytecode using the contract name, this ensure getting the last compiled one.
+ const data = await this.call('compilerArtefacts', 'getArtefactsByContractName', tx.record.contractName)
+ tx.record.bytecode = data.artefact.evm.bytecode.object
+ }
var record = this.resolveAddress(tx.record, accounts, options)
var abi = abis[tx.record.abi]
if (!abi) {
@@ -257,8 +286,10 @@ class Recorder {
}, () => { this.setListen(true) })
}
- runScenario (json, continueCb, promptCb, alertCb, confirmationCb, logCallBack, cb) {
+ runScenario (liveMode, json, continueCb, promptCb, alertCb, confirmationCb, logCallBack, cb) {
+ _paq.push(['trackEvent', 'run', 'recorder', 'start'])
if (!json) {
+ _paq.push(['trackEvent', 'run', 'recorder', 'wrong-json'])
return cb('a json content must be provided')
}
if (typeof json === 'string') {
@@ -269,12 +300,17 @@ class Recorder {
}
}
+ let txArray
+ let accounts
+ let options
+ let abis
+ let linkReferences
try {
- var txArray = json.transactions || []
- var accounts = json.accounts || []
- var options = json.options || {}
- var abis = json.abis || {}
- var linkReferences = json.linkReferences || {}
+ txArray = json.transactions || []
+ accounts = json.accounts || []
+ options = json.options || {}
+ abis = json.abis || {}
+ linkReferences = json.linkReferences || {}
} catch (e) {
return cb('Invalid Scenario File. Please try again')
}
@@ -283,7 +319,7 @@ class Recorder {
return
}
- this.run(txArray, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, (abi, address, contractName) => {
+ this.run(txArray, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, liveMode, (abi, address, contractName) => {
cb(null, abi, address, contractName)
})
}
diff --git a/apps/remix-ide/src/app/tabs/theme-module.js b/apps/remix-ide/src/app/tabs/theme-module.js
index 2fd9e92e1a..ceb7192317 100644
--- a/apps/remix-ide/src/app/tabs/theme-module.js
+++ b/apps/remix-ide/src/app/tabs/theme-module.js
@@ -37,7 +37,7 @@ export class ThemeModule extends Plugin {
themes.map((theme) => {
this.themes[theme.name.toLocaleLowerCase()] = {
...theme,
- url: window.location.origin + window.location.pathname + theme.url
+ url: window.location.origin + ( window.location.pathname.startsWith('/address/') || window.location.pathname.endsWith('.sol') ? '/' : window.location.pathname ) + theme.url
}
})
this._paq = _paq
diff --git a/apps/remix-ide/src/app/udapp/run-tab.js b/apps/remix-ide/src/app/udapp/run-tab.js
index 8a1e30324e..c9912ecb4c 100644
--- a/apps/remix-ide/src/app/udapp/run-tab.js
+++ b/apps/remix-ide/src/app/udapp/run-tab.js
@@ -102,6 +102,7 @@ export class RunTab extends ViewPlugin {
await this.call('blockchain', 'addProvider', {
name: 'Hardhat Provider',
+ isInjected: false,
provider: {
async sendAsync (payload, callback) {
try {
@@ -116,6 +117,7 @@ export class RunTab extends ViewPlugin {
await this.call('blockchain', 'addProvider', {
name: 'Ganache Provider',
+ isInjected: false,
provider: {
async sendAsync (payload, callback) {
try {
@@ -130,6 +132,7 @@ export class RunTab extends ViewPlugin {
await this.call('blockchain', 'addProvider', {
name: 'Foundry Provider',
+ isInjected: false,
provider: {
async sendAsync (payload, callback) {
try {
@@ -144,6 +147,7 @@ export class RunTab extends ViewPlugin {
await this.call('blockchain', 'addProvider', {
name: 'Wallet Connect',
+ isInjected: false,
provider: {
async sendAsync (payload, callback) {
try {
@@ -155,6 +159,50 @@ export class RunTab extends ViewPlugin {
}
}
})
+
+ await this.call('blockchain', 'addProvider', {
+ name: 'External Http Provider',
+ provider: {
+ async sendAsync (payload, callback) {
+ try {
+ const result = await udapp.call('basic-http-provider', 'sendAsync', payload)
+ callback(null, result)
+ } catch (e) {
+ callback(e)
+ }
+ }
+ }
+ })
+
+ await this.call('blockchain', 'addProvider', {
+ name: 'Optimism Provider',
+ isInjected: true,
+ provider: {
+ async sendAsync (payload, callback) {
+ try {
+ const result = await udapp.call('injected-optimism-provider', 'sendAsync', payload)
+ callback(null, result)
+ } catch (e) {
+ callback(e)
+ }
+ }
+ }
+ })
+
+ await this.call('blockchain', 'addProvider', {
+ name: 'Arbitrum One Provider',
+ isInjected: true,
+ provider: {
+ async sendAsync (payload, callback) {
+ try {
+ const result = await udapp.call('injected-arbitrum-one-provider', 'sendAsync', payload)
+ callback(null, result)
+ } catch (e) {
+ callback(e)
+ }
+ }
+ }
+ })
}
writeFile (fileName, content) {
diff --git a/apps/remix-ide/src/blockchain/blockchain.js b/apps/remix-ide/src/blockchain/blockchain.js
index 5f6d3ad6c6..3d6e885cec 100644
--- a/apps/remix-ide/src/blockchain/blockchain.js
+++ b/apps/remix-ide/src/blockchain/blockchain.js
@@ -464,7 +464,7 @@ export class Blockchain extends Plugin {
}
/**
- * This function send a tx only to javascript VM or testnet, will return an error for the mainnet
+ * This function send a tx only to Remix VM or testnet, will return an error for the mainnet
* SHOULD BE TAKEN CAREFULLY!
*
* @param {Object} tx - transaction.
@@ -530,6 +530,7 @@ export class Blockchain extends Plugin {
if (this.transactionContextAPI.getAddress) {
return this.transactionContextAPI.getAddress(function (err, address) {
if (err) return reject(err)
+ if (!address) return reject('"from" is not defined. Please make sure an account is selected. If you are using a public node, it is likely that no account will be provided. In that case, add the public node to your injected provider (type Metamask) and use injected provider in Remix.')
return resolve(address)
})
}
@@ -548,9 +549,18 @@ export class Blockchain extends Plugin {
const runTransaction = async () => {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
- const fromAddress = await getAccount()
- const value = await queryValue()
- const gasLimit = await getGasLimit()
+ let fromAddress
+ let value
+ let gasLimit
+ try {
+ fromAddress = await getAccount()
+ value = await queryValue()
+ gasLimit = await getGasLimit()
+ } catch (e) {
+ reject(e)
+ return
+ }
+
const tx = { to: args.to, data: args.data.dataHex, useCall: args.useCall, from: fromAddress, value: value, gasLimit: gasLimit, timestamp: args.data.timestamp }
const payLoad = { funAbi: args.data.funAbi, funArgs: args.data.funArgs, contractBytecode: args.data.contractBytecode, contractName: args.data.contractName, contractABI: args.data.contractABI, linkReferences: args.data.linkReferences }
diff --git a/apps/remix-ide/src/blockchain/execution-context.js b/apps/remix-ide/src/blockchain/execution-context.js
index e570b25056..a6286ed507 100644
--- a/apps/remix-ide/src/blockchain/execution-context.js
+++ b/apps/remix-ide/src/blockchain/execution-context.js
@@ -155,6 +155,9 @@ export class ExecutionContext {
infoCb('No injected Web3 provider found. Make sure your provider (e.g. MetaMask) is active and running (when recently activated you may have to reload the page).')
return cb()
} else {
+ if (injectedProvider && injectedProvider._metamask && injectedProvider._metamask.isUnlocked) {
+ if (!await injectedProvider._metamask.isUnlocked()) this.call('notification', 'toast', 'Please make sure the injected provider is unlocked (e.g Metamask).')
+ }
this.askPermission()
this.executionContext = context
web3.setProvider(injectedProvider)
@@ -164,16 +167,23 @@ export class ExecutionContext {
}
}
- if (context === 'web3') {
- confirmCb(cb)
- }
if (this.customNetWorks[context]) {
var network = this.customNetWorks[context]
- this.setProviderFromEndpoint(network.provider, { context: network.name }, (error) => {
- if (error) infoCb(error)
- cb()
- })
- }
+ if (!this.customNetWorks[context].isInjected) {
+ this.setProviderFromEndpoint(network.provider, { context: network.name }, (error) => {
+ if (error) infoCb(error)
+ cb()
+ })
+ } else {
+ // injected
+ this.askPermission()
+ this.executionContext = context
+ web3.setProvider(network.provider)
+ await this._updateChainContext()
+ this.event.trigger('contextChanged', [context])
+ return cb()
+ }
+ }
}
currentblockGasLimit () {
diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js
index bf8c5dccd3..fbe147a67b 100644
--- a/apps/remix-ide/src/remixAppManager.js
+++ b/apps/remix-ide/src/remixAppManager.js
@@ -19,7 +19,7 @@ const sensitiveCalls = {
}
export function isNative(name) {
- const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider']
+ const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider']
return nativePlugins.includes(name) || requiredModules.includes(name)
}
diff --git a/libs/remix-core-plugin/src/index.ts b/libs/remix-core-plugin/src/index.ts
index 420fe2e6d7..5018bca7e2 100644
--- a/libs/remix-core-plugin/src/index.ts
+++ b/libs/remix-core-plugin/src/index.ts
@@ -8,3 +8,4 @@ export { GistHandler } from './lib/gist-handler'
export * from './types/contract'
export { LinkLibraries, DeployLibraries } from './lib/link-libraries'
export { OpenZeppelinProxy } from './lib/openzeppelin-proxy'
+export { fetchContractFromEtherscan } from './lib/helpers/fetch-etherscan'
diff --git a/libs/remix-core-plugin/src/lib/helpers/fetch-etherscan.ts b/libs/remix-core-plugin/src/lib/helpers/fetch-etherscan.ts
index 32264c32fa..16f6b284b8 100644
--- a/libs/remix-core-plugin/src/lib/helpers/fetch-etherscan.ts
+++ b/libs/remix-core-plugin/src/lib/helpers/fetch-etherscan.ts
@@ -1,8 +1,11 @@
-export const fetchContractFromEtherscan = async (plugin, network, contractAddress, targetPath) => {
+export const fetchContractFromEtherscan = async (plugin, network, contractAddress, targetPath, key?) => {
let data
const compilationTargets = {}
+ let etherscanKey
- const etherscanKey = await plugin.call('config', 'getAppParameter', 'etherscan-access-token')
+ if (!key) etherscanKey = await plugin.call('config', 'getAppParameter', 'etherscan-access-token')
+ else etherscanKey = key
+
if (etherscanKey) {
const endpoint = network.id == 1 ? 'api.etherscan.io' : 'api-' + network.name + '.etherscan.io'
data = await fetch('https://' + endpoint + '/api?module=contract&action=getsourcecode&address=' + contractAddress + '&apikey=' + etherscanKey)
@@ -10,7 +13,7 @@ export const fetchContractFromEtherscan = async (plugin, network, contractAddres
// etherscan api doc https://docs.etherscan.io/api-endpoints/contracts
if (data.message === 'OK' && data.status === "1") {
if (data.result.length) {
- if (data.result[0].SourceCode === '') throw new Error('contract not verified in Etherscan')
+ if (data.result[0].SourceCode === '') throw new Error(`contract not verified on Etherscan ${network.name} network`)
if (data.result[0].SourceCode.startsWith('{')) {
data.result[0].SourceCode = JSON.parse(data.result[0].SourceCode.replace(/(?:\r\n|\r|\n)/g, '').replace(/^{{/,'{').replace(/}}$/,'}'))
}
diff --git a/libs/remix-lib/src/execution/forkAt.ts b/libs/remix-lib/src/execution/forkAt.ts
index c5f1a1e981..8e45ce9acf 100644
--- a/libs/remix-lib/src/execution/forkAt.ts
+++ b/libs/remix-lib/src/execution/forkAt.ts
@@ -50,6 +50,14 @@ const forks = {
{
number: 12965000,
name: 'london'
+ },
+ {
+ number: 13773000,
+ name: 'arrowGlacier'
+ },
+ {
+ number: 15050000,
+ name: 'grayGlacier'
}
],
3: [
diff --git a/libs/remix-lib/src/execution/txRunnerWeb3.ts b/libs/remix-lib/src/execution/txRunnerWeb3.ts
index 4483a41fe0..2300973b3a 100644
--- a/libs/remix-lib/src/execution/txRunnerWeb3.ts
+++ b/libs/remix-lib/src/execution/txRunnerWeb3.ts
@@ -85,7 +85,7 @@ export class TxRunnerWeb3 {
runInNode (from, to, data, value, gasLimit, useCall, timestamp, confirmCb, gasEstimationForceSend, promptCb, callback) {
const tx = { from: from, to: to, data: data, value: value }
-
+ if (!from) return callback('the value of "from" is not defined. Please make sure an account is selected.')
if (useCall) {
tx['gas'] = gasLimit
if (this._api && this._api.isVM()) tx['timestamp'] = timestamp
diff --git a/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx b/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx
index 12644f5fa7..17c6f604d6 100644
--- a/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx
+++ b/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from 'react'
-import { ModalDialog, ModalDialogProps } from '@remix-ui/modal-dialog'
+import { ModalDialog, ModalDialogProps, ValidationResult } from '@remix-ui/modal-dialog'
import { ModalTypes } from '../../types'
interface ModalWrapperProps extends ModalDialogProps {
@@ -29,12 +29,23 @@ const ModalWrapper = (props: ModalWrapperProps) => {
(props.cancelFn) ? props.cancelFn() : props.resolve(false)
}
- const createModalMessage = (defaultValue: string) => {
+ const onInputChanged = (event) => {
+ if (props.validationFn) {
+ const validation = props.validationFn(event.target.value)
+ setState(prevState => {
+ return { ...prevState, message: createModalMessage(props.defaultValue, validation), validation }
+ })
+ }
+ }
+
+ const createModalMessage = (defaultValue: string, validation: ValidationResult) => {
return (
<>
{props.message}
- >
- )
+
+ {!validation.valid && {validation.message}}
+ >
+ )
}
useEffect(() => {
@@ -47,13 +58,13 @@ const ModalWrapper = (props: ModalWrapperProps) => {
...props,
okFn: onFinishPrompt,
cancelFn: onCancelFn,
- message: createModalMessage(props.defaultValue)
+ message: createModalMessage(props.defaultValue, { valid: true })
})
break
default:
setState({
...props,
- okFn: (onOkFn),
+ okFn: onOkFn,
cancelFn: onCancelFn
})
break
@@ -67,8 +78,16 @@ const ModalWrapper = (props: ModalWrapperProps) => {
}
}, [props])
+ // reset the message and input if any, so when the modal is shown again it doesn't show the previous value.
+ const handleHide = () => {
+ setState(prevState => {
+ return { ...prevState, message: '' }
+ })
+ props.handleHide()
+ }
+
return (
-
+
)
}
export default ModalWrapper
diff --git a/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx b/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
index 2aca27c1be..701375265d 100644
--- a/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
+++ b/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
@@ -16,11 +16,11 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
}
const modal = (modalData: AppModal) => {
- const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn, data } = modalData
+ const { id, title, message, validationFn, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn, data } = modalData
return new Promise((resolve, reject) => {
dispatch({
type: modalActionTypes.setModal,
- payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn, resolve, next: onNextFn, data }
+ payload: { id, title, message, okLabel, validationFn, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn, resolve, next: onNextFn, data }
})
})
}
diff --git a/libs/remix-ui/app/src/lib/remix-app/interface/index.ts b/libs/remix-ui/app/src/lib/remix-app/interface/index.ts
index f31536678e..cc0521dd51 100644
--- a/libs/remix-ui/app/src/lib/remix-app/interface/index.ts
+++ b/libs/remix-ui/app/src/lib/remix-app/interface/index.ts
@@ -1,10 +1,16 @@
import { ModalTypes } from '../types'
+export type ValidationResult = {
+ valid: boolean,
+ message?: string
+}
+
export interface AppModal {
id: string
timestamp?: number
hide?: boolean
title: string
+ validationFn?: (value: string) => ValidationResult
// eslint-disable-next-line no-undef
message: string | JSX.Element
okLabel: string
diff --git a/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts b/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts
index a50a3dbd66..51641c654c 100644
--- a/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts
+++ b/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts
@@ -11,6 +11,7 @@ export const modalReducer = (state: ModalState = ModalInitialState, action: Moda
id: action.payload.id || timestamp.toString(),
hide: false,
title: action.payload.title,
+ validationFn: action.payload.validationFn,
message: action.payload.message,
okLabel: action.payload.okLabel,
okFn: action.payload.okFn,
diff --git a/libs/remix-ui/app/src/lib/remix-app/state/modals.ts b/libs/remix-ui/app/src/lib/remix-app/state/modals.ts
index 8332d60120..2b10dccbea 100644
--- a/libs/remix-ui/app/src/lib/remix-app/state/modals.ts
+++ b/libs/remix-ui/app/src/lib/remix-app/state/modals.ts
@@ -8,6 +8,7 @@ export const ModalInitialState: ModalState = {
hide: true,
title: '',
message: '',
+ validationFn: () => { return {valid: true, message: ''} },
okLabel: '',
okFn: () => { },
cancelLabel: '',
diff --git a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
index 25dd08705b..bd8008d0e3 100644
--- a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
+++ b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
@@ -331,7 +331,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
return { ...prevState, opt: { ...prevState.opt, debugWithGeneratedSources: checked } }
})
}} type="checkbox" title="Debug with generated sources" />
-
+
{ state.isLocalNodeUsed &&
{
diff --git a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
index 7787daeddf..46a5e76d9b 100644
--- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
+++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
@@ -416,6 +416,20 @@ export const EditorUI = (props: EditorUIProps) => {
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.US_MINUS, () => {
editor.updateOptions({ fontSize: editor.getOption(43).fontSize - 1 })
})
+
+ const editorService = editor._codeEditorService;
+ const openEditorBase = editorService.openCodeEditor.bind(editorService);
+ editorService.openCodeEditor = async (input, source) => {
+ const result = await openEditorBase(input, source)
+ if (input && input.resource && input.resource.path) {
+ try {
+ await props.plugin.call('fileManager', 'open', input.resource.path)
+ } catch (e) {
+ console.log(e)
+ }
+ }
+ return result
+ }
}
function handleEditorWillMount (monaco) {
diff --git a/libs/remix-ui/helper/src/index.ts b/libs/remix-ui/helper/src/index.ts
index 36d73ef523..9f050ea8b8 100644
--- a/libs/remix-ui/helper/src/index.ts
+++ b/libs/remix-ui/helper/src/index.ts
@@ -1,3 +1,4 @@
export * from './lib/remix-ui-helper'
export * from './lib/helper-components'
-export * from './lib/components/PluginViewWrapper'
\ No newline at end of file
+export * from './lib/components/PluginViewWrapper'
+export * from './lib/components/custom-dropdown'
\ No newline at end of file
diff --git a/libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx b/libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx
new file mode 100644
index 0000000000..5f0696a554
--- /dev/null
+++ b/libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx
@@ -0,0 +1,42 @@
+// The forwardRef is important!!
+
+import React, { Ref } from "react"
+
+// Dropdown needs access to the DOM node in order to position the Menu
+export const CustomToggle = React.forwardRef(({ children, onClick, icon, className = '' }: { children: React.ReactNode, onClick: (e) => void, icon: string, className: string }, ref: Ref) => (
+
+))
+
+// forwardRef again here!
+// Dropdown needs access to the DOM of the Menu to measure it
+export const CustomMenu = React.forwardRef(
+ ({ children, style, className, 'aria-labelledby': labeledBy }: { children: React.ReactNode, style?: React.CSSProperties, className: string, 'aria-labelledby'?: string }, ref: Ref) => {
+ return (
+
+
+ {
+ children
+ }
+
+
+ )
+ },
+)
diff --git a/libs/remix-ui/helper/src/lib/components/web3Dialog.tsx b/libs/remix-ui/helper/src/lib/components/web3Dialog.tsx
index 30055f322a..3412148086 100644
--- a/libs/remix-ui/helper/src/lib/components/web3Dialog.tsx
+++ b/libs/remix-ui/helper/src/lib/components/web3Dialog.tsx
@@ -24,10 +24,10 @@ export function Web3ProviderDialog (props: web3ProviderDialogProps) {
WARNING: It is not safe to use the --http.corsdomain flag with a wildcard: --http.corsdomain *
- For more info: Remix Docs on Web3 Provider
+ For more info: Remix Docs on Remix Provider
+
)
diff --git a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
index df1831deaa..b61563ef5b 100644
--- a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
+++ b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
@@ -57,10 +57,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
content: currentFile
})
enableAtAddress(true)
- } else if (/.(.sol)$/.exec(currentFile) ||
- /.(.vy)$/.exec(currentFile) || // vyper
- /.(.lex)$/.exec(currentFile) || // lexon
- /.(.contract)$/.exec(currentFile)) {
+ } else if (isContractFile(currentFile)) {
setAbiLabel({
display: 'none',
content: ''
@@ -115,16 +112,23 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
}
}
+ const isContractFile = (file) => {
+ return /.(.sol)$/.exec(file) ||
+ /.(.vy)$/.exec(file) || // vyper
+ /.(.lex)$/.exec(file) || // lexon
+ /.(.contract)$/.exec(file)
+ }
+
const enableAtAddress = (enable: boolean) => {
if (enable) {
setAtAddressOptions({
disabled: false,
- title: 'Interact with the given contract.'
+ title: 'Interact with the deployed contract - requires the .abi file or compiled .sol file to be selected in the editor (with the same compiler configuration)'
})
} else {
setAtAddressOptions({
disabled: true,
- title: loadedAddress ? 'âš Compile *.sol file or select *.abi file.' : 'âš Compile *.sol file or select *.abi file & then enter the address of deployed contract.'
+ title: loadedAddress ? 'Compile a *.sol file or select a *.abi file.' : 'To interact with a deployed contract, enter its address and compile its source *.sol file (with the same compiler settings) or select its .abi file in the editor. '
})
}
}
@@ -133,12 +137,12 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
if (enable) {
setContractOptions({
disabled: false,
- title: 'Select contract for Deploy or At Address.'
+ title: 'Select a compiled contract to deploy or to use with At Address.'
})
} else {
setContractOptions({
disabled: true,
- title: loadType === 'sol' ? 'âš Select and compile *.sol file to deploy or access a contract.' : 'âš Selected *.abi file allows accessing contracts, select and compile *.sol file to deploy and access one.'
+ title: loadType === 'sol' ? 'Select and compile *.sol file to deploy or access a contract.' : 'When there is a compiled .sol file, the choice of contracts to deploy or to use with AtAddress is made here.'
})
}
}
@@ -214,7 +218,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
-
: ''
}
-
or
+
or
{
const provider = props.providers.providerList.find(exEnv => exEnv.value === env)
const fork = provider.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'
+ context = context.startsWith('vm') ? 'vm' : context
props.setExecutionContext({ context, fork })
}
+ const currentProvider = props.providers.providerList.find(exEnv => exEnv.value === props.selectedEnv)
+ const bridges = {
+ 'Optimism Provider': 'https://www.optimism.io/apps/bridges',
+ 'Arbitrum One Provider': 'https://bridge.arbitrum.io/'
+ }
+
+
+ const isL2 = (provider) => provider && (provider.value === 'Optimism Provider' || provider.value === 'Arbitrum One Provider')
return (
- All transactions (deployed contracts and function executions) can be saved and replayed in
- another environment. e.g Transactions created in Javascript VM can be replayed in the Injected Web3.
-
-
-
-
-
-
+
+
+
+ Use live mode (Run transactions against latest compiled contracts).
+
+
+
+ Save {props.count} transaction(s) as scenario file.
+
+
+ }>
+
+
+
+ Run transaction(s) from the current scenario file.
+
+
+ }>
+
+
+