diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index cca71d7632..8155a709ab 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -36,7 +36,8 @@ import { HardhatProvider } from './app/providers/hardhat-provider' import { GanacheProvider } from './app/providers/ganache-provider' import { FoundryProvider } from './app/providers/foundry-provider' import { ExternalHttpProvider } from './app/providers/external-http-provider' -import { BasicInjectedProvider } from './app/providers/basic-injected-provider' +import { InjectedProviderDefault } from './app/providers/injected-provider-default' +import { InjectedProviderTrustWallet } from './app/providers/injected-provider-trustwallet' import { Injected0ptimismProvider } from './app/providers/injected-optimism-provider' import { InjectedArbitrumOneProvider } from './app/providers/injected-arbitrum-one-provider' import { FileDecorator } from './app/plugins/file-decorator' @@ -216,7 +217,8 @@ class AppComponent { const ganacheProvider = new GanacheProvider(blockchain) const foundryProvider = new FoundryProvider(blockchain) const externalHttpProvider = new ExternalHttpProvider(blockchain) - const basicInjectedProvider = new BasicInjectedProvider() + const trustWalletInjectedProvider = new InjectedProviderTrustWallet() + const defaultInjectedProvider = new InjectedProviderDefault const injected0ptimismProvider = new Injected0ptimismProvider() const injectedArbitrumOneProvider = new InjectedArbitrumOneProvider() // ----------------- convert offset to line/column service ----------- @@ -289,7 +291,8 @@ class AppComponent { ganacheProvider, foundryProvider, externalHttpProvider, - basicInjectedProvider, + defaultInjectedProvider, + trustWalletInjectedProvider, injected0ptimismProvider, injectedArbitrumOneProvider, this.walkthroughService, diff --git a/apps/remix-ide/src/app/providers/basic-injected-provider.tsx b/apps/remix-ide/src/app/providers/basic-injected-provider.tsx deleted file mode 100644 index 95895a9dca..0000000000 --- a/apps/remix-ide/src/app/providers/basic-injected-provider.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as packageJson from '../../../../../package.json' -import { InjectedProvider } from './injected-provider' - -const profile = { - name: 'injected', - displayName: 'Injected Provider', - kind: 'provider', - description: 'injected Provider', - methods: ['sendAsync', 'init'], - version: packageJson.version -} - -export class BasicInjectedProvider extends InjectedProvider { - - constructor () { - super(profile) - } -} \ No newline at end of file diff --git a/apps/remix-ide/src/app/providers/injected-L2-provider.tsx b/apps/remix-ide/src/app/providers/injected-L2-provider.tsx index 3aa3ab770f..afae690a39 100644 --- a/apps/remix-ide/src/app/providers/injected-L2-provider.tsx +++ b/apps/remix-ide/src/app/providers/injected-L2-provider.tsx @@ -1,6 +1,6 @@ -import { InjectedProvider } from './injected-provider' +import { InjectedProviderDefaultBase } from './injected-provider-default' -export class InjectedL2Provider extends InjectedProvider { +export class InjectedL2Provider extends InjectedProviderDefaultBase { chainName: string chainId: string rpcUrls: Array diff --git a/apps/remix-ide/src/app/providers/injected-provider-default.tsx b/apps/remix-ide/src/app/providers/injected-provider-default.tsx new file mode 100644 index 0000000000..9af45e5660 --- /dev/null +++ b/apps/remix-ide/src/app/providers/injected-provider-default.tsx @@ -0,0 +1,40 @@ +/* global ethereum */ +import * as packageJson from '../../../../../package.json' +import { InjectedProvider } from './injected-provider' + +export class InjectedProviderDefaultBase extends InjectedProvider { + constructor (profile) { + super(profile) + } + + async init () { + const injectedProvider = this.getInjectedProvider() + 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).') + } + return super.init() + } + + getInjectedProvider () { + return (window as any).ethereum + } + + notFound () { + return '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).' + } +} + +const profile = { + name: 'injected', + displayName: 'Injected Provider', + kind: 'provider', + description: 'injected Provider', + methods: ['sendAsync', 'init'], + version: packageJson.version +} + +export class InjectedProviderDefault extends InjectedProviderDefaultBase { + constructor () { + super(profile) + } +} diff --git a/apps/remix-ide/src/app/providers/injected-provider-trustwallet.tsx b/apps/remix-ide/src/app/providers/injected-provider-trustwallet.tsx new file mode 100644 index 0000000000..c58f6d12f8 --- /dev/null +++ b/apps/remix-ide/src/app/providers/injected-provider-trustwallet.tsx @@ -0,0 +1,26 @@ +/* global ethereum */ +import * as packageJson from '../../../../../package.json' +import { InjectedProvider } from './injected-provider' + +const profile = { + name: 'injected-trustwallet', + displayName: 'Trust wallet', + kind: 'provider', + description: 'Trust wallet', + methods: ['sendAsync', 'init'], + version: packageJson.version +} + +export class InjectedProviderTrustWallet extends InjectedProvider { + constructor () { + super(profile) + } + + getInjectedProvider () { + return (window as any).trustwallet + } + + notFound () { + return 'Could not find Trust Wallet provider. Please make sure the Trust Wallet extension is active. Download the latest version from https://trustwallet.com/browser-extension' + } +} diff --git a/apps/remix-ide/src/app/providers/injected-provider.tsx b/apps/remix-ide/src/app/providers/injected-provider.tsx index 3c8a5a0ddb..4de903c0c8 100644 --- a/apps/remix-ide/src/app/providers/injected-provider.tsx +++ b/apps/remix-ide/src/app/providers/injected-provider.tsx @@ -2,13 +2,9 @@ import React from 'react' // eslint-disable-line import { Plugin } from '@remixproject/engine' import { JsonDataRequest, RejectRequest, SuccessRequest } from '../providers/abstract-provider' -import Web3 from 'web3' import { IProvider } from './abstract-provider' -const noInjectedProviderMsg = '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).' - -export class InjectedProvider extends Plugin implements IProvider { - provider: any +export abstract class InjectedProvider extends Plugin implements IProvider { options: { [id: string] : any } = {} listenerAccountsChanged: (accounts: Array) => void listenerChainChanged: (chainId: number) => void @@ -21,26 +17,37 @@ export class InjectedProvider extends Plugin implements IProvider { this.listenerChainChanged = (chainId: number) => { this.emit('chainChanged', chainId) } - if ((window as any).ethereum) { - this.provider = new Web3((window as any).ethereum) - } } - onActivation(): void { - (window as any).ethereum.on('accountsChanged', this.listenerAccountsChanged); - (window as any).ethereum.on('chainChanged', this.listenerChainChanged); + abstract getInjectedProvider(): any + abstract notFound(): string + + onActivation(): void { + try { + const web3Provider = this.getInjectedProvider() + web3Provider.on('accountsChanged', this.listenerAccountsChanged); + web3Provider.on('chainChanged', this.listenerChainChanged); + } catch (error) { + console.log('unable to listen on context changed') + } } onDeactivation(): void { - (window as any).ethereum.removeListener('accountsChanged', this.listenerAccountsChanged) - (window as any).ethereum.removeListener('chainChanged', this.listenerChainChanged) + try { + const web3Provider = this.getInjectedProvider() + web3Provider.removeListener('accountsChanged', this.listenerAccountsChanged) + web3Provider.removeListener('chainChanged', this.listenerChainChanged) + } catch (error) { + console.log('unable to remove listener on context changed') + } } askPermission (throwIfNoInjectedProvider) { - if ((typeof (window as any).ethereum) !== "undefined" && (typeof (window as any).ethereum.request) === "function") { - (window as any).ethereum.request({ method: "eth_requestAccounts" }) + const web3Provider = this.getInjectedProvider() + if (typeof web3Provider !== "undefined" && typeof web3Provider.request === "function") { + web3Provider.request({ method: "eth_requestAccounts" }) } else if (throwIfNoInjectedProvider) { - throw new Error(noInjectedProviderMsg) + throw new Error(this.notFound()) } } @@ -51,14 +58,11 @@ export class InjectedProvider extends Plugin implements IProvider { } async init () { - const injectedProvider = (window as any).ethereum + const injectedProvider = this.getInjectedProvider() if (injectedProvider === undefined) { - this.call('notification', 'toast', noInjectedProviderMsg) - throw new Error(noInjectedProviderMsg) + this.call('notification', 'toast', this.notFound()) + throw new Error(this.notFound()) } 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(true) } return {} @@ -73,12 +77,19 @@ export class InjectedProvider extends Plugin implements IProvider { 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) { + const web3Provider = this.getInjectedProvider() + if (!web3Provider) { this.call('notification', 'toast', 'No injected provider (e.g Metamask) has been found.') return resolve({ jsonrpc: '2.0', error: 'no injected provider found', id: data.id }) } try { - let resultData = await this.provider.currentProvider.send(data.method, data.params) + let resultData + if (web3Provider.send) resultData = await web3Provider.send(data.method, data.params) + else if (web3Provider.request) resultData = await web3Provider.request({ method: data.method, params: data.params}) + else { + resolve({ jsonrpc: '2.0', error: 'provider not valid', id: data.id }) + return + } if (resultData) { if (resultData.jsonrpc && resultData.jsonrpc === '2.0') { resultData = resultData.result diff --git a/apps/remix-ide/src/app/udapp/run-tab.js b/apps/remix-ide/src/app/udapp/run-tab.js index f833cc29e5..8bec995ca8 100644 --- a/apps/remix-ide/src/app/udapp/run-tab.js +++ b/apps/remix-ide/src/app/udapp/run-tab.js @@ -132,11 +132,20 @@ export class RunTab extends ViewPlugin { } // basic injected - const displayNameInjected = `Injected Provider${(window && window.ethereum && !(window.ethereum.providers && !window.ethereum.selectedProvider)) ? - window.ethereum.isCoinbaseWallet || window.ethereum.selectedProvider?.isCoinbaseWallet ? ' - Coinbase' : - window.ethereum.isBraveWallet || window.ethereum.selectedProvider?.isBraveWallet ? ' - Brave' : - window.ethereum.isMetaMask || window.ethereum.selectedProvider?.isMetaMask ? ' - MetaMask' : '' : ''}` - await addProvider('injected', displayNameInjected, true, false) + // if it's the trust wallet provider, we have a specific provider for that, see below + if (window && window.ethereum && !(window.ethereum.isTrustWallet || window.ethereum.selectedProvider?.isTrustWallet)) { + const displayNameInjected = `Injected Provider${(window && window.ethereum && !(window.ethereum.providers && !window.ethereum.selectedProvider)) ? + window.ethereum.isCoinbaseWallet || window.ethereum.selectedProvider?.isCoinbaseWallet ? ' - Coinbase' : + window.ethereum.isBraveWallet || window.ethereum.selectedProvider?.isBraveWallet ? ' - Brave' : + window.ethereum.isMetaMask || window.ethereum.selectedProvider?.isMetaMask ? ' - MetaMask' : '' : ''}` + await addProvider('injected', displayNameInjected, true, false) + } + + if (window && window.trustwallet) { + const displayNameInjected = `Injected Provider - TrustWallet` + await addProvider('injected-trustwallet', displayNameInjected, true, false) + } + // VM const titleVM = 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.' await addProvider('vm-merge', 'Remix VM (Merge)', false, true, 'merge', 'settingsVMMergeMode', titleVM) diff --git a/apps/remix-ide/src/blockchain/blockchain.js b/apps/remix-ide/src/blockchain/blockchain.js index 2c51182370..49367096ab 100644 --- a/apps/remix-ide/src/blockchain/blockchain.js +++ b/apps/remix-ide/src/blockchain/blockchain.js @@ -66,10 +66,18 @@ export class Blockchain extends Plugin { this._triggerEvent('networkStatus', [this.networkStatus]) }) }) + + this.on('injected-trustwallet', 'chainChanged', () => { + this.detectNetwork((error, network) => { + this.networkStatus = { network, error } + this._triggerEvent('networkStatus', [this.networkStatus]) + }) + }) } onDeactivation () { this.off('injected', 'chainChanged') + this.off('injected-trustwallet', 'chainChanged') } setupEvents () { diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index db5e5370df..2a4d7f4671 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -10,7 +10,7 @@ const requiredModules = [ // services + layout views + system views 'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons', 'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout', 'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'openzeppelin-proxy', - 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected', 'injected-optimism-provider', 'injected-arbitrum-one-provider', 'vm-custom-fork', 'vm-goerli-fork', 'vm-mainnet-fork', 'vm-sepolia-fork', 'vm-merge', 'vm-london', 'vm-berlin', + 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected', 'injected-trustwallet', 'injected-optimism-provider', 'injected-arbitrum-one-provider', 'vm-custom-fork', 'vm-goerli-fork', 'vm-mainnet-fork', 'vm-sepolia-fork', 'vm-merge', 'vm-london', 'vm-berlin', 'compileAndRun', 'search', 'recorder', 'fileDecorator', 'codeParser', 'codeFormatter', 'solidityumlgen', 'contractflattener'] // dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd) diff --git a/libs/remix-ui/run-tab/src/lib/actions/events.ts b/libs/remix-ui/run-tab/src/lib/actions/events.ts index 46876b28e3..5fd49279d2 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/events.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/events.ts @@ -129,6 +129,12 @@ export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch) => { dispatch(fetchAccountsListSuccess(accountsMap)) }) + plugin.on('injected-trustwallet', 'accountsChanged', (accounts: Array) => { + const accountsMap = {} + accounts.map(account => { accountsMap[account] = shortenAddress(account, '0')}) + dispatch(fetchAccountsListSuccess(accountsMap)) + }) + setInterval(() => { fillAccountsList(plugin, dispatch) updateInstanceBalance(plugin, dispatch)