diff --git a/README.md b/README.md index aea4299352..06fd3fbe16 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,24 @@ -[![CircleCI](https://circleci.com/gh/ethereum/remix-project.svg?style=svg)](https://circleci.com/gh/ethereum/remix-project) -[![Documentation Status](https://readthedocs.org/projects/docs/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)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md) -![GitHub contributors](https://img.shields.io/github/contributors/ethereum/remix-project) -[![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green)](https://github.com/ethereum/awesome-remix) +

+ Remix Logo +

+

Remix Project

+ +
+ + +[![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) -[![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) -[![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=social)](https://twitter.com/ethereumremix) +[![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=flat&logo=twitter&color=green)](https://twitter.com/ethereumremix) + +
+ +## 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 IDE diff --git a/apps/remix-ide-e2e/src/commands/switchEnvironment.ts b/apps/remix-ide-e2e/src/commands/switchEnvironment.ts new file mode 100644 index 0000000000..03564ced41 --- /dev/null +++ b/apps/remix-ide-e2e/src/commands/switchEnvironment.ts @@ -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 diff --git a/apps/remix-ide-e2e/src/tests/ballot.test.ts b/apps/remix-ide-e2e/src/tests/ballot.test.ts index 9d2e9c5b07..1c8383f047 100644 --- a/apps/remix-ide-e2e/src/tests/ballot.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot.test.ts @@ -83,7 +83,7 @@ module.exports = { browser .openFile('Untitled.sol') .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"]') .execute(function () { const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any @@ -91,13 +91,7 @@ module.exports = { modal.click() }) .pause(5000) - .execute(function () { - const env: any = document.getElementById('selectExEnvOptions') - - return env.value - }, [], function (result) { - browser.assert.ok(result.value === 'External Http Provider', 'Web3 Provider not selected') - }) + .waitForElementContainsText('#selectExEnvOptions button', 'External Http Provider') .clickLaunchIcon('solidity') .clickLaunchIcon('udapp') .pause(2000) diff --git a/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts b/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts index 95696e3882..9a36ad67f4 100644 --- a/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts @@ -78,7 +78,7 @@ module.exports = { browser .openFile('Untitled.sol') .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"]') .execute(function () { const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any diff --git a/apps/remix-ide-e2e/src/tests/debugger.test.ts b/apps/remix-ide-e2e/src/tests/debugger.test.ts index 9b8da7e7df..19aa196b60 100644 --- a/apps/remix-ide-e2e/src/tests/debugger.test.ts +++ b/apps/remix-ide-e2e/src/tests/debugger.test.ts @@ -214,10 +214,10 @@ module.exports = { .setSolidityCompilerVersion('soljson-v0.8.7+commit.e28d00a7.js') .addFile('useDebugNodes.sol', sources[5]['useDebugNodes.sol']) // compile contract .clickLaunchIcon('udapp') - .click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') // select web3 provider with debug nodes URL - .clearValue('*[data-id="modalDialogCustomPromp"]') - .setValue('*[data-id="modalDialogCustomPromp"]', 'https://remix-rinkeby.ethdevops.io') - .modalFooterOKClick('basic-http-provider') + .switchEnvironment('External Http Provider') // select web3 provider with debug nodes URL + .clearValue('*[data-id="modalDialogCustomPromptText"]') + .setValue('*[data-id="modalDialogCustomPromptText"]', 'https://remix-rinkeby.ethdevops.io') + .modalFooterOKClick() .waitForElementPresent('*[title="Deploy - transact (not payable)"]', 65000) // wait for the compilation to succeed .clickLaunchIcon('debugger') .clearValue('*[data-id="debuggerTransactionInput"]') diff --git a/apps/remix-ide-e2e/src/tests/plugin_api.ts b/apps/remix-ide-e2e/src/tests/plugin_api.ts index f05a2fe130..c7ef41a231 100644 --- a/apps/remix-ide-e2e/src/tests/plugin_api.ts +++ b/apps/remix-ide-e2e/src/tests/plugin_api.ts @@ -188,7 +188,7 @@ module.exports = { .frameParent() .useCss() .clickLaunchIcon('udapp') - .waitForElementContainsText('#selectExEnvOptions option:checked', 'Remix VM (Berlin)') + .waitForElementContainsText('#selectExEnvOptions button', 'Remix VM (Berlin)') .clickLaunchIcon('localPlugin') .useXpath() // @ts-ignore @@ -391,7 +391,7 @@ module.exports = { .useCss() .clickLaunchIcon('pluginManager') .clickLaunchIcon('udapp') - .click('*[data-id="Hardhat Provider"]') + .switchEnvironment('Hardhat Provider') .modalFooterOKClick('hardhat-provider') .waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom') // e.g Custom (1337) network .clickLaunchIcon('localPlugin') diff --git a/apps/remix-ide-e2e/src/tests/providers.test.ts b/apps/remix-ide-e2e/src/tests/providers.test.ts index fe6dcde8bf..5cb1048d91 100644 --- a/apps/remix-ide-e2e/src/tests/providers.test.ts +++ b/apps/remix-ide-e2e/src/tests/providers.test.ts @@ -10,7 +10,7 @@ module.exports = { 'Should switch to ganache provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) { browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000) .clickLaunchIcon('udapp') - .click('*[data-id="Ganache Provider"]') + .switchEnvironment('Ganache Provider') .waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]') .execute(() => { (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) { - browser.click('*[data-id="Ganache Provider"]') + browser.switchEnvironment('Ganache Provider') .waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]') .modalFooterOKClick('ganache-provider') .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) { browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000) - .click('*[data-id="Foundry Provider"]') + .switchEnvironment('Foundry Provider') .waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]') .execute(() => { (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) { - browser.click('*[data-id="Foundry Provider"]') + browser.switchEnvironment('Foundry Provider') .waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]') .modalFooterOKClick('foundry-provider') .waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (') diff --git a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts index dc00530bdd..6d87899d79 100644 --- a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts +++ b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts @@ -32,7 +32,7 @@ module.exports = { 'Should sign message using account key #group2': function (browser: NightwatchBrowser) { browser.waitForElementVisible('*[data-id="settingsRemixRunSignMsg"]') - .click('select[id="selectExEnvOptions"] option[value="vm-berlin"]') + .switchEnvironment('vm-berlin') .pause(2000) .click('*[data-id="settingsRemixRunSignMsg"]') .pause(2000) diff --git a/apps/remix-ide-e2e/src/tests/terminal.test.ts b/apps/remix-ide-e2e/src/tests/terminal.test.ts index 59af766031..b958b7d42d 100644 --- a/apps/remix-ide-e2e/src/tests/terminal.test.ts +++ b/apps/remix-ide-e2e/src/tests/terminal.test.ts @@ -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"]') }, - 'Call web3.eth.getAccounts() using Web3 Provider #group5': function (browser: NightwatchBrowser) { + 'Call web3.eth.getAccounts() using External Http Provider #group5': function (browser: NightwatchBrowser) { browser .click('*[data-id="terminalClearConsole"]') // clear the terminal .clickLaunchIcon('udapp') - .click('*[data-id="settingsSelectEnvOptions"] *[data-id="External Http Provider"]') + .switchEnvironment('External Http Provider') .modalFooterOKClick('basic-http-provider') .executeScript('web3.eth.getAccounts()') .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 .clickLaunchIcon('settings') .clickLaunchIcon('udapp') - .click('*[data-id="settingsVMLondonMode"]') + .switchEnvironment('vm-london') .click('*[data-id="terminalClearConsole"]') // clear the terminal .clickLaunchIcon('filePanel') .click('*[data-id="treeViewDivtreeViewItem"]') // make sure we create the file at the root folder diff --git a/apps/remix-ide-e2e/src/tests/transactionExecution.test.ts b/apps/remix-ide-e2e/src/tests/transactionExecution.test.ts index 3eac406fcd..842da3b3c7 100644 --- a/apps/remix-ide-e2e/src/tests/transactionExecution.test.ts +++ b/apps/remix-ide-e2e/src/tests/transactionExecution.test.ts @@ -161,7 +161,7 @@ module.exports = { browser .clickLaunchIcon('udapp') .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 .click('.udapp_contractActionsContainerSingle > button') .clickInstance(0) diff --git a/apps/remix-ide-e2e/src/types/index.d.ts b/apps/remix-ide-e2e/src/types/index.d.ts index eccd50bad5..9ba94f470b 100644 --- a/apps/remix-ide-e2e/src/types/index.d.ts +++ b/apps/remix-ide-e2e/src/types/index.d.ts @@ -64,6 +64,7 @@ declare module 'nightwatch' { getBrowserLogs (this: NightwatchBrowser): NightwatchBrowser currentSelectedFileIs (name: string): NightwatchBrowser, switchWorkspace: (workspaceName: string) => NightwatchBrowser + switchEnvironment: (provider: string) => NightwatchBrowser } export interface NightwatchBrowser { diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 6037f1bfbb..6adce5ec67 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -30,6 +30,8 @@ import { HardhatProvider } from './app/tabs/hardhat-provider' import { GanacheProvider } from './app/tabs/ganache-provider' import { FoundryProvider } from './app/tabs/foundry-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') @@ -185,6 +187,8 @@ class AppComponent { const ganacheProvider = new GanacheProvider(blockchain) const foundryProvider = new FoundryProvider(blockchain) const externalHttpProvider = new ExternalHttpProvider(blockchain) + const injected0ptimismProvider = new Injected0ptimismProvider(blockchain) + const injectedArbitrumOneProvider = new InjectedArbitrumOneProvider(blockchain) // ----------------- convert offset to line/column service ----------- const offsetToLineColumnConverter = new OffsetToLineColumnConverter() Registry.getInstance().put({ @@ -244,6 +248,8 @@ class AppComponent { ganacheProvider, foundryProvider, externalHttpProvider, + injected0ptimismProvider, + injectedArbitrumOneProvider, this.walkthroughService, search ]) diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index 2f41aa2b8d..7b46d54504 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -153,6 +153,9 @@ module.exports = class Filepanel extends ViewPlugin { const workspaceProvider = this.fileProviders.workspace 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) } diff --git a/apps/remix-ide/src/app/tabs/abstract-provider.tsx b/apps/remix-ide/src/app/tabs/abstract-provider.tsx index ff924a618a..31b1a4e317 100644 --- a/apps/remix-ide/src/app/tabs/abstract-provider.tsx +++ b/apps/remix-ide/src/app/tabs/abstract-provider.tsx @@ -3,21 +3,21 @@ import { AppModal, AlertModal, ModalTypes } from '@remix-ui/app' import { Blockchain } from '../../blockchain/blockchain' import { ethers } from 'ethers' -type JsonDataRequest = { +export type JsonDataRequest = { id: number, jsonrpc: string // version method: string, params: Array, } -type JsonDataResult = { +export type JsonDataResult = { id: number, jsonrpc: string // version result: any } -type RejectRequest = (error: Error) => void -type SuccessRequest = (data: JsonDataResult) => void +export type RejectRequest = (error: Error) => void +export type SuccessRequest = (data: JsonDataResult) => void export abstract class AbstractProvider extends Plugin { provider: ethers.providers.JsonRpcProvider diff --git a/apps/remix-ide/src/app/tabs/external-http-provider.tsx b/apps/remix-ide/src/app/tabs/external-http-provider.tsx index 5a22b48ba4..71c7c249c0 100644 --- a/apps/remix-ide/src/app/tabs/external-http-provider.tsx +++ b/apps/remix-ide/src/app/tabs/external-http-provider.tsx @@ -30,7 +30,7 @@ export class ExternalHttpProvider extends AbstractProvider {
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 External HTTP Provider

External HTTP Provider Endpoint 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/udapp/run-tab.js b/apps/remix-ide/src/app/udapp/run-tab.js index f465b88bcc..8475a92848 100644 --- a/apps/remix-ide/src/app/udapp/run-tab.js +++ b/apps/remix-ide/src/app/udapp/run-tab.js @@ -103,6 +103,7 @@ export class RunTab extends ViewPlugin { await this.call('blockchain', 'addProvider', { name: 'Hardhat Provider', + isInjected: false, provider: { async sendAsync (payload, callback) { try { @@ -117,6 +118,7 @@ export class RunTab extends ViewPlugin { await this.call('blockchain', 'addProvider', { name: 'Ganache Provider', + isInjected: false, provider: { async sendAsync (payload, callback) { try { @@ -131,6 +133,7 @@ export class RunTab extends ViewPlugin { await this.call('blockchain', 'addProvider', { name: 'Foundry Provider', + isInjected: false, provider: { async sendAsync (payload, callback) { try { @@ -145,6 +148,7 @@ export class RunTab extends ViewPlugin { await this.call('blockchain', 'addProvider', { name: 'Wallet Connect', + isInjected: false, provider: { async sendAsync (payload, callback) { 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) { diff --git a/apps/remix-ide/src/blockchain/execution-context.js b/apps/remix-ide/src/blockchain/execution-context.js index 95b4abe45d..1fe181acfb 100644 --- a/apps/remix-ide/src/blockchain/execution-context.js +++ b/apps/remix-ide/src/blockchain/execution-context.js @@ -152,9 +152,12 @@ export class ExecutionContext { if (context === 'injected') { 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() } 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) @@ -166,11 +169,21 @@ export class ExecutionContext { 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 e1dcd80f07..b90f37a022 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', '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) } 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/workspace/src/lib/components/custom-dropdown.tsx b/libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx similarity index 100% rename from libs/remix-ui/workspace/src/lib/components/custom-dropdown.tsx rename to libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx diff --git a/libs/remix-ui/plugin-manager/src/lib/components/LocalPluginForm.tsx b/libs/remix-ui/plugin-manager/src/lib/components/LocalPluginForm.tsx index 36c72b1410..0329189fdc 100644 --- a/libs/remix-ui/plugin-manager/src/lib/components/LocalPluginForm.tsx +++ b/libs/remix-ui/plugin-manager/src/lib/components/LocalPluginForm.tsx @@ -222,7 +222,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor type="radio" name="location" value="sidePanel" - id="none" + id="localPluginRadioButtonsidePanelSidePanel" data-id='localPluginRadioButtonsidePanel' checked={location === 'sidePanel'} onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} /> @@ -234,7 +234,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor type="radio" name="location" value="mainPanel" - id="none" + id="localPluginRadioButtonsidePanelMainPanel" data-id='localPluginRadioButtonmainPanel' checked={location === 'mainPanel'} onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} /> @@ -246,7 +246,7 @@ function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFor type="radio" name="location" value="none" - id="none" + id="localPluginRadioButtonsidePanelNone" data-id='localPluginRadioButtonnone' checked={location === 'none'} onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} /> diff --git a/libs/remix-ui/run-tab/src/lib/components/account.tsx b/libs/remix-ui/run-tab/src/lib/components/account.tsx index 151f165632..f8d5619725 100644 --- a/libs/remix-ui/run-tab/src/lib/components/account.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/account.tsx @@ -25,7 +25,7 @@ export function AccountUI (props: AccountProps) { case 'injected': setPlusOpt({ 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 diff --git a/libs/remix-ui/run-tab/src/lib/components/environment.tsx b/libs/remix-ui/run-tab/src/lib/components/environment.tsx index 8fbd17cfa9..04315d6d8b 100644 --- a/libs/remix-ui/run-tab/src/lib/components/environment.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/environment.tsx @@ -2,11 +2,14 @@ import React from 'react' import { FormattedMessage, useIntl } from 'react-intl' 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) { const handleChangeExEnv = (env: string) => { 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 context = context.startsWith('vm') ? 'vm' : context @@ -16,22 +19,55 @@ export function EnvironmentUI (props: EnvironmentProps) { 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 (
- + + + + { isL2(currentProvider) && 'L2 - '} + { currentProvider && currentProvider.content } + { currentProvider && bridges[currentProvider.value] && + Click to open a bridge for converting L1 mainnet ETH to the selected network currency. + + }> + + } + + + { + props.providers.providerList.map(({ content, value }, index) => ( + { + handleChangeExEnv(value) + }} + data-id={`dropdown-item-${value}`} + > + { isL2({ value }) && 'L2 - ' }{ content } + + )) + } + + {props.count}
- Save transactions (deployed contracts and function executions) and replay them in another environment.
e.g Transactions created in Javascript VM can be replayed in the Injected Web3. + Save transactions (deployed contracts and function executions) and replay them in another environment.
e.g Transactions created in Remix VM can be replayed in the Injected Provider.
}> diff --git a/libs/remix-ui/run-tab/src/lib/css/run-tab.css b/libs/remix-ui/run-tab/src/lib/css/run-tab.css index c4c65e4e01..2921381007 100644 --- a/libs/remix-ui/run-tab/src/lib/css/run-tab.css +++ b/libs/remix-ui/run-tab/src/lib/css/run-tab.css @@ -529,4 +529,7 @@ text-decoration: none; background-color: #007aa6; } +.udapp_selectExEnvOptions { + width: 100%; +} diff --git a/libs/remix-ui/workspace/src/lib/actions/index.ts b/libs/remix-ui/workspace/src/lib/actions/index.ts index 7a5387474b..b684fdc05a 100644 --- a/libs/remix-ui/workspace/src/lib/actions/index.ts +++ b/libs/remix-ui/workspace/src/lib/actions/index.ts @@ -127,6 +127,14 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React. plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(route)) } 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 { await basicWorkspaceInit(workspaces, workspaceProvider) } diff --git a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx index 86f4cccaf2..f754869931 100644 --- a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx +++ b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx @@ -5,7 +5,9 @@ import { Toaster } from '@remix-ui/toaster' // eslint-disable-line // eslint-disable-next-line @typescript-eslint/no-unused-vars import { FileSystemContext } from '../contexts' 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' // eslint-disable-next-line @typescript-eslint/no-unused-vars import { Workspace } from '../remix-ui-workspace' diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx index 32e82f3499..aee6534b22 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useRef, useContext } from 'react' // eslint-disable-line import { FormattedMessage, useIntl } from 'react-intl' 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 { FileSystemContext } from './contexts' import './css/remix-ui-workspace.css' @@ -21,6 +21,7 @@ export function Workspace () { const cloneUrlRef = useRef() useEffect(() => { + setCurrentWorkspace(localStorage.getItem('currentWorkspace') ? localStorage.getItem('currentWorkspace') : '') resetFocus() }, []) @@ -280,13 +281,13 @@ export function Workspace () { }} data-id={`dropdown-item-${name}`} > - { isGitRepo ? -
- { currentWorkspace === name ? ✓ { name } : { name } } - -
: + { isGitRepo ? +
{ currentWorkspace === name ? ✓ { name } : { name } } - } + +
: + { currentWorkspace === name ? ✓ { name } : { name } } + } )) }