Merge branch 'master' into intl

pull/2581/head
drafish 3 years ago
commit c19fabbfb1
  1. 27
      README.md
  2. 18
      apps/remix-ide-e2e/src/commands/switchEnvironment.ts
  3. 10
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  4. 2
      apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts
  5. 8
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  6. 4
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  7. 8
      apps/remix-ide-e2e/src/tests/providers.test.ts
  8. 2
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  9. 6
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  10. 2
      apps/remix-ide-e2e/src/tests/transactionExecution.test.ts
  11. 1
      apps/remix-ide-e2e/src/types/index.d.ts
  12. 6
      apps/remix-ide/src/app.js
  13. 3
      apps/remix-ide/src/app/panels/file-panel.js
  14. 8
      apps/remix-ide/src/app/tabs/abstract-provider.tsx
  15. 2
      apps/remix-ide/src/app/tabs/external-http-provider.tsx
  16. 21
      apps/remix-ide/src/app/tabs/injected-arbitrum-one-provider.tsx
  17. 21
      apps/remix-ide/src/app/tabs/injected-optimism-provider.tsx
  18. 75
      apps/remix-ide/src/app/tabs/injected-provider.tsx
  19. 34
      apps/remix-ide/src/app/udapp/run-tab.js
  20. 15
      apps/remix-ide/src/blockchain/execution-context.js
  21. 2
      apps/remix-ide/src/remixAppManager.js
  22. 1
      libs/remix-ui/helper/src/index.ts
  23. 0
      libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx
  24. 6
      libs/remix-ui/plugin-manager/src/lib/components/LocalPluginForm.tsx
  25. 2
      libs/remix-ui/run-tab/src/lib/components/account.tsx
  26. 54
      libs/remix-ui/run-tab/src/lib/components/environment.tsx
  27. 2
      libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx
  28. 3
      libs/remix-ui/run-tab/src/lib/css/run-tab.css
  29. 8
      libs/remix-ui/workspace/src/lib/actions/index.ts
  30. 4
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  31. 3
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx

