diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 464356b902..d9c93fd4f6 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -29,6 +29,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') @@ -181,6 +183,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({ @@ -239,6 +243,8 @@ class AppComponent { ganacheProvider, foundryProvider, externalHttpProvider, + injected0ptimismProvider, + injectedArbitrumOneProvider, this.walkthroughService, search ]) 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/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..a633c66a64 --- /dev/null +++ b/apps/remix-ide/src/app/tabs/injected-provider.tsx @@ -0,0 +1,69 @@ +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) + 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 !!' + + try { + if ((window as any) && typeof (window as any).ethereum.enable === 'function') (window as any).ethereum.enable() + 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 cf71ad3780..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 { @@ -169,6 +173,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..b04e83d656 100644 --- a/apps/remix-ide/src/blockchain/execution-context.js +++ b/apps/remix-ide/src/blockchain/execution-context.js @@ -166,11 +166,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 9c00f8ab46..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', '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) }