@ -1,13 +1,24 @@
[![CircleCI](https://circleci.com/gh/ethereum/remix-project.svg?style=svg)](https://circleci.com/gh/ethereum/remix-project) <p align="center">
[![Documentation Status](https://readthedocs.org/projects/docs/badge/?version=latest)](https://remix-ide.readthedocs.io/en/latest/index.html) <img src="./apps/remix-ide/src/assets/img/icon.png" alt="Remix Logo" width="200"/>
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md) </p>
![GitHub contributors](https://img.shields.io/github/contributors/ethereum/remix-project) <h3 align="center">Remix Project</h3>
[![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green)](https://github.com/ethereum/awesome-remix)
<div align="center">
[![CircleCI](https://img.shields.io/circleci/build/github/ethereum/remix-project?logo=circleci)](https://circleci.com/gh/ethereum/remix-project)
[![Documentation Status](https://readthedocs.org/projects/remix-ide/badge/?version=latest)](https://remix-ide.readthedocs.io/en/latest/index.html)
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat&logo=github)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md)
[![GitHub contributors](https://img.shields.io/github/contributors/ethereum/remix-project?style=flat&logo=github)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md)
[![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green?logo=awesomelists)](https://github.com/ethereum/awesome-remix)
![GitHub](https://img.shields.io/github/license/ethereum/remix-project) ![GitHub](https://img.shields.io/github/license/ethereum/remix-project)
[![Join the chat at https://gitter.im/ethereum/remix](https://badges.gitter.im/ethereum/remix.svg)](https://gitter.im/ethereum/remix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Gitter Chat](https://img.shields.io/badge/Gitter%20-chat-brightgreen?style=plastic&logo=gitter)](https://gitter.im/ethereum/remix)
[![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=social)](https://twitter.com/ethereumremix) [![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=flat&logo=twitter&color=green)](https://twitter.com/ethereumremix)
</div>
## Remix Project
# Remix Project
**Remix Project** is a rich toolset including Remix IDE, a comprehensive smart contract development tool. The Remix Project also includes Remix Plugin Engine and Remix Libraries which are low-level tools for wider use. **Remix Project** is a rich toolset including Remix IDE, a comprehensive smart contract development tool. The Remix Project also includes Remix Plugin Engine and Remix Libraries which are low-level tools for wider use.
## Remix IDE ## Remix IDE

@ -0,0 +1,18 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class switchEnvironment extends EventEmitter {
command (this: NightwatchBrowser, provider: string): NightwatchBrowser {
this.api.waitForElementVisible('[data-id="settingsSelectEnvOptions"]')
.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementVisible(`[data-id="dropdown-item-${provider}"]`)
.click(`[data-id="dropdown-item-${provider}"]`)
.perform((done) => {
done()
this.emit('complete')
})
return this
}
}
module.exports = switchEnvironment

@ -83,7 +83,7 @@ module.exports = {
browser browser
.openFile('Untitled.sol') .openFile('Untitled.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') .switchEnvironment('External Http Provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]') .waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(function () { .execute(function () {
const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any
@ -91,13 +91,7 @@ module.exports = {
modal.click() modal.click()
}) })
.pause(5000) .pause(5000)
.execute(function () { .waitForElementContainsText('#selectExEnvOptions button', 'External Http Provider')
const env: any = document.getElementById('selectExEnvOptions')
return env.value
}, [], function (result) {
browser.assert.ok(result.value === 'External Http Provider', 'Web3 Provider not selected')
})
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.pause(2000) .pause(2000)

@ -78,7 +78,7 @@ module.exports = {
browser browser
.openFile('Untitled.sol') .openFile('Untitled.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') .switchEnvironment('External Http Provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]') .waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(function () { .execute(function () {
const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any

@ -214,10 +214,10 @@ module.exports = {
.setSolidityCompilerVersion('soljson-v0.8.7+commit.e28d00a7.js') .setSolidityCompilerVersion('soljson-v0.8.7+commit.e28d00a7.js')
.addFile('useDebugNodes.sol', sources[5]['useDebugNodes.sol']) // compile contract .addFile('useDebugNodes.sol', sources[5]['useDebugNodes.sol']) // compile contract
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') // select web3 provider with debug nodes URL .switchEnvironment('External Http Provider') // select web3 provider with debug nodes URL
.clearValue('*[data-id="modalDialogCustomPromp"]') .clearValue('*[data-id="modalDialogCustomPromptText"]')
.setValue('*[data-id="modalDialogCustomPromp"]', 'https://remix-rinkeby.ethdevops.io') .setValue('*[data-id="modalDialogCustomPromptText"]', 'https://remix-rinkeby.ethdevops.io')
.modalFooterOKClick('basic-http-provider') .modalFooterOKClick()
.waitForElementPresent('*[title="Deploy - transact (not payable)"]', 65000) // wait for the compilation to succeed .waitForElementPresent('*[title="Deploy - transact (not payable)"]', 65000) // wait for the compilation to succeed
.clickLaunchIcon('debugger') .clickLaunchIcon('debugger')
.clearValue('*[data-id="debuggerTransactionInput"]') .clearValue('*[data-id="debuggerTransactionInput"]')

@ -188,7 +188,7 @@ module.exports = {
.frameParent() .frameParent()
.useCss() .useCss()
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.waitForElementContainsText('#selectExEnvOptions option:checked', 'Remix VM (Berlin)') .waitForElementContainsText('#selectExEnvOptions button', 'Remix VM (Berlin)')
.clickLaunchIcon('localPlugin') .clickLaunchIcon('localPlugin')
.useXpath() .useXpath()
// @ts-ignore // @ts-ignore
@ -391,7 +391,7 @@ module.exports = {
.useCss() .useCss()
.clickLaunchIcon('pluginManager') .clickLaunchIcon('pluginManager')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="Hardhat Provider"]') .switchEnvironment('Hardhat Provider')
.modalFooterOKClick('hardhat-provider') .modalFooterOKClick('hardhat-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom') // e.g Custom (1337) network .waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom') // e.g Custom (1337) network
.clickLaunchIcon('localPlugin') .clickLaunchIcon('localPlugin')

@ -10,7 +10,7 @@ module.exports = {
'Should switch to ganache provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) { 'Should switch to ganache provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000) browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="Ganache Provider"]') .switchEnvironment('Ganache Provider')
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.execute(() => { .execute(() => {
(document.querySelector('*[data-id="ganache-providerModalDialogModalBody-react"] input') as any).focus() (document.querySelector('*[data-id="ganache-providerModalDialogModalBody-react"] input') as any).focus()
@ -25,7 +25,7 @@ module.exports = {
}, },
'Should switch to ganache provider, use the default ganache URL and succeed to connect': function (browser: NightwatchBrowser) { 'Should switch to ganache provider, use the default ganache URL and succeed to connect': function (browser: NightwatchBrowser) {
browser.click('*[data-id="Ganache Provider"]') browser.switchEnvironment('Ganache Provider')
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.modalFooterOKClick('ganache-provider') .modalFooterOKClick('ganache-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (') .waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (')
@ -33,7 +33,7 @@ module.exports = {
'Should switch to foundry provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) { 'Should switch to foundry provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000) browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.click('*[data-id="Foundry Provider"]') .switchEnvironment('Foundry Provider')
.waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.execute(() => { .execute(() => {
(document.querySelector('*[data-id="foundry-providerModalDialogModalBody-react"] input') as any).focus() (document.querySelector('*[data-id="foundry-providerModalDialogModalBody-react"] input') as any).focus()
@ -48,7 +48,7 @@ module.exports = {
}, },
'Should switch to foundry provider, use the default foundry URL and succeed to connect': function (browser: NightwatchBrowser) { 'Should switch to foundry provider, use the default foundry URL and succeed to connect': function (browser: NightwatchBrowser) {
browser.click('*[data-id="Foundry Provider"]') browser.switchEnvironment('Foundry Provider')
.waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]') .waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.modalFooterOKClick('foundry-provider') .modalFooterOKClick('foundry-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (') .waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (')

@ -32,7 +32,7 @@ module.exports = {
'Should sign message using account key #group2': function (browser: NightwatchBrowser) { 'Should sign message using account key #group2': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="settingsRemixRunSignMsg"]') browser.waitForElementVisible('*[data-id="settingsRemixRunSignMsg"]')
.click('select[id="selectExEnvOptions"] option[value="vm-berlin"]') .switchEnvironment('vm-berlin')
.pause(2000) .pause(2000)
.click('*[data-id="settingsRemixRunSignMsg"]') .click('*[data-id="settingsRemixRunSignMsg"]')
.pause(2000) .pause(2000)

@ -46,11 +46,11 @@ module.exports = {
.waitForElementContainsText('*[data-id="terminalJournal"]', '["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db","0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB","0x617F2E2fD72FD9D5503197092aC168c91465E7f2","0x17F6AD8Ef982297579C203069C1DbfFE4348c372","0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678","0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7","0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C","0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c","0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C","0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB","0x583031D1113aD414F02576BD6afaBfb302140225","0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]') .waitForElementContainsText('*[data-id="terminalJournal"]', '["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db","0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB","0x617F2E2fD72FD9D5503197092aC168c91465E7f2","0x17F6AD8Ef982297579C203069C1DbfFE4348c372","0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678","0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7","0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C","0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c","0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C","0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB","0x583031D1113aD414F02576BD6afaBfb302140225","0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]')
}, },
'Call web3.eth.getAccounts() using Web3 Provider #group5': function (browser: NightwatchBrowser) { 'Call web3.eth.getAccounts() using External Http Provider #group5': function (browser: NightwatchBrowser) {
browser browser
.click('*[data-id="terminalClearConsole"]') // clear the terminal .click('*[data-id="terminalClearConsole"]') // clear the terminal
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') .switchEnvironment('External Http Provider')
.modalFooterOKClick('basic-http-provider') .modalFooterOKClick('basic-http-provider')
.executeScript('web3.eth.getAccounts()') .executeScript('web3.eth.getAccounts()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '["', 60000) // we check if an array is present, don't need to check for the content .waitForElementContainsText('*[data-id="terminalJournal"]', '["', 60000) // we check if an array is present, don't need to check for the content
@ -95,7 +95,7 @@ module.exports = {
browser browser
.clickLaunchIcon('settings') .clickLaunchIcon('settings')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsVMLondonMode"]') .switchEnvironment('vm-london')
.click('*[data-id="terminalClearConsole"]') // clear the terminal .click('*[data-id="terminalClearConsole"]') // clear the terminal
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.click('*[data-id="treeViewDivtreeViewItem"]') // make sure we create the file at the root folder .click('*[data-id="treeViewDivtreeViewItem"]') // make sure we create the file at the root folder

@ -161,7 +161,7 @@ module.exports = {
browser browser
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.clearTransactions() .clearTransactions()
.click('*[data-id="settingsVMLondonMode"]') // switch to London fork .switchEnvironment('vm-london') // switch to London fork
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('.udapp_contractActionsContainerSingle > button') .click('.udapp_contractActionsContainerSingle > button')
.clickInstance(0) .clickInstance(0)

@ -64,6 +64,7 @@ declare module 'nightwatch' {
getBrowserLogs (this: NightwatchBrowser): NightwatchBrowser getBrowserLogs (this: NightwatchBrowser): NightwatchBrowser
currentSelectedFileIs (name: string): NightwatchBrowser, currentSelectedFileIs (name: string): NightwatchBrowser,
switchWorkspace: (workspaceName: string) => NightwatchBrowser switchWorkspace: (workspaceName: string) => NightwatchBrowser
switchEnvironment: (provider: string) => NightwatchBrowser
} }
export interface NightwatchBrowser { export interface NightwatchBrowser {

@ -30,6 +30,8 @@ import { HardhatProvider } from './app/tabs/hardhat-provider'
import { GanacheProvider } from './app/tabs/ganache-provider' import { GanacheProvider } from './app/tabs/ganache-provider'
import { FoundryProvider } from './app/tabs/foundry-provider' import { FoundryProvider } from './app/tabs/foundry-provider'
import { ExternalHttpProvider } from './app/tabs/external-http-provider' import { ExternalHttpProvider } from './app/tabs/external-http-provider'
import { Injected0ptimismProvider } from './app/tabs/injected-optimism-provider'
import { InjectedArbitrumOneProvider } from './app/tabs/injected-arbitrum-one-provider'
const isElectron = require('is-electron') const isElectron = require('is-electron')
@ -185,6 +187,8 @@ class AppComponent {
const ganacheProvider = new GanacheProvider(blockchain) const ganacheProvider = new GanacheProvider(blockchain)
const foundryProvider = new FoundryProvider(blockchain) const foundryProvider = new FoundryProvider(blockchain)
const externalHttpProvider = new ExternalHttpProvider(blockchain) const externalHttpProvider = new ExternalHttpProvider(blockchain)
const injected0ptimismProvider = new Injected0ptimismProvider(blockchain)
const injectedArbitrumOneProvider = new InjectedArbitrumOneProvider(blockchain)
// ----------------- convert offset to line/column service ----------- // ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter() const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
Registry.getInstance().put({ Registry.getInstance().put({
@ -244,6 +248,8 @@ class AppComponent {
ganacheProvider, ganacheProvider,
foundryProvider, foundryProvider,
externalHttpProvider, externalHttpProvider,
injected0ptimismProvider,
injectedArbitrumOneProvider,
this.walkthroughService, this.walkthroughService,
search search
]) ])

@ -153,6 +153,9 @@ module.exports = class Filepanel extends ViewPlugin {
const workspaceProvider = this.fileProviders.workspace const workspaceProvider = this.fileProviders.workspace
this.currentWorkspaceMetadata = { name: workspace.name, isLocalhost: workspace.isLocalhost, absolutePath: `${workspaceProvider.workspacesPath}/${workspace.name}` } this.currentWorkspaceMetadata = { name: workspace.name, isLocalhost: workspace.isLocalhost, absolutePath: `${workspaceProvider.workspacesPath}/${workspace.name}` }
if (workspace.name !== " - connect to localhost - ") {
localStorage.setItem('currentWorkspace', workspace.name)
}
this.emit('setWorkspace', workspace) this.emit('setWorkspace', workspace)
} }

@ -3,21 +3,21 @@ import { AppModal, AlertModal, ModalTypes } from '@remix-ui/app'
import { Blockchain } from '../../blockchain/blockchain' import { Blockchain } from '../../blockchain/blockchain'
import { ethers } from 'ethers' import { ethers } from 'ethers'
type JsonDataRequest = { export type JsonDataRequest = {
id: number, id: number,
jsonrpc: string // version jsonrpc: string // version
method: string, method: string,
params: Array<any>, params: Array<any>,
} }
type JsonDataResult = { export type JsonDataResult = {
id: number, id: number,
jsonrpc: string // version jsonrpc: string // version
result: any result: any
} }
type RejectRequest = (error: Error) => void export type RejectRequest = (error: Error) => void
type SuccessRequest = (data: JsonDataResult) => void export type SuccessRequest = (data: JsonDataResult) => void
export abstract class AbstractProvider extends Plugin { export abstract class AbstractProvider extends Plugin {
provider: ethers.providers.JsonRpcProvider provider: ethers.providers.JsonRpcProvider

@ -30,7 +30,7 @@ export class ExternalHttpProvider extends AbstractProvider {
<br /> <br />
<b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b> <b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b>
<br /> <br />
<br />For more info: <a href="https://remix-ide.readthedocs.io/en/latest/run.html#more-about-web3-provider" target="_blank" rel="noreferrer">Remix Docs on Web3 Provider</a> <br />For more info: <a href="https://remix-ide.readthedocs.io/en/latest/run.html#more-about-web3-provider" target="_blank" rel="noreferrer">Remix Docs on External HTTP Provider</a>
<br /> <br />
<br /> <br />
External HTTP Provider Endpoint External HTTP Provider Endpoint

@ -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']
}
}

@ -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']
}
}

@ -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<string>
constructor (profile) {
super(profile)
if ((window as any).ethereum) {
this.provider = new Web3((window as any).ethereum)
}
}
sendAsync (data: JsonDataRequest): Promise<any> {
return new Promise((resolve, reject) => {
this.sendAsyncInternal(data, resolve, reject)
})
}
private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> {
// 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<string>) => {
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
}
}

@ -103,6 +103,7 @@ export class RunTab extends ViewPlugin {
await this.call('blockchain', 'addProvider', { await this.call('blockchain', 'addProvider', {
name: 'Hardhat Provider', name: 'Hardhat Provider',
isInjected: false,
provider: { provider: {
async sendAsync (payload, callback) { async sendAsync (payload, callback) {
try { try {
@ -117,6 +118,7 @@ export class RunTab extends ViewPlugin {
await this.call('blockchain', 'addProvider', { await this.call('blockchain', 'addProvider', {
name: 'Ganache Provider', name: 'Ganache Provider',
isInjected: false,
provider: { provider: {
async sendAsync (payload, callback) { async sendAsync (payload, callback) {
try { try {
@ -131,6 +133,7 @@ export class RunTab extends ViewPlugin {
await this.call('blockchain', 'addProvider', { await this.call('blockchain', 'addProvider', {
name: 'Foundry Provider', name: 'Foundry Provider',
isInjected: false,
provider: { provider: {
async sendAsync (payload, callback) { async sendAsync (payload, callback) {
try { try {
@ -145,6 +148,7 @@ export class RunTab extends ViewPlugin {
await this.call('blockchain', 'addProvider', { await this.call('blockchain', 'addProvider', {
name: 'Wallet Connect', name: 'Wallet Connect',
isInjected: false,
provider: { provider: {
async sendAsync (payload, callback) { async sendAsync (payload, callback) {
try { try {
@ -170,6 +174,36 @@ export class RunTab extends ViewPlugin {
} }
} }
}) })
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) { writeFile (fileName, content) {

@ -152,9 +152,12 @@ export class ExecutionContext {
if (context === 'injected') { if (context === 'injected') {
if (injectedProvider === undefined) { if (injectedProvider === undefined) {
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).') infoCb('No injected 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() return cb()
} else { } 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.askPermission()
this.executionContext = context this.executionContext = context
web3.setProvider(injectedProvider) web3.setProvider(injectedProvider)
@ -166,10 +169,20 @@ export class ExecutionContext {
if (this.customNetWorks[context]) { if (this.customNetWorks[context]) {
var network = this.customNetWorks[context] var network = this.customNetWorks[context]
if (!this.customNetWorks[context].isInjected) {
this.setProviderFromEndpoint(network.provider, { context: network.name }, (error) => { this.setProviderFromEndpoint(network.provider, { context: network.name }, (error) => {
if (error) infoCb(error) if (error) infoCb(error)
cb() cb()
}) })
} else {
// injected
this.askPermission()
this.executionContext = context
web3.setProvider(network.provider)
await this._updateChainContext()
this.event.trigger('contextChanged', [context])
return cb()
}
} }
} }

@ -19,7 +19,7 @@ const sensitiveCalls = {
} }
export function isNative(name) { export function isNative(name) {
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-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) return nativePlugins.includes(name) || requiredModules.includes(name)
} }

@ -1,3 +1,4 @@
export * from './lib/remix-ui-helper' export * from './lib/remix-ui-helper'
export * from './lib/helper-components' export * from './lib/helper-components'
export * from './lib/components/PluginViewWrapper' export * from './lib/components/PluginViewWrapper'
export * from './lib/components/custom-dropdown'

@ -222,7 +222,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor
type="radio" type="radio"
name="location" name="location"
value="sidePanel" value="sidePanel"
id="none" id="localPluginRadioButtonsidePanelSidePanel"
data-id='localPluginRadioButtonsidePanel' data-id='localPluginRadioButtonsidePanel'
checked={location === 'sidePanel'} checked={location === 'sidePanel'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} /> onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} />
@ -234,7 +234,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor
type="radio" type="radio"
name="location" name="location"
value="mainPanel" value="mainPanel"
id="none" id="localPluginRadioButtonsidePanelMainPanel"
data-id='localPluginRadioButtonmainPanel' data-id='localPluginRadioButtonmainPanel'
checked={location === 'mainPanel'} checked={location === 'mainPanel'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} /> onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} />
@ -246,7 +246,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor
type="radio" type="radio"
name="location" name="location"
value="none" value="none"
id="none" id="localPluginRadioButtonsidePanelNone"
data-id='localPluginRadioButtonnone' data-id='localPluginRadioButtonnone'
checked={location === 'none'} checked={location === 'none'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} /> onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} />

@ -25,7 +25,7 @@ export function AccountUI (props: AccountProps) {
case 'injected': case 'injected':
setPlusOpt({ setPlusOpt({
classList: 'udapp_disableMouseEvents', classList: 'udapp_disableMouseEvents',
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)." title: "Unfortunately it's not possible to create an account using injected provider. Please create the account directly from your provider (i.e metamask or other of the same type)."
}) })
break break

@ -2,11 +2,14 @@
import React from 'react' import React from 'react'
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl'
import { EnvironmentProps } from '../types' import { EnvironmentProps } from '../types'
import { Dropdown } from 'react-bootstrap'
import { CustomMenu, CustomToggle } from '@remix-ui/helper'
import { OverlayTrigger, Tooltip } from 'react-bootstrap' // eslint-disable-line
export function EnvironmentUI (props: EnvironmentProps) { export function EnvironmentUI (props: EnvironmentProps) {
const handleChangeExEnv = (env: string) => { const handleChangeExEnv = (env: string) => {
const provider = props.providers.providerList.find(exEnv => exEnv.value === env) 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) const fork = provider.fork // can be undefined if connected to an external source (External Http Provider / injected)
let context = provider.value let context = provider.value
context = context.startsWith('vm') ? 'vm' : context context = context.startsWith('vm') ? 'vm' : context
@ -16,22 +19,55 @@ export function EnvironmentUI (props: EnvironmentProps) {
const intl = useIntl() const intl = useIntl()
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 ( return (
<div className="udapp_crow"> <div className="udapp_crow">
<label id="selectExEnv" className="udapp_settingsLabel"> <label id="selectExEnv" className="udapp_settingsLabel">
<FormattedMessage id='udapp.environment' defaultMessage='Environment' /> <FormattedMessage id='udapp.environment' defaultMessage='Environment' />
<OverlayTrigger placement={'right'} overlay={
<Tooltip className="text-nowrap" id="info-recorder">
<span>Open chainlist and add a new provider for the chain you want to interact to.</span>
</Tooltip>
}>
<a href='https://chainlist.org/' target='_blank'><i style={{ fontSize: 'medium' }} className={'ml-2 fad fa-plug'} aria-hidden="true"></i></a>
</OverlayTrigger>
</label> </label>
<div className="udapp_environment"> <div className="udapp_environment">
<select id="selectExEnvOptions" data-id="settingsSelectEnvOptions" className="form-control udapp_select custom-select" value={props.selectedEnv || ''} onChange={(e) => { handleChangeExEnv(e.target.value) }}>
<Dropdown id="selectExEnvOptions" data-id="settingsSelectEnvOptions" className='udapp_selectExEnvOptions'>
<Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control" icon={null}>
{ isL2(currentProvider) && 'L2 - '}
{ currentProvider && currentProvider.content }
{ currentProvider && bridges[currentProvider.value] && <OverlayTrigger placement={'right'} overlay={
<Tooltip className="text-nowrap" id="info-recorder">
<span>Click to open a bridge for converting L1 mainnet ETH to the selected network currency.</span>
</Tooltip>
}>
<i style={{ fontSize: 'medium' }} className={'ml-2 fal fa-plug'} aria-hidden="true" onClick={() => { window.open(bridges[currentProvider.value], '_blank') }}></i>
</OverlayTrigger>}
</Dropdown.Toggle>
<Dropdown.Menu as={CustomMenu} className='w-100 custom-dropdown-items' data-id="custom-dropdown-items" >
{ {
props.providers.providerList.map((provider, index) => props.providers.providerList.map(({ content, value }, index) => (
<option id={provider.id} key={index} data-id={provider.dataId} <Dropdown.Item
title={provider.title} key={index}
value={provider.value}> { provider.content } onClick={() => {
</option> handleChangeExEnv(value)
) }}
data-id={`dropdown-item-${value}`}
>
<span className="pl-3">{ isL2({ value }) && 'L2 - ' }{ content }</span>
</Dropdown.Item>
))
} }
</select> </Dropdown.Menu>
</Dropdown>
<a href="https://remix-ide.readthedocs.io/en/latest/run.html#environment" target="_blank" rel="noreferrer"> <a href="https://remix-ide.readthedocs.io/en/latest/run.html#environment" target="_blank" rel="noreferrer">
<i <i
className="udapp_infoDeployAction ml-2 fas fa-info" className="udapp_infoDeployAction ml-2 fas fa-info"

@ -30,7 +30,7 @@ export function RecorderUI (props: RecorderProps) {
<div className="ml-2 mb-2 badge badge-pill badge-primary" title="The number of recorded transactions">{props.count}</div> <div className="ml-2 mb-2 badge badge-pill badge-primary" title="The number of recorded transactions">{props.count}</div>
<OverlayTrigger placement={'right'} overlay={ <OverlayTrigger placement={'right'} overlay={
<Tooltip className="text-nowrap" id="info-recorder"> <Tooltip className="text-nowrap" id="info-recorder">
<span>Save transactions (deployed contracts and function executions) and replay them in another environment. <br/> e.g Transactions created in Javascript VM can be replayed in the Injected Web3. <span>Save transactions (deployed contracts and function executions) and replay them in another environment. <br/> e.g Transactions created in Remix VM can be replayed in the Injected Provider.
</span> </span>
</Tooltip> </Tooltip>
}> }>

@ -529,4 +529,7 @@
text-decoration: none; text-decoration: none;
background-color: #007aa6; background-color: #007aa6;
} }
.udapp_selectExEnvOptions {
width: 100%;
}

@ -127,6 +127,14 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(route)) plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(route))
} else await basicWorkspaceInit(workspaces, workspaceProvider) } else await basicWorkspaceInit(workspaces, workspaceProvider)
} else await basicWorkspaceInit(workspaces, workspaceProvider) } else await basicWorkspaceInit(workspaces, workspaceProvider)
} else if (localStorage.getItem("currentWorkspace")) {
const index = workspaces.findIndex(element => element.name == localStorage.getItem("currentWorkspace"))
if (index !== -1) {
const name = localStorage.getItem("currentWorkspace")
workspaceProvider.setWorkspace(name)
plugin.setWorkspace({ name: name, isLocalhost: false })
dispatch(setCurrentWorkspace({ name: name, isGitRepo: false }))
}
} else { } else {
await basicWorkspaceInit(workspaces, workspaceProvider) await basicWorkspaceInit(workspaces, workspaceProvider)
} }

@ -5,7 +5,9 @@ import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
import { FileSystemContext } from '../contexts' import { FileSystemContext } from '../contexts'
import { browserReducer, browserInitialState } from '../reducers/workspace' import { browserReducer, browserInitialState } from '../reducers/workspace'
import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder, deletePath, renamePath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip, cloneRepository } from '../actions' import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder,
deletePath, renamePath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace,
fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip, cloneRepository } from '../actions'
import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types' import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types'
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Workspace } from '../remix-ui-workspace' import { Workspace } from '../remix-ui-workspace'

@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef, useContext } from 'react' // eslint-disable-line import React, { useState, useEffect, useRef, useContext } from 'react' // eslint-disable-line
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl'
import { Dropdown } from 'react-bootstrap' import { Dropdown } from 'react-bootstrap'
import { CustomMenu, CustomToggle } from './components/custom-dropdown' import { CustomMenu, CustomToggle } from '@remix-ui/helper'
import { FileExplorer } from './components/file-explorer' // eslint-disable-line import { FileExplorer } from './components/file-explorer' // eslint-disable-line
import { FileSystemContext } from './contexts' import { FileSystemContext } from './contexts'
import './css/remix-ui-workspace.css' import './css/remix-ui-workspace.css'
@ -21,6 +21,7 @@ export function Workspace () {
const cloneUrlRef = useRef<HTMLInputElement>() const cloneUrlRef = useRef<HTMLInputElement>()
useEffect(() => { useEffect(() => {
setCurrentWorkspace(localStorage.getItem('currentWorkspace') ? localStorage.getItem('currentWorkspace') : '')
resetFocus() resetFocus()
}, []) }, [])

Loading…
Cancel
Save