From d892a15f87033f75753b94a97d2dc9067189b939 Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Sun, 8 Sep 2024 10:49:10 +0200 Subject: [PATCH 01/36] add script runner bridge --- apps/remix-ide/src/app.js | 9 +- apps/remix-ide/src/app/panels/terminal.tsx | 8 +- apps/remix-ide/src/app/plugins/matomo.ts | 2 +- .../remix-ide/src/app/tabs/compile-and-run.ts | 2 +- .../src/app/tabs/script-runner-ui.tsx | 87 +++++++++++++++++++ apps/remix-ide/src/assets/list.json | 14 ++- apps/remix-ide/src/remixAppManager.js | 10 ++- libs/remix-ui/scriptrunner/src/index.ts | 1 + .../scriptrunner/src/lib/script-runner-ui.tsx | 83 ++++++++++++++++++ libs/remix-ui/scriptrunner/src/lib/types.ts | 20 +++++ libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx | 2 +- .../src/lib/actions/terminalAction.ts | 9 +- .../terminal/src/lib/remix-ui-terminal.tsx | 2 +- .../workspace/src/lib/actions/index.ts | 2 +- tsconfig.paths.json | 5 +- 15 files changed, 238 insertions(+), 18 deletions(-) create mode 100644 apps/remix-ide/src/app/tabs/script-runner-ui.tsx create mode 100644 libs/remix-ui/scriptrunner/src/index.ts create mode 100644 libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx create mode 100644 libs/remix-ui/scriptrunner/src/lib/types.ts diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index a3722c7572..17a84f0dc4 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -69,6 +69,7 @@ const remixLib = require('@remix-project/remix-lib') import { QueryParams } from '@remix-project/remix-lib' import { SearchPlugin } from './app/tabs/search' +import { ScriptRunnerUIPlugin } from './app/tabs/script-runner-ui' import { ElectronProvider } from './app/files/electronProvider' const Storage = remixLib.Storage @@ -221,6 +222,9 @@ class AppComponent { //----- search const search = new SearchPlugin() + //---------------- Script Runner UI Plugin ------------------------- + const scriptRunnerUI = new ScriptRunnerUIPlugin(this.engine) + //---- templates const templates = new TemplatesPlugin() @@ -371,7 +375,8 @@ class AppComponent { git, pluginStateLogger, matomo, - templateSelection + templateSelection, + scriptRunnerUI ]) //---- fs plugin @@ -611,7 +616,7 @@ class AppComponent { }) // activate solidity plugin - this.appManager.activatePlugin(['solidity', 'udapp', 'deploy-libraries', 'link-libraries', 'openzeppelin-proxy']) + this.appManager.activatePlugin(['solidity', 'udapp', 'deploy-libraries', 'link-libraries', 'openzeppelin-proxy', 'scriptRunnerBridge']) } } diff --git a/apps/remix-ide/src/app/panels/terminal.tsx b/apps/remix-ide/src/app/panels/terminal.tsx index 2cb89e3fcb..a30cf085bd 100644 --- a/apps/remix-ide/src/app/panels/terminal.tsx +++ b/apps/remix-ide/src/app/panels/terminal.tsx @@ -117,10 +117,10 @@ class Terminal extends Plugin { } onDeactivation() { - this.off('scriptRunner', 'log') - this.off('scriptRunner', 'info') - this.off('scriptRunner', 'warn') - this.off('scriptRunner', 'error') + this.off('scriptRunnerBridge', 'log') + this.off('scriptRunnerBridge', 'info') + this.off('scriptRunnerBridge', 'warn') + this.off('scriptRunnerBridge', 'error') } logHtml(html) { diff --git a/apps/remix-ide/src/app/plugins/matomo.ts b/apps/remix-ide/src/app/plugins/matomo.ts index 8aa8f61f70..688139d113 100644 --- a/apps/remix-ide/src/app/plugins/matomo.ts +++ b/apps/remix-ide/src/app/plugins/matomo.ts @@ -11,7 +11,7 @@ const profile = { version: '1.0.0' } -const allowedPlugins = ['LearnEth', 'etherscan', 'vyper', 'circuit-compiler', 'doc-gen', 'doc-viewer', 'solhint', 'walletconnect', 'scriptRunner', 'dgit'] +const allowedPlugins = ['LearnEth', 'etherscan', 'vyper', 'circuit-compiler', 'doc-gen', 'doc-viewer', 'solhint', 'walletconnect', 'scriptRunner', 'scriptRunnerBridge', 'dgit'] export class Matomo extends Plugin { diff --git a/apps/remix-ide/src/app/tabs/compile-and-run.ts b/apps/remix-ide/src/app/tabs/compile-and-run.ts index 63952747c9..9643cd78e9 100644 --- a/apps/remix-ide/src/app/tabs/compile-and-run.ts +++ b/apps/remix-ide/src/app/tabs/compile-and-run.ts @@ -62,7 +62,7 @@ export class CompileAndRun extends Plugin { if (clearAllInstances) { await this.call('udapp', 'clearAllInstances') } - await this.call('scriptRunner', 'execute', content, fileName) + await this.call('scriptRunnerBridge', 'execute', content, fileName) } catch (e) { this.call('notification', 'toast', e.message || e) } diff --git a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx new file mode 100644 index 0000000000..d5072b80cd --- /dev/null +++ b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx @@ -0,0 +1,87 @@ +import { IframePlugin, IframeProfile, ViewPlugin } from '@remixproject/engine-web' +import * as packageJson from '../../../../../package.json' +import React from 'react' // eslint-disable-line +import { ScriptRunnerUI } from '@remix-scriptrunner' // eslint-disable-line +import { Profile } from '@remixproject/plugin-utils' +import { Engine } from '@remixproject/engine' +const profile = { + name: 'scriptRunnerBridge', + displayName: 'Script Bridge', + methods: ['execute'], + events: ['log', 'info', 'warn', 'error'], + icon: 'assets/img/settings.webp', + description: 'Set up a script runner', + kind: '', + location: 'sidePanel', + version: packageJson.version, + maintainedBy: 'Remix' +} + +export class ScriptRunnerUIPlugin extends ViewPlugin { + engine: Engine + current: string + constructor(engine: Engine) { + super(profile) + console.log('ScriptRunnerUIPlugin', this) + this.engine = engine + } + + async onActivation () { + console.log('onActivation', this) + } + + async loadScriptRunner(name: string) { + console.log('loadScriptRunner', name, this) + const profile: IframeProfile = await this.call('manager', 'getProfile', 'scriptRunner') + const newProfile: IframeProfile = { + ...profile, + name: profile.name + name, + url: 'http://localhost:3000?template=' + name + } + console.log('loadScriptRunner', newProfile) + const plugin: IframePlugin = new IframePlugin(newProfile) + await this.engine.register(plugin) + + await this.call('manager', 'activatePlugin', newProfile.name) + this.current = newProfile.name + this.on(newProfile.name, 'log', this.log.bind(this)) + this.on(newProfile.name, 'info', this.log.bind(this)) + this.on(newProfile.name, 'warn', this.log.bind(this)) + this.on(newProfile.name, 'error', this.log.bind(this)) + } + + async execute (script: string, filePath: string) { + if(!this.current) await this.loadScriptRunner('default') + console.log('execute', script, filePath) + this.call(this.current, 'execute', script, filePath) + } + + async log(data: any){ + console.log('log', data) + this.emit('log', data) + } + + async warn(data: any){ + console.log('warn', data) + this.emit('warn', data) + } + + async error(data: any){ + console.log('error', data) + this.emit('error', data) + } + + async info(data: any){ + console.log('info', data) + this.emit('info', data) + } + + + render() { + return ( +
+ +
+ ) + } +} \ No newline at end of file diff --git a/apps/remix-ide/src/assets/list.json b/apps/remix-ide/src/assets/list.json index 6a55fff4f7..82f8fe17bb 100644 --- a/apps/remix-ide/src/assets/list.json +++ b/apps/remix-ide/src/assets/list.json @@ -1022,9 +1022,21 @@ "urls": [ "dweb:/ipfs/QmS5JdeXtYhGBvdgNTLWuBNHupyP623X4sf43fRbrgiTaA" ] + }, + { + "path": "soljson-v0.8.27+commit.40a35a09.js", + "version": "0.8.27", + "build": "commit.40a35a09", + "longVersion": "0.8.27+commit.40a35a09", + "keccak256": "0x68c7a06651a847fc9a60886a6ba590a2b20d87f2d4f9570bf70fbb2b901e7713", + "sha256": "0xd91c08277f801321af4e80958015aea18b41c01d2c6a38310a23014485b0e51c", + "urls": [ + "dweb:/ipfs/QmVTALD1WUQwRvEL19jgwrEFyBJMQmy9z32zvT6TAtYPY1" + ] } ], "releases": { + "0.8.27": "soljson-v0.8.27+commit.40a35a09.js", "0.8.26": "soljson-v0.8.26+commit.8a97fa7a.js", "0.8.25": "soljson-v0.8.25+commit.b61c2a91.js", "0.8.24": "soljson-v0.8.24+commit.e11b9ed9.js", @@ -1119,5 +1131,5 @@ "0.4.0": "soljson-v0.4.0+commit.acd334c9.js", "0.3.6": "soljson-v0.3.6+commit.3fc68da5.js" }, - "latestRelease": "0.8.26" + "latestRelease": "0.8.27" } \ No newline at end of file diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index f823069585..bf70a26fdb 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -24,6 +24,7 @@ let requiredModules = [ // services + layout views + system views 'blockchain', 'web3Provider', 'scriptRunner', + 'scriptRunnerBridge', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', @@ -107,6 +108,10 @@ const isVM = (name) => { return name.startsWith('vm') } +const isScriptRunner = (name) => { + return name.startsWith('scriptRunner') +} + export function isNative(name) { // nativePlugin allows to bypass the permission request @@ -139,7 +144,7 @@ export function isNative(name) { 'templateSelection', 'walletconnect' ] - return nativePlugins.includes(name) || requiredModules.includes(name) || isInjectedProvider(name) || isVM(name) + return nativePlugins.includes(name) || requiredModules.includes(name) || isInjectedProvider(name) || isVM(name) || isScriptRunner(name) } /** @@ -192,6 +197,8 @@ export class RemixAppManager extends PluginManager { } } await this.toggleActive(name) + }else{ + console.log('cannot deactivate', name) } } @@ -294,6 +301,7 @@ export class RemixAppManager extends PluginManager { return plugins.map(plugin => { if (plugin.name === 'dgit' && Registry.getInstance().get('platform').api.isDesktop()) { plugin.url = 'https://dgit4-76cc9.web.app/' } // temporary fix if (plugin.name === testPluginName) plugin.url = testPluginUrl + //console.log('plugin', plugin) return new IframePlugin(plugin) }) } diff --git a/libs/remix-ui/scriptrunner/src/index.ts b/libs/remix-ui/scriptrunner/src/index.ts new file mode 100644 index 0000000000..be8e8bb09c --- /dev/null +++ b/libs/remix-ui/scriptrunner/src/index.ts @@ -0,0 +1 @@ +export { ScriptRunnerUI } from './lib/script-runner-ui'; \ No newline at end of file diff --git a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx new file mode 100644 index 0000000000..5fe5ad0bf4 --- /dev/null +++ b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx @@ -0,0 +1,83 @@ +import React, { useEffect, useState } from "react"; +import { Accordion, Card, Button } from "react-bootstrap"; +import axios from "axios"; +import { ProjectConfiguration } from "./types"; +import { FormattedMessage } from "react-intl"; +import { faCheck, faToggleOn } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Profile } from "@remixproject/plugin-utils"; +import { IframeProfile, ViewProfile } from "@remixproject/engine-web"; +import { Plugin } from "@remixproject/engine"; + +export interface ScriptRunnerUIProps { + // Add your props here + loadScriptRunner: (name: string) => void; +} + +export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => { + const { loadScriptRunner } = props; + const [configurations, setConfigurations] = useState([]); + const [activeKey, setActiveKey] = useState('default'); + const [activeConfig, setActiveConfig] = useState('default'); + + useEffect(() => { + // Fetch the JSON data from the localhost server using Axios + const fetchData = async () => { + try { + const response = await axios.get('http://localhost:3000/projects.json'); + setConfigurations(response.data); + } catch (error) { + console.error("Error fetching the projects data:", error); + } + }; + + fetchData(); + }, []); // Empty array ensures this effect runs once when the component mounts + + const handleSelect = (key) => { + console.log("Selected key:", key, activeKey); + setActiveConfig(key); + console.log(loadScriptRunner) + loadScriptRunner(key) + }; + + // Filter out unpublished configurations + const publishedConfigurations = configurations.filter((config) => config.publish); + + return ( +
+ + {publishedConfigurations.map((config: ProjectConfiguration, index) => ( +
+
+ + {config.name} + +
handleSelect(config.name)} className="pointer px-2"> + {activeConfig !== config.name ? + : + + } +
+
+ + + + <> +

Description: {config.description}

+

Dependencies:

+
    + {config.dependencies.map((dep, depIndex) => ( +
  • + {dep.name} (v{dep.version}) +
  • + ))} +
+
))} +
+ +
+ ); +}; + + diff --git a/libs/remix-ui/scriptrunner/src/lib/types.ts b/libs/remix-ui/scriptrunner/src/lib/types.ts new file mode 100644 index 0000000000..ff093b428b --- /dev/null +++ b/libs/remix-ui/scriptrunner/src/lib/types.ts @@ -0,0 +1,20 @@ +export interface Dependency { + version: string; + name: string; + alias?: string; + import: boolean; + require?: boolean; + windowImport?: boolean; + } + + export interface Replacements { + [key: string]: string; + } + + export interface ProjectConfiguration { + name: string; + publish: boolean; + description: string; + dependencies: Dependency[]; + replacements: Replacements; + } diff --git a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx index 311b4c166a..3f57709eca 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -209,7 +209,7 @@ export const TabsUI = (props: TabsUIProps) => { const path = active().substr(active().indexOf('/') + 1, active().length) const content = await props.plugin.call('fileManager', 'readFile', path) if (tabsState.currentExt === 'js' || tabsState.currentExt === 'ts') { - await props.plugin.call('scriptRunner', 'execute', content, path) + await props.plugin.call('scriptRunnerBridge', 'execute', content, path) _paq.push(['trackEvent', 'editor', 'clickRunFromEditor', tabsState.currentExt]) } else if (tabsState.currentExt === 'sol' || tabsState.currentExt === 'yul') { await props.plugin.call('solidity', 'compile', path) diff --git a/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts b/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts index ceeea496f7..0a8a6b5891 100644 --- a/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts +++ b/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts @@ -79,28 +79,29 @@ export const filterFnAction = (name: string, filterFn, dispatch: React.Dispatch< } export const registerLogScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch) => { - on('scriptRunner', commandName, (msg) => { + console.log('registerLogScriptRunnerAction', commandName) + on('scriptRunnerBridge', commandName, (msg) => { commandFn.log.apply(commandFn, msg.data) // eslint-disable-line dispatch({ type: commandName, payload: { commandFn, message: msg.data } }) }) } export const registerInfoScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch) => { - on('scriptRunner', commandName, (msg) => { + on('scriptRunnerBridge', commandName, (msg) => { commandFn.info.apply(commandFn, msg.data) // eslint-disable-line dispatch({ type: commandName, payload: { commandFn, message: msg.data } }) }) } export const registerWarnScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch) => { - on('scriptRunner', commandName, (msg) => { + on('scriptRunnerBridge', commandName, (msg) => { commandFn.warn.apply(commandFn, msg.data) // eslint-disable-line dispatch({ type: commandName, payload: { commandFn, message: msg.data } }) }) } export const registerErrorScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch) => { - on('scriptRunner', commandName, (msg) => { + on('scriptRunnerBridge', commandName, (msg) => { commandFn.error.apply(commandFn, msg.data) // eslint-disable-line dispatch({ type: commandName, payload: { commandFn, message: msg.data } }) }) diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx index 29f471be3a..b88b7da73e 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx @@ -245,7 +245,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { await call('solcoder', 'solidity_answer', script) _paq.push(['trackEvent', 'ai', 'solcoder', 'askFromTerminal']) } else { - await call('scriptRunner', 'execute', script) + await call('scriptRunnerBridge', 'execute', script) } done() } catch (error) { diff --git a/libs/remix-ui/workspace/src/lib/actions/index.ts b/libs/remix-ui/workspace/src/lib/actions/index.ts index e10fa20d43..23b9598ce4 100644 --- a/libs/remix-ui/workspace/src/lib/actions/index.ts +++ b/libs/remix-ui/workspace/src/lib/actions/index.ts @@ -506,7 +506,7 @@ export const runScript = async (path: string) => { if (error) { return dispatch(displayPopUp(error)) } - plugin.call('scriptRunner', 'execute', content, path) + plugin.call('scriptRunnerBridge', 'execute', content, path) }) } diff --git a/tsconfig.paths.json b/tsconfig.paths.json index 3b4433ee92..c1c11be318 100644 --- a/tsconfig.paths.json +++ b/tsconfig.paths.json @@ -180,7 +180,10 @@ ], "@remix-api": [ "libs/remix-api/src/index.ts" - ] + ], + "@remix-scriptrunner": [ + "libs/remix-ui/scriptrunner/src/index.ts" + ], } } } From c4eabfca4f8bbf6e591a60d84c3892cff8c9a883 Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Sun, 8 Sep 2024 10:52:04 +0200 Subject: [PATCH 02/36] current template --- apps/remix-ide/src/app/tabs/script-runner-ui.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx index d5072b80cd..e698f3a642 100644 --- a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx +++ b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx @@ -20,6 +20,7 @@ const profile = { export class ScriptRunnerUIPlugin extends ViewPlugin { engine: Engine current: string + currentTemplate: string constructor(engine: Engine) { super(profile) console.log('ScriptRunnerUIPlugin', this) @@ -44,6 +45,7 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { await this.call('manager', 'activatePlugin', newProfile.name) this.current = newProfile.name + this.currentTemplate = name this.on(newProfile.name, 'log', this.log.bind(this)) this.on(newProfile.name, 'info', this.log.bind(this)) this.on(newProfile.name, 'warn', this.log.bind(this)) @@ -52,8 +54,8 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { async execute (script: string, filePath: string) { if(!this.current) await this.loadScriptRunner('default') - console.log('execute', script, filePath) - this.call(this.current, 'execute', script, filePath) + console.log('execute', this.current) + await this.call(this.current, 'execute', script, filePath) } async log(data: any){ From dadf71ba45ea400162cf218e0129ce09b5c413ce Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Sun, 8 Sep 2024 17:39:44 +0200 Subject: [PATCH 03/36] catch loaded --- .../src/app/tabs/script-runner-ui.tsx | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx index e698f3a642..68e0badad6 100644 --- a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx +++ b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx @@ -27,7 +27,7 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { this.engine = engine } - async onActivation () { + async onActivation() { console.log('onActivation', this) } @@ -40,40 +40,45 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { url: 'http://localhost:3000?template=' + name } console.log('loadScriptRunner', newProfile) - const plugin: IframePlugin = new IframePlugin(newProfile) - await this.engine.register(plugin) - - await this.call('manager', 'activatePlugin', newProfile.name) - this.current = newProfile.name - this.currentTemplate = name - this.on(newProfile.name, 'log', this.log.bind(this)) - this.on(newProfile.name, 'info', this.log.bind(this)) - this.on(newProfile.name, 'warn', this.log.bind(this)) - this.on(newProfile.name, 'error', this.log.bind(this)) + try { + const plugin: IframePlugin = new IframePlugin(newProfile) + await this.engine.register(plugin) + await this.call('manager', 'activatePlugin', newProfile.name) + this.current = newProfile.name + this.currentTemplate = name + this.on(newProfile.name, 'log', this.log.bind(this)) + this.on(newProfile.name, 'info', this.log.bind(this)) + this.on(newProfile.name, 'warn', this.log.bind(this)) + this.on(newProfile.name, 'error', this.log.bind(this)) + } catch (e) { + this.current = newProfile.name + this.currentTemplate = name + console.log('Already loaded') + } } - async execute (script: string, filePath: string) { - if(!this.current) await this.loadScriptRunner('default') + async execute(script: string, filePath: string) { + if (!this.current) await this.loadScriptRunner('default') console.log('execute', this.current) await this.call(this.current, 'execute', script, filePath) } - async log(data: any){ + async log(data: any) { console.log('log', data) this.emit('log', data) } - async warn(data: any){ + async warn(data: any) { console.log('warn', data) this.emit('warn', data) } - async error(data: any){ + async error(data: any) { console.log('error', data) this.emit('error', data) } - async info(data: any){ + async info(data: any) { console.log('info', data) this.emit('info', data) } From 692e52a3a702000f28e8ca507b9dc38debb069c0 Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Sun, 8 Sep 2024 17:55:01 +0200 Subject: [PATCH 04/36] change name --- apps/remix-ide/src/app/tabs/script-runner-ui.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx index 68e0badad6..345fdaecd6 100644 --- a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx +++ b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx @@ -6,7 +6,7 @@ import { Profile } from '@remixproject/plugin-utils' import { Engine } from '@remixproject/engine' const profile = { name: 'scriptRunnerBridge', - displayName: 'Script Bridge', + displayName: 'Script configuration', methods: ['execute'], events: ['log', 'info', 'warn', 'error'], icon: 'assets/img/settings.webp', From 518f9b59c384ecf22dd3194abaf8296593b7d68e Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Mon, 9 Sep 2024 09:43:30 +0200 Subject: [PATCH 05/36] fix binding --- .../src/app/tabs/script-runner-ui.tsx | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx index 345fdaecd6..bf29df6d89 100644 --- a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx +++ b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx @@ -4,6 +4,7 @@ import React from 'react' // eslint-disable-line import { ScriptRunnerUI } from '@remix-scriptrunner' // eslint-disable-line import { Profile } from '@remixproject/plugin-utils' import { Engine } from '@remixproject/engine' + const profile = { name: 'scriptRunnerBridge', displayName: 'Script configuration', @@ -32,12 +33,26 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { } async loadScriptRunner(name: string) { - console.log('loadScriptRunner', name, this) + console.log('loadScriptRunner', name) const profile: IframeProfile = await this.call('manager', 'getProfile', 'scriptRunner') + const testPluginName = localStorage.getItem('test-plugin-name') + const testPluginUrl = localStorage.getItem('test-plugin-url') + let baseUrl = 'http://localhost:3000' + let url = `${baseUrl}?template=${name}` + if(testPluginName === 'scriptRunner'){ + // if testpluginurl has template specified only use that + if(testPluginUrl.indexOf('template')>-1){ + url = testPluginUrl + }else{ + baseUrl = `//${new URL(testPluginUrl).host}` + url = `${baseUrl}?template=${name}` + } + } + const newProfile: IframeProfile = { ...profile, name: profile.name + name, - url: 'http://localhost:3000?template=' + name + url: url } console.log('loadScriptRunner', newProfile) try { @@ -47,9 +62,9 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { this.current = newProfile.name this.currentTemplate = name this.on(newProfile.name, 'log', this.log.bind(this)) - this.on(newProfile.name, 'info', this.log.bind(this)) - this.on(newProfile.name, 'warn', this.log.bind(this)) - this.on(newProfile.name, 'error', this.log.bind(this)) + this.on(newProfile.name, 'info', this.info.bind(this)) + this.on(newProfile.name, 'warn', this.warn.bind(this)) + this.on(newProfile.name, 'error', this.error.bind(this)) } catch (e) { this.current = newProfile.name this.currentTemplate = name From c7f6932bbaaaa4e50ade873ab30ea1ea4eb9fef4 Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Wed, 11 Sep 2024 15:40:25 +0200 Subject: [PATCH 06/36] add random --- apps/remix-ide/src/app/tabs/script-runner-ui.tsx | 2 +- libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx index bf29df6d89..2aad6be73e 100644 --- a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx +++ b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx @@ -45,7 +45,7 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { url = testPluginUrl }else{ baseUrl = `//${new URL(testPluginUrl).host}` - url = `${baseUrl}?template=${name}` + url = `${baseUrl}?template=${name}×tamp=${Date.now()}` } } diff --git a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx index 5fe5ad0bf4..b7af4a207c 100644 --- a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx +++ b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx @@ -24,7 +24,7 @@ export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => { // Fetch the JSON data from the localhost server using Axios const fetchData = async () => { try { - const response = await axios.get('http://localhost:3000/projects.json'); + const response = await axios.get('http://localhost:3000/projects.json?timestamp=' + Date.now()); setConfigurations(response.data); } catch (error) { console.error("Error fetching the projects data:", error); From e97d28073a8b09eb3a109fb3324619f973cfa311 Mon Sep 17 00:00:00 2001 From: yann300 Date: Fri, 13 Sep 2024 16:45:35 +0200 Subject: [PATCH 07/36] fix ethers6 --- libs/remix-simulator/src/methods/accounts.ts | 5 +++++ libs/remix-simulator/src/methods/transactions.ts | 10 ++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/libs/remix-simulator/src/methods/accounts.ts b/libs/remix-simulator/src/methods/accounts.ts index 6c99565455..c28818c8e6 100644 --- a/libs/remix-simulator/src/methods/accounts.ts +++ b/libs/remix-simulator/src/methods/accounts.ts @@ -76,6 +76,7 @@ export class Web3Accounts { methods (): Record { return { + eth_requestAccounts: this.eth_requestAccounts.bind(this), eth_accounts: this.eth_accounts.bind(this), eth_getBalance: this.eth_getBalance.bind(this), eth_sign: this.eth_sign.bind(this), @@ -85,6 +86,10 @@ export class Web3Accounts { } } + eth_requestAccounts (_payload, cb) { + return cb(null, Object.keys(this.accounts)) + } + eth_accounts (_payload, cb) { return cb(null, Object.keys(this.accounts)) } diff --git a/libs/remix-simulator/src/methods/transactions.ts b/libs/remix-simulator/src/methods/transactions.ts index 714852f693..d2bf5f6b2b 100644 --- a/libs/remix-simulator/src/methods/transactions.ts +++ b/libs/remix-simulator/src/methods/transactions.ts @@ -308,7 +308,6 @@ export class Transactions { const txBlock = this.vmContext.blockByTxHash[receipt.transactionHash] const tx = this.vmContext.txByHash[receipt.transactionHash] - // TODO: params to add later const r: Record = { blockHash: bytesToHex(txBlock.hash()), @@ -322,11 +321,10 @@ export class Transactions { input: receipt.input, nonce: bigIntToHex(tx.nonce), transactionIndex: this.TX_INDEX, - value: bigIntToHex(tx.value) - // "value":"0xf3dbb76162000" // 4290000000000000 - // "v": "0x25", // 37 - // "r": "0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea", - // "s": "0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c" + value: bigIntToHex(tx.value), + v: bigIntToHex(tx.v), + r: bigIntToHex(tx.r), + s: bigIntToHex(tx.s) } if (receipt.to) { From 0a2bad63fcf3aee4ff9a4d07732cbbc189834b49 Mon Sep 17 00:00:00 2001 From: yann300 Date: Fri, 13 Sep 2024 16:59:24 +0200 Subject: [PATCH 08/36] template script and testing: use ethers6 --- apps/remix-ide-e2e/src/tests/terminal.test.ts | 26 +++++++++---------- libs/ghaction-helper/src/methods.ts | 2 +- .../basic-contract-deploy.ts | 4 +-- .../create2-factory-deploy.ts | 2 +- .../scripts/deploy_with_ethers.ts | 2 +- .../gnosisSafeMultisig/scripts/ethers-lib.ts | 4 +-- .../ozerc1155/scripts/deploy_with_ethers.ts | 2 +- .../templates/ozerc1155/scripts/ethers-lib.ts | 4 +-- .../ozerc20/scripts/deploy_with_ethers.ts | 2 +- .../templates/ozerc20/scripts/ethers-lib.ts | 4 +-- .../ozerc721/scripts/deploy_with_ethers.ts | 2 +- .../templates/ozerc721/scripts/ethers-lib.ts | 4 +-- .../playground/scripts/deploy_with_ethers.ts | 2 +- .../playground/scripts/ethers-lib.ts | 4 +-- .../scripts/deploy_with_ethers.ts | 2 +- .../remixDefault/scripts/ethers-lib.ts | 4 +-- .../remixDefault/tests/storage.test.js | 4 +-- .../zeroxErc20/scripts/deploy_with_ethers.ts | 2 +- .../zeroxErc20/scripts/ethers-lib.ts | 4 +-- 19 files changed, 40 insertions(+), 40 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/terminal.test.ts b/apps/remix-ide-e2e/src/tests/terminal.test.ts index 9d73831389..0ea0cb3263 100644 --- a/apps/remix-ide-e2e/src/tests/terminal.test.ts +++ b/apps/remix-ide-e2e/src/tests/terminal.test.ts @@ -501,7 +501,7 @@ const deployWithEthersJs = ` const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() + const signer = (new ethers.BrowserProvider(web3Provider)).getSigner() let factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) @@ -510,7 +510,7 @@ const deployWithEthersJs = ` console.log('Contract Address: ', contract.address); // The contract is NOT deployed yet; we must wait until it is mined - await contract.deployed() + await contract.waitForDeployment() console.log('Deployment successful.') contract.on('OwnerSet', (previousOwner, newOwner) => { @@ -531,20 +531,20 @@ describe("Storage with lib", function () { it("test initial value", async function () { // Make sure contract is compiled and artifacts are generated const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json')) - const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() + const signer = (new ethers.BrowserProvider(web3Provider)).getSigner() let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) let storage = await Storage.deploy(); console.log('storage contract Address: ' + storage.address); - await storage.deployed() + await storage.waitForDeployment() expect((await storage.retrieve()).toNumber()).to.equal(0); }); it("test updating and retrieving updated value", async function () { const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json')) - const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() + const signer = (new ethers.BrowserProvider(web3Provider)).getSigner() let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) let storage = await Storage.deploy(); - await storage.deployed() + await storage.waitForDeployment() const setValue = await storage.store(56); await setValue.wait(); expect((await storage.retrieve()).toNumber()).to.equal(56); @@ -552,10 +552,10 @@ describe("Storage with lib", function () { it("fail test updating and retrieving updated value", async function () { const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json')) - const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() + const signer = (new ethers.BrowserProvider(web3Provider)).getSigner() let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) let storage = await Storage.deploy(); - await storage.deployed() + await storage.waitForDeployment() const setValue = await storage.store(56); await setValue.wait(); expect((await storage.retrieve()).toNumber(), 'incorrect number').to.equal(55); @@ -623,7 +623,7 @@ describe("Storage", function () { const optionsLib = {} const factoryLib = await ethers.getContractFactoryFromArtifact(artifactLib, optionsLib) const lib = await factoryLib.deploy(); - await lib.deployed() + await lib.waitForDeployment() const metadata = JSON.parse(await remix.call('fileManager', 'readFile', 'contracts/artifacts/StorageWithLib.json')) const artifact = { @@ -643,7 +643,7 @@ describe("Storage", function () { const factory = await ethers.getContractFactoryFromArtifact(artifact, options) const storage = await factory.deploy(); - await storage.deployed() + await storage.waitForDeployment() const storeValue = await storage.store(333); await storeValue.wait(); expect((await storage.getFromLib()).toString()).to.equal('34'); @@ -779,7 +779,7 @@ const scriptAutoExec = { const lib = await factoryLib.deploy(); - await lib.deployed() + await lib.waitForDeployment() console.log('lib deployed', lib.address) @@ -803,7 +803,7 @@ const scriptAutoExec = { const storage = await factory.deploy(); - await storage.deployed() + await storage.waitForDeployment() const storeValue = await storage.store(333); @@ -826,7 +826,7 @@ const scriptBlockAndTransaction = ` try { web3.eth.getTransaction('0x0d2baaed96425861677e87dcf6961d34e2b73ad9a0929c32a05607ca94f98d17').then(console.log).catch(console.error) web3.eth.getBlock(4757766).then(console.log).catch(console.error) - let ethersProvider = new ethers.providers.Web3Provider(web3Provider) + let ethersProvider = new ethers.BrowserProvider(web3Provider) ethersProvider.getBlock(4757767).then(console.log).catch(console.error) } catch (e) { console.log(e.message) diff --git a/libs/ghaction-helper/src/methods.ts b/libs/ghaction-helper/src/methods.ts index d7ece2b6c6..e5087d2c80 100644 --- a/libs/ghaction-helper/src/methods.ts +++ b/libs/ghaction-helper/src/methods.ts @@ -14,7 +14,7 @@ const providerConfig = { const config = { defaultTransactionType: '0x0' } global.remixProvider = new Provider(providerConfig) global.remixProvider.init() -global.web3Provider = new ethers.providers.Web3Provider(global.remixProvider) +global.web3Provider = new ethers.BrowserProvider(global.remixProvider) global.provider = global.web3Provider global.ethereum = global.web3Provider global.web3 = new Web3(global.web3Provider) diff --git a/libs/remix-ws-templates/src/script-templates/contract-deployer/basic-contract-deploy.ts b/libs/remix-ws-templates/src/script-templates/contract-deployer/basic-contract-deploy.ts index 8e4adf95a6..20ae79477e 100644 --- a/libs/remix-ws-templates/src/script-templates/contract-deployer/basic-contract-deploy.ts +++ b/libs/remix-ws-templates/src/script-templates/contract-deployer/basic-contract-deploy.ts @@ -17,13 +17,13 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex) + const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) const contract = await factory.deploy(...args) // The contract is NOT deployed yet; we must wait until it is mined - await contract.deployed() + await contract.waitForDeployment() return contract } \ No newline at end of file diff --git a/libs/remix-ws-templates/src/script-templates/contract-deployer/create2-factory-deploy.ts b/libs/remix-ws-templates/src/script-templates/contract-deployer/create2-factory-deploy.ts index 6de36cfa20..65f6ab8bce 100644 --- a/libs/remix-ws-templates/src/script-templates/contract-deployer/create2-factory-deploy.ts +++ b/libs/remix-ws-templates/src/script-templates/contract-deployer/create2-factory-deploy.ts @@ -15,7 +15,7 @@ export const CREATE2_DEPLOYER_ADDRESS = '0x13b0D85CcB8bf860b6b79AF3029fCA081AE9b export const deploy = async (contractName: string, args: Array, salt: string, accountIndex?: number): Promise => { console.log(`deploying ${contractName}`) - const signer = new ethers.providers.Web3Provider(web3Provider).getSigner(accountIndex) + const signer = new ethers.BrowserProvider(web3Provider).getSigner(accountIndex) const factory = new ethers.Contract(CREATE2_DEPLOYER_ADDRESS, contractDeployerAbi, signer) //@ts-ignore diff --git a/libs/remix-ws-templates/src/templates/gnosisSafeMultisig/scripts/deploy_with_ethers.ts b/libs/remix-ws-templates/src/templates/gnosisSafeMultisig/scripts/deploy_with_ethers.ts index b825f91a76..9a3114510e 100644 --- a/libs/remix-ws-templates/src/templates/gnosisSafeMultisig/scripts/deploy_with_ethers.ts +++ b/libs/remix-ws-templates/src/templates/gnosisSafeMultisig/scripts/deploy_with_ethers.ts @@ -3,7 +3,7 @@ import { deploy } from './ethers-lib' (async () => { try { const result = await deploy('MultisigWallet', []) - console.log(`address: ${result.address}`) + console.log(`address: ${await result.getAddress()}`) } catch (e) { console.log(e.message) } diff --git a/libs/remix-ws-templates/src/templates/gnosisSafeMultisig/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/gnosisSafeMultisig/scripts/ethers-lib.ts index 8e4adf95a6..20ae79477e 100644 --- a/libs/remix-ws-templates/src/templates/gnosisSafeMultisig/scripts/ethers-lib.ts +++ b/libs/remix-ws-templates/src/templates/gnosisSafeMultisig/scripts/ethers-lib.ts @@ -17,13 +17,13 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex) + const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) const contract = await factory.deploy(...args) // The contract is NOT deployed yet; we must wait until it is mined - await contract.deployed() + await contract.waitForDeployment() return contract } \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/ozerc1155/scripts/deploy_with_ethers.ts b/libs/remix-ws-templates/src/templates/ozerc1155/scripts/deploy_with_ethers.ts index 63533fd6f5..ff27dbf13e 100644 --- a/libs/remix-ws-templates/src/templates/ozerc1155/scripts/deploy_with_ethers.ts +++ b/libs/remix-ws-templates/src/templates/ozerc1155/scripts/deploy_with_ethers.ts @@ -3,7 +3,7 @@ import { deploy } from './ethers-lib' (async () => { try { const result = await deploy('MyToken', []) - console.log(`address: ${result.address}`) + console.log(`address: ${await result.getAddress()}`) } catch (e) { console.log(e.message) } diff --git a/libs/remix-ws-templates/src/templates/ozerc1155/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/ozerc1155/scripts/ethers-lib.ts index 8e4adf95a6..20ae79477e 100644 --- a/libs/remix-ws-templates/src/templates/ozerc1155/scripts/ethers-lib.ts +++ b/libs/remix-ws-templates/src/templates/ozerc1155/scripts/ethers-lib.ts @@ -17,13 +17,13 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex) + const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) const contract = await factory.deploy(...args) // The contract is NOT deployed yet; we must wait until it is mined - await contract.deployed() + await contract.waitForDeployment() return contract } \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/ozerc20/scripts/deploy_with_ethers.ts b/libs/remix-ws-templates/src/templates/ozerc20/scripts/deploy_with_ethers.ts index 63533fd6f5..ff27dbf13e 100644 --- a/libs/remix-ws-templates/src/templates/ozerc20/scripts/deploy_with_ethers.ts +++ b/libs/remix-ws-templates/src/templates/ozerc20/scripts/deploy_with_ethers.ts @@ -3,7 +3,7 @@ import { deploy } from './ethers-lib' (async () => { try { const result = await deploy('MyToken', []) - console.log(`address: ${result.address}`) + console.log(`address: ${await result.getAddress()}`) } catch (e) { console.log(e.message) } diff --git a/libs/remix-ws-templates/src/templates/ozerc20/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/ozerc20/scripts/ethers-lib.ts index 8e4adf95a6..20ae79477e 100644 --- a/libs/remix-ws-templates/src/templates/ozerc20/scripts/ethers-lib.ts +++ b/libs/remix-ws-templates/src/templates/ozerc20/scripts/ethers-lib.ts @@ -17,13 +17,13 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex) + const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) const contract = await factory.deploy(...args) // The contract is NOT deployed yet; we must wait until it is mined - await contract.deployed() + await contract.waitForDeployment() return contract } \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/ozerc721/scripts/deploy_with_ethers.ts b/libs/remix-ws-templates/src/templates/ozerc721/scripts/deploy_with_ethers.ts index 63533fd6f5..ff27dbf13e 100644 --- a/libs/remix-ws-templates/src/templates/ozerc721/scripts/deploy_with_ethers.ts +++ b/libs/remix-ws-templates/src/templates/ozerc721/scripts/deploy_with_ethers.ts @@ -3,7 +3,7 @@ import { deploy } from './ethers-lib' (async () => { try { const result = await deploy('MyToken', []) - console.log(`address: ${result.address}`) + console.log(`address: ${await result.getAddress()}`) } catch (e) { console.log(e.message) } diff --git a/libs/remix-ws-templates/src/templates/ozerc721/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/ozerc721/scripts/ethers-lib.ts index 8e4adf95a6..20ae79477e 100644 --- a/libs/remix-ws-templates/src/templates/ozerc721/scripts/ethers-lib.ts +++ b/libs/remix-ws-templates/src/templates/ozerc721/scripts/ethers-lib.ts @@ -17,13 +17,13 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex) + const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) const contract = await factory.deploy(...args) // The contract is NOT deployed yet; we must wait until it is mined - await contract.deployed() + await contract.waitForDeployment() return contract } \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/playground/scripts/deploy_with_ethers.ts b/libs/remix-ws-templates/src/templates/playground/scripts/deploy_with_ethers.ts index d190d654f0..6773145fa0 100644 --- a/libs/remix-ws-templates/src/templates/playground/scripts/deploy_with_ethers.ts +++ b/libs/remix-ws-templates/src/templates/playground/scripts/deploy_with_ethers.ts @@ -7,7 +7,7 @@ import { deploy } from './ethers-lib' (async () => { try { const result = await deploy('HelloWorld', []) - console.log(`address: ${result.address}`) + console.log(`address: ${await result.getAddress()}`) } catch (e) { console.log(e.message) } diff --git a/libs/remix-ws-templates/src/templates/playground/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/playground/scripts/ethers-lib.ts index 8e4adf95a6..20ae79477e 100644 --- a/libs/remix-ws-templates/src/templates/playground/scripts/ethers-lib.ts +++ b/libs/remix-ws-templates/src/templates/playground/scripts/ethers-lib.ts @@ -17,13 +17,13 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex) + const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) const contract = await factory.deploy(...args) // The contract is NOT deployed yet; we must wait until it is mined - await contract.deployed() + await contract.waitForDeployment() return contract } \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/remixDefault/scripts/deploy_with_ethers.ts b/libs/remix-ws-templates/src/templates/remixDefault/scripts/deploy_with_ethers.ts index 513d496d76..3d5f4700f0 100644 --- a/libs/remix-ws-templates/src/templates/remixDefault/scripts/deploy_with_ethers.ts +++ b/libs/remix-ws-templates/src/templates/remixDefault/scripts/deploy_with_ethers.ts @@ -7,7 +7,7 @@ import { deploy } from './ethers-lib' (async () => { try { const result = await deploy('Storage', []) - console.log(`address: ${result.address}`) + console.log(`address: ${await result.getAddress()}`) } catch (e) { console.log(e.message) } diff --git a/libs/remix-ws-templates/src/templates/remixDefault/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/remixDefault/scripts/ethers-lib.ts index 8e4adf95a6..20ae79477e 100644 --- a/libs/remix-ws-templates/src/templates/remixDefault/scripts/ethers-lib.ts +++ b/libs/remix-ws-templates/src/templates/remixDefault/scripts/ethers-lib.ts @@ -17,13 +17,13 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex) + const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) const contract = await factory.deploy(...args) // The contract is NOT deployed yet; we must wait until it is mined - await contract.deployed() + await contract.waitForDeployment() return contract } \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/remixDefault/tests/storage.test.js b/libs/remix-ws-templates/src/templates/remixDefault/tests/storage.test.js index 8e37b7c39b..eaee3ebefa 100644 --- a/libs/remix-ws-templates/src/templates/remixDefault/tests/storage.test.js +++ b/libs/remix-ws-templates/src/templates/remixDefault/tests/storage.test.js @@ -7,14 +7,14 @@ describe("Storage", function () { it("test initial value", async function () { const Storage = await ethers.getContractFactory("Storage"); const storage = await Storage.deploy(); - await storage.deployed(); + await storage.waitForDeployment(); console.log("storage deployed at:" + storage.address); expect((await storage.retrieve()).toNumber()).to.equal(0); }); it("test updating and retrieving updated value", async function () { const Storage = await ethers.getContractFactory("Storage"); const storage = await Storage.deploy(); - await storage.deployed(); + await storage.waitForDeployment(); const storage2 = await ethers.getContractAt("Storage", storage.address); const setValue = await storage2.store(56); await setValue.wait(); diff --git a/libs/remix-ws-templates/src/templates/zeroxErc20/scripts/deploy_with_ethers.ts b/libs/remix-ws-templates/src/templates/zeroxErc20/scripts/deploy_with_ethers.ts index 1aadc32123..a16c54f5a9 100644 --- a/libs/remix-ws-templates/src/templates/zeroxErc20/scripts/deploy_with_ethers.ts +++ b/libs/remix-ws-templates/src/templates/zeroxErc20/scripts/deploy_with_ethers.ts @@ -3,7 +3,7 @@ import { deploy } from './ethers-lib' (async () => { try { const result = await deploy('SampleERC20', ["TestToken", "TST", 18, 1000]) - console.log(`address: ${result.address}`) + console.log(`address: ${await result.getAddress()}`) } catch (e) { console.log(e.message) } diff --git a/libs/remix-ws-templates/src/templates/zeroxErc20/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/zeroxErc20/scripts/ethers-lib.ts index 8e4adf95a6..20ae79477e 100644 --- a/libs/remix-ws-templates/src/templates/zeroxErc20/scripts/ethers-lib.ts +++ b/libs/remix-ws-templates/src/templates/zeroxErc20/scripts/ethers-lib.ts @@ -17,13 +17,13 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex) + const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) const contract = await factory.deploy(...args) // The contract is NOT deployed yet; we must wait until it is mined - await contract.deployed() + await contract.waitForDeployment() return contract } \ No newline at end of file From ca9f9b0142ff6c8f2dccbf5f9d18c2a7f7800ea7 Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 16 Sep 2024 11:42:00 +0200 Subject: [PATCH 09/36] fix script --- apps/remix-ide-e2e/src/tests/terminal.test.ts | 8 ++++---- .../contract-deployer/basic-contract-deploy.ts | 2 +- .../contract-deployer/create2-factory-deploy.ts | 2 +- .../templates/gnosisSafeMultisig/scripts/ethers-lib.ts | 2 +- .../src/templates/ozerc1155/scripts/ethers-lib.ts | 2 +- .../src/templates/ozerc20/scripts/ethers-lib.ts | 2 +- .../src/templates/ozerc721/scripts/ethers-lib.ts | 2 +- .../src/templates/playground/scripts/ethers-lib.ts | 2 +- .../src/templates/remixDefault/scripts/ethers-lib.ts | 2 +- .../src/templates/zeroxErc20/scripts/ethers-lib.ts | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/terminal.test.ts b/apps/remix-ide-e2e/src/tests/terminal.test.ts index 0ea0cb3263..8cdfd8ce3b 100644 --- a/apps/remix-ide-e2e/src/tests/terminal.test.ts +++ b/apps/remix-ide-e2e/src/tests/terminal.test.ts @@ -501,7 +501,7 @@ const deployWithEthersJs = ` const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.BrowserProvider(web3Provider)).getSigner() + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner() let factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) @@ -531,7 +531,7 @@ describe("Storage with lib", function () { it("test initial value", async function () { // Make sure contract is compiled and artifacts are generated const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json')) - const signer = (new ethers.BrowserProvider(web3Provider)).getSigner() + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner() let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) let storage = await Storage.deploy(); console.log('storage contract Address: ' + storage.address); @@ -541,7 +541,7 @@ describe("Storage with lib", function () { it("test updating and retrieving updated value", async function () { const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json')) - const signer = (new ethers.BrowserProvider(web3Provider)).getSigner() + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner() let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) let storage = await Storage.deploy(); await storage.waitForDeployment() @@ -552,7 +552,7 @@ describe("Storage with lib", function () { it("fail test updating and retrieving updated value", async function () { const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json')) - const signer = (new ethers.BrowserProvider(web3Provider)).getSigner() + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner() let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) let storage = await Storage.deploy(); await storage.waitForDeployment() diff --git a/libs/remix-ws-templates/src/script-templates/contract-deployer/basic-contract-deploy.ts b/libs/remix-ws-templates/src/script-templates/contract-deployer/basic-contract-deploy.ts index 20ae79477e..ea41ec2d21 100644 --- a/libs/remix-ws-templates/src/script-templates/contract-deployer/basic-contract-deploy.ts +++ b/libs/remix-ws-templates/src/script-templates/contract-deployer/basic-contract-deploy.ts @@ -17,7 +17,7 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) diff --git a/libs/remix-ws-templates/src/script-templates/contract-deployer/create2-factory-deploy.ts b/libs/remix-ws-templates/src/script-templates/contract-deployer/create2-factory-deploy.ts index 65f6ab8bce..5bd4cace4c 100644 --- a/libs/remix-ws-templates/src/script-templates/contract-deployer/create2-factory-deploy.ts +++ b/libs/remix-ws-templates/src/script-templates/contract-deployer/create2-factory-deploy.ts @@ -15,7 +15,7 @@ export const CREATE2_DEPLOYER_ADDRESS = '0x13b0D85CcB8bf860b6b79AF3029fCA081AE9b export const deploy = async (contractName: string, args: Array, salt: string, accountIndex?: number): Promise => { console.log(`deploying ${contractName}`) - const signer = new ethers.BrowserProvider(web3Provider).getSigner(accountIndex) + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.Contract(CREATE2_DEPLOYER_ADDRESS, contractDeployerAbi, signer) //@ts-ignore diff --git a/libs/remix-ws-templates/src/templates/gnosisSafeMultisig/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/gnosisSafeMultisig/scripts/ethers-lib.ts index 20ae79477e..ea41ec2d21 100644 --- a/libs/remix-ws-templates/src/templates/gnosisSafeMultisig/scripts/ethers-lib.ts +++ b/libs/remix-ws-templates/src/templates/gnosisSafeMultisig/scripts/ethers-lib.ts @@ -17,7 +17,7 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) diff --git a/libs/remix-ws-templates/src/templates/ozerc1155/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/ozerc1155/scripts/ethers-lib.ts index 20ae79477e..ea41ec2d21 100644 --- a/libs/remix-ws-templates/src/templates/ozerc1155/scripts/ethers-lib.ts +++ b/libs/remix-ws-templates/src/templates/ozerc1155/scripts/ethers-lib.ts @@ -17,7 +17,7 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) diff --git a/libs/remix-ws-templates/src/templates/ozerc20/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/ozerc20/scripts/ethers-lib.ts index 20ae79477e..ea41ec2d21 100644 --- a/libs/remix-ws-templates/src/templates/ozerc20/scripts/ethers-lib.ts +++ b/libs/remix-ws-templates/src/templates/ozerc20/scripts/ethers-lib.ts @@ -17,7 +17,7 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) diff --git a/libs/remix-ws-templates/src/templates/ozerc721/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/ozerc721/scripts/ethers-lib.ts index 20ae79477e..ea41ec2d21 100644 --- a/libs/remix-ws-templates/src/templates/ozerc721/scripts/ethers-lib.ts +++ b/libs/remix-ws-templates/src/templates/ozerc721/scripts/ethers-lib.ts @@ -17,7 +17,7 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) diff --git a/libs/remix-ws-templates/src/templates/playground/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/playground/scripts/ethers-lib.ts index 20ae79477e..ea41ec2d21 100644 --- a/libs/remix-ws-templates/src/templates/playground/scripts/ethers-lib.ts +++ b/libs/remix-ws-templates/src/templates/playground/scripts/ethers-lib.ts @@ -17,7 +17,7 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) diff --git a/libs/remix-ws-templates/src/templates/remixDefault/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/remixDefault/scripts/ethers-lib.ts index 20ae79477e..ea41ec2d21 100644 --- a/libs/remix-ws-templates/src/templates/remixDefault/scripts/ethers-lib.ts +++ b/libs/remix-ws-templates/src/templates/remixDefault/scripts/ethers-lib.ts @@ -17,7 +17,7 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) diff --git a/libs/remix-ws-templates/src/templates/zeroxErc20/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/zeroxErc20/scripts/ethers-lib.ts index 20ae79477e..ea41ec2d21 100644 --- a/libs/remix-ws-templates/src/templates/zeroxErc20/scripts/ethers-lib.ts +++ b/libs/remix-ws-templates/src/templates/zeroxErc20/scripts/ethers-lib.ts @@ -17,7 +17,7 @@ export const deploy = async (contractName: string, args: Array, accountInde const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) // 'web3Provider' is a remix global variable object - const signer = (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) From eced8cd5462e4cd752715c890aa0399073e5464f Mon Sep 17 00:00:00 2001 From: yann300 Date: Tue, 17 Sep 2024 10:06:16 +0200 Subject: [PATCH 10/36] linting --- apps/remix-ide/src/app/tabs/script-runner-ui.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx index 2aad6be73e..61ea29c316 100644 --- a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx +++ b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx @@ -39,16 +39,16 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { const testPluginUrl = localStorage.getItem('test-plugin-url') let baseUrl = 'http://localhost:3000' let url = `${baseUrl}?template=${name}` - if(testPluginName === 'scriptRunner'){ + if(testPluginName === 'scriptRunner') { // if testpluginurl has template specified only use that - if(testPluginUrl.indexOf('template')>-1){ - url = testPluginUrl - }else{ + if (testPluginUrl.indexOf('template')>-1) { + url = testPluginUrl + } else { baseUrl = `//${new URL(testPluginUrl).host}` url = `${baseUrl}?template=${name}×tamp=${Date.now()}` } } - + const newProfile: IframeProfile = { ...profile, name: profile.name + name, @@ -98,7 +98,6 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { this.emit('info', data) } - render() { return (
From 9bf9b3ebe54496b5d6ac188136a6aedea2948989 Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Wed, 25 Sep 2024 13:08:11 +0200 Subject: [PATCH 11/36] custom script runner UI --- .../src/app/tabs/script-runner-ui.tsx | 37 +++- libs/remix-ui/scriptrunner/src/index.ts | 3 +- .../src/lib/custom-script-runner.tsx | 159 ++++++++++++++++++ .../scriptrunner/src/lib/script-runner-ui.tsx | 47 ++++-- .../src/{lib/types.ts => types/index.ts} | 5 + 5 files changed, 236 insertions(+), 15 deletions(-) create mode 100644 libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx rename libs/remix-ui/scriptrunner/src/{lib/types.ts => types/index.ts} (78%) diff --git a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx index 61ea29c316..793db0cf42 100644 --- a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx +++ b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx @@ -1,9 +1,10 @@ import { IframePlugin, IframeProfile, ViewPlugin } from '@remixproject/engine-web' import * as packageJson from '../../../../../package.json' import React from 'react' // eslint-disable-line -import { ScriptRunnerUI } from '@remix-scriptrunner' // eslint-disable-line +import { customScriptRunnerConfig, Dependency, ScriptRunnerUI } from '@remix-scriptrunner' // eslint-disable-line import { Profile } from '@remixproject/plugin-utils' import { Engine } from '@remixproject/engine' +import axios from 'axios' const profile = { name: 'scriptRunnerBridge', @@ -98,10 +99,42 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { this.emit('info', data) } + async buildScriptRunner(dependencies: Dependency[]) { + console.log('buildScriptRunner', dependencies) + } + + async loadCustomConfig(){ + console.log('loadCustomConfig') + await this.call('fileManager', 'open', 'script.config.json') + const content = await this.call('fileManager', 'readFile', 'script.config.json') + return JSON.parse(content) + } + + async saveCustomConfig(content: customScriptRunnerConfig){ + console.log('saveCustomConfig', content) + await this.call('fileManager', 'writeFile', 'script.config.json', JSON.stringify(content, null, 2)) + } + + async activateCustomScriptRunner(config: customScriptRunnerConfig){ + console.log('activateCustomScriptRunner', config) + // post config to localhost:4000 using axios + const result = await axios.post('http://localhost:4000/build', config) + console.log(result) + if(result.data.hash) { + await this.loadScriptRunner(result.data.hash) + } + return result.data.hash + } + render() { return (
- +
) } diff --git a/libs/remix-ui/scriptrunner/src/index.ts b/libs/remix-ui/scriptrunner/src/index.ts index be8e8bb09c..a1683b6b22 100644 --- a/libs/remix-ui/scriptrunner/src/index.ts +++ b/libs/remix-ui/scriptrunner/src/index.ts @@ -1 +1,2 @@ -export { ScriptRunnerUI } from './lib/script-runner-ui'; \ No newline at end of file +export { ScriptRunnerUI } from './lib/script-runner-ui'; +export * from './types'; \ No newline at end of file diff --git a/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx b/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx new file mode 100644 index 0000000000..78ccb7cedd --- /dev/null +++ b/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx @@ -0,0 +1,159 @@ +import React, { useEffect, useState } from "react"; +import { Accordion, Card, Button } from "react-bootstrap"; +import axios from "axios"; +import { customScriptRunnerConfig, Dependency, ProjectConfiguration } from "../types"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faTrash } from "@fortawesome/free-solid-svg-icons"; + +export interface ScriptRunnerUIProps { + // build custom script runner + buildScriptRunner: (dependencies: Dependency[]) => void; + publishedConfigurations: ProjectConfiguration[]; + loadCustomConfig: () => any; + saveCustomConfig(content: customScriptRunnerConfig): void; + activateCustomScriptRunner(config: customScriptRunnerConfig): string; + addCustomConfig(config: ProjectConfiguration) : void; +} + +export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { + const [dependencies, setDependencies] = useState([]); + const [name, setName] = useState(''); + const [alias, setAlias] = useState(''); + const [version, setVersion] = useState(''); + const [baseConfig, setBaseConfig] = useState('default'); + const [loading, setLoading] = useState(false); + + const handleAddDependency = () => { + if (name.trim() && version.trim()) { + const newDependency: Dependency = { name, version, import: true, alias }; + setDependencies([...dependencies, newDependency]); + setName(''); + setVersion(''); + } else { + alert('Please fill out both name and version.'); + } + }; + + const handleRemoveDependency = (index: number) => { + const updatedDependencies = dependencies.filter((_, i) => i !== index); + setDependencies(updatedDependencies); + }; + + const handleSaveToFile = () => { + const fileData = JSON.stringify(dependencies, null, 2); + console.log(fileData, baseConfig); + const customConfig: customScriptRunnerConfig = { baseConfiguration: baseConfig, dependencies }; + console.log(customConfig); + props.saveCustomConfig(customConfig); + }; + + const loadFromFile = async () => { + const fileData: customScriptRunnerConfig = await props.loadCustomConfig(); + console.log(fileData); + setDependencies(fileData.dependencies); + setBaseConfig(fileData.baseConfiguration); + } + + const activateCustomConfig = async () => { + console.log('activate'); + const customConfig: customScriptRunnerConfig = { baseConfiguration: baseConfig, dependencies }; + console.log(customConfig); + setLoading(true); + const loadedConfig = await props.activateCustomScriptRunner(customConfig); + console.log(loadedConfig); + const newConfig: ProjectConfiguration = { + name: loadedConfig, + publish: true, + description: `Extension of ${baseConfig}`, + dependencies: dependencies, + replacements: {} + }; + console.log(newConfig); + props.addCustomConfig(newConfig); + setLoading(false); + } + + const onSelectBaseConfig = (e: React.ChangeEvent) => { + setBaseConfig(e.target.value); + } + + if (loading) { + return
+
+ +
+
+ } + + return ( +
+
Custom configuration
+ + + +
+ setName(e.target.value)} + style={{ marginRight: '10px' }} + /> + setAlias(e.target.value)} /> + setVersion(e.target.value)} + /> + +
+
    + {dependencies.map((dependency, index) => ( +
  • +
    + {dependency.name} - {dependency.version} + +
    +
  • + ))} +
+ {dependencies.length > 0 && ( + + )} + + {dependencies.length > 0 && ( + )} +
+ ); +} \ No newline at end of file diff --git a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx index b7af4a207c..bde50bac7b 100644 --- a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx +++ b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx @@ -1,22 +1,28 @@ import React, { useEffect, useState } from "react"; import { Accordion, Card, Button } from "react-bootstrap"; import axios from "axios"; -import { ProjectConfiguration } from "./types"; +import { customScriptRunnerConfig, Dependency, ProjectConfiguration } from "../types"; import { FormattedMessage } from "react-intl"; import { faCheck, faToggleOn } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Profile } from "@remixproject/plugin-utils"; import { IframeProfile, ViewProfile } from "@remixproject/engine-web"; import { Plugin } from "@remixproject/engine"; +import { CustomScriptRunner } from "./custom-script-runner"; export interface ScriptRunnerUIProps { // Add your props here loadScriptRunner: (name: string) => void; + // build custom script runner + buildScriptRunner: (dependencies: Dependency[]) => void; + loadCustomConfig: () => any; + saveCustomConfig(content: customScriptRunnerConfig): void; + activateCustomScriptRunner(config: customScriptRunnerConfig): string; } export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => { const { loadScriptRunner } = props; - const [configurations, setConfigurations] = useState([]); + const [configurations, setConfigurations] = useState([]); const [activeKey, setActiveKey] = useState('default'); const [activeConfig, setActiveConfig] = useState('default'); @@ -41,23 +47,34 @@ export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => { loadScriptRunner(key) }; - // Filter out unpublished configurations - const publishedConfigurations = configurations.filter((config) => config.publish); + const addCustomConfig = (config: ProjectConfiguration) => { + if(configurations.find((c) => c.name === config.name)) { + return; + } + setConfigurations([...configurations, config]); + setActiveConfig(config.name); + } + return (
- {publishedConfigurations.map((config: ProjectConfiguration, index) => ( + {configurations.filter((config) => config.publish).map((config: ProjectConfiguration, index) => (
- - {config.name} + +
{config.name}
handleSelect(config.name)} className="pointer px-2"> - {activeConfig !== config.name ? - : - - } + {activeConfig !== config.name ? + : + + }
@@ -75,7 +92,13 @@ export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => {
))}
- + config.publish)} + buildScriptRunner={props.buildScriptRunner} />
); }; diff --git a/libs/remix-ui/scriptrunner/src/lib/types.ts b/libs/remix-ui/scriptrunner/src/types/index.ts similarity index 78% rename from libs/remix-ui/scriptrunner/src/lib/types.ts rename to libs/remix-ui/scriptrunner/src/types/index.ts index ff093b428b..85a88ce447 100644 --- a/libs/remix-ui/scriptrunner/src/lib/types.ts +++ b/libs/remix-ui/scriptrunner/src/types/index.ts @@ -18,3 +18,8 @@ export interface Dependency { dependencies: Dependency[]; replacements: Replacements; } + + export interface customScriptRunnerConfig { + baseConfiguration: string; + dependencies: Dependency[]; + } \ No newline at end of file From 54f82017c10ba80ea6d023a0a4579d0b9854e2da Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Wed, 2 Oct 2024 12:53:13 +0200 Subject: [PATCH 12/36] sync configs --- .../src/app/tabs/script-runner-ui.tsx | 217 +++++++++++++++--- .../src/lib/plugins/menuicons-api.ts | 14 ++ libs/remix-api/src/lib/remix-api.ts | 2 + .../src/lib/custom-script-runner.tsx | 111 ++++++--- .../scriptrunner/src/lib/script-runner-ui.tsx | 25 +- libs/remix-ui/scriptrunner/src/types/index.ts | 4 +- 6 files changed, 291 insertions(+), 82 deletions(-) create mode 100644 libs/remix-api/src/lib/plugins/menuicons-api.ts diff --git a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx index 793db0cf42..b20cf820a3 100644 --- a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx +++ b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx @@ -1,10 +1,14 @@ import { IframePlugin, IframeProfile, ViewPlugin } from '@remixproject/engine-web' import * as packageJson from '../../../../../package.json' import React from 'react' // eslint-disable-line -import { customScriptRunnerConfig, Dependency, ScriptRunnerUI } from '@remix-scriptrunner' // eslint-disable-line +import { customScriptRunnerConfig, Dependency, ProjectConfiguration, ScriptRunnerUI } from '@remix-scriptrunner' // eslint-disable-line import { Profile } from '@remixproject/plugin-utils' -import { Engine } from '@remixproject/engine' +import { Engine, Plugin } from '@remixproject/engine' import axios from 'axios' +import { AppModal } from '@remix-ui/app' +import { isArray } from 'lodash' +import { PluginViewWrapper } from '@remix-ui/helper' +import { CustomRemixApi } from '@remix-api' const profile = { name: 'scriptRunnerBridge', @@ -19,30 +23,99 @@ const profile = { maintainedBy: 'Remix' } +const configFileName = 'script.config.json' + export class ScriptRunnerUIPlugin extends ViewPlugin { engine: Engine current: string currentTemplate: string + dispatch: React.Dispatch = () => { } + workspaceScriptRunnerDefaults: Record + customConfig: customScriptRunnerConfig + configurations: ProjectConfiguration[] + plugin: Plugin constructor(engine: Engine) { super(profile) console.log('ScriptRunnerUIPlugin', this) this.engine = engine + this.workspaceScriptRunnerDefaults = {} + this.plugin = this } async onActivation() { console.log('onActivation', this) + + console.log('onActivation', this.customConfig) + + this.on('filePanel', 'setWorkspace', async (workspace: string) => { + console.log('setWorkspace', workspace, this) + this.customConfig = { + baseConfiguration: 'default', + dependencies: [] + } + await this.loadCustomConfig() + this.loadConfigurations() + this.renderComponent() + console.log('setWorkspace', this.customConfig) + }) + + this.plugin.on('fileManager','fileSaved', async (file: string) =>{ + console.log(file) + if(file === configFileName) { + await this.loadCustomConfig() + this.renderComponent() + } + }) + + await this.loadCustomConfig() + this.loadConfigurations() + this.renderComponent() + } + + render() { + return ( +
+ +
+ ) + } + + setDispatch(dispatch: React.Dispatch) { + this.dispatch = dispatch + this.renderComponent() + } + + renderComponent() { + this.dispatch({ + customConfig: this.customConfig, + configurations: this.configurations, + }) + } + + updateComponent(state: any) { + console.log('updateComponent', state) + return ( + + ) } async loadScriptRunner(name: string) { console.log('loadScriptRunner', name) - const profile: IframeProfile = await this.call('manager', 'getProfile', 'scriptRunner') + const profile: Profile = await this.plugin.call('manager', 'getProfile', 'scriptRunner') const testPluginName = localStorage.getItem('test-plugin-name') const testPluginUrl = localStorage.getItem('test-plugin-url') let baseUrl = 'http://localhost:3000' - let url = `${baseUrl}?template=${name}` - if(testPluginName === 'scriptRunner') { + let url = `${baseUrl}?template=${name}×tamp=${Date.now()}` + if (testPluginName === 'scriptRunner') { // if testpluginurl has template specified only use that - if (testPluginUrl.indexOf('template')>-1) { + if (testPluginUrl.indexOf('template') > -1) { url = testPluginUrl } else { baseUrl = `//${new URL(testPluginUrl).host}` @@ -53,19 +126,21 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { const newProfile: IframeProfile = { ...profile, name: profile.name + name, + location: 'hidden', url: url } console.log('loadScriptRunner', newProfile) try { const plugin: IframePlugin = new IframePlugin(newProfile) await this.engine.register(plugin) - await this.call('manager', 'activatePlugin', newProfile.name) + await this.plugin.call('manager', 'activatePlugin', newProfile.name) this.current = newProfile.name this.currentTemplate = name this.on(newProfile.name, 'log', this.log.bind(this)) this.on(newProfile.name, 'info', this.info.bind(this)) this.on(newProfile.name, 'warn', this.warn.bind(this)) this.on(newProfile.name, 'error', this.error.bind(this)) + this.on(newProfile.name, 'dependencyError', this.dependencyError.bind(this)) } catch (e) { this.current = newProfile.name this.currentTemplate = name @@ -79,6 +154,30 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { await this.call(this.current, 'execute', script, filePath) } + async dependencyError(data: any) { + console.log('dependencyError', data) + let message = `Error loading dependencies: ` + if (isArray(data.data)) { + data.data.forEach((data: any) => { + message += `${data}` + }) + } + + const modal: AppModal = { + id: 'TemplatesSelection', + title: 'Missing dependencies', + message: `${message} \n\n You may need to setup a script engine for this workspace to load the correct dependencies. Do you want go to setup now?`, + okLabel: window._intl.formatMessage({ id: 'filePanel.ok' }), + cancelLabel: 'ignore' + } + const modalResult = await this.plugin.call('notification' as any, 'modal', modal) + if (modalResult) { + await this.plugin.call('menuicons', 'select', 'scriptRunnerBridge') + } else { + + } + } + async log(data: any) { console.log('log', data) this.emit('log', data) @@ -103,39 +202,95 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { console.log('buildScriptRunner', dependencies) } - async loadCustomConfig(){ + async loadCustomConfig(): Promise { console.log('loadCustomConfig') - await this.call('fileManager', 'open', 'script.config.json') - const content = await this.call('fileManager', 'readFile', 'script.config.json') - return JSON.parse(content) + //await this.plugin.call('fileManager', 'open', 'script.config.json') + try { + const content = await this.plugin.call('fileManager', 'readFile', configFileName) + const parsed = JSON.parse(content) + this.customConfig = parsed + } catch (e) { + return { + baseConfiguration: 'default', + dependencies: [] + } + } + } + + async openCustomConfig() { + try { + await this.plugin.call('fileManager', 'open', 'script.config.json') + }catch(e){ + + } } - async saveCustomConfig(content: customScriptRunnerConfig){ + async loadConfigurations() { + try { + const response = await axios.get('http://localhost:3000/projects.json?timestamp=' + Date.now()); + this.configurations = response.data; + } catch (error) { + console.error("Error fetching the projects data:", error); + } + + } + + async saveCustomConfig(content: customScriptRunnerConfig) { console.log('saveCustomConfig', content) - await this.call('fileManager', 'writeFile', 'script.config.json', JSON.stringify(content, null, 2)) + await this.plugin.call('fileManager', 'writeFile', 'script.config.json', JSON.stringify(content, null, 2)) } - async activateCustomScriptRunner(config: customScriptRunnerConfig){ + async activateCustomScriptRunner(config: customScriptRunnerConfig) { console.log('activateCustomScriptRunner', config) // post config to localhost:4000 using axios - const result = await axios.post('http://localhost:4000/build', config) - console.log(result) - if(result.data.hash) { - await this.loadScriptRunner(result.data.hash) + try { + const result = await axios.post('http://localhost:4000/build', config) + console.log(result) + if (result.data.hash) { + await this.loadScriptRunner(result.data.hash) + } + return result.data.hash + } catch (error) { + let message + if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + console.log('Error status:', error.response.status); + console.log('Error data:', error.response.data); // This should give you the output being sent + console.log('Error headers:', error.response.headers); + + if (error.response.data.error) { + + if (isArray(error.response.data.error)) { + const message = `${error.response.data.error[0]}` + this.plugin.call('notification', 'alert', { + id: 'scriptalert', + message, + title: 'Error' + }) + throw new Error(message) + } + message = `${error.response.data.error}` + } + message = `Uknown error: ${error.response.data}` + this.plugin.call('notification', 'alert', { + id: 'scriptalert', + message, + title: 'Error' + }) + throw new Error(message) + } else if (error.request) { + // The request was made but no response was received + console.log('No response received:', error.request); + throw new Error('No response received') + } else { + // Something happened in setting up the request that triggered an Error + console.log('Error message:', error.message); + throw new Error(error.message) + } + } - return result.data.hash } - render() { - return ( -
- -
- ) - } + } \ No newline at end of file diff --git a/libs/remix-api/src/lib/plugins/menuicons-api.ts b/libs/remix-api/src/lib/plugins/menuicons-api.ts new file mode 100644 index 0000000000..41f5715c02 --- /dev/null +++ b/libs/remix-api/src/lib/plugins/menuicons-api.ts @@ -0,0 +1,14 @@ +import { IFilePanel } from '@remixproject/plugin-api' +import { Profile, StatusEvents } from '@remixproject/plugin-utils' + +export interface IMenuIconsApi { + events: { + toggleContent: (name: string) => void, + showContent: (name: string) => void + } & StatusEvents + methods: { + select: (name: string) => void + linkContent: (profile: Profile) => void + unlinkContent: (profile: Profile) => void + } +} diff --git a/libs/remix-api/src/lib/remix-api.ts b/libs/remix-api/src/lib/remix-api.ts index 56628dc5b0..3620a1ac18 100644 --- a/libs/remix-api/src/lib/remix-api.ts +++ b/libs/remix-api/src/lib/remix-api.ts @@ -12,6 +12,7 @@ import { ISidePanelApi } from "./plugins/sidePanel-api" import { IPinnedPanelApi } from "./plugins/pinned-panel-api" import { ILayoutApi } from "./plugins/layout-api" import { IMatomoApi } from "./plugins/matomo-api" +import { IMenuIconsApi } from "./plugins/menuicons-api" export interface ICustomRemixApi extends IRemixApi { dgitApi: IGitApi @@ -25,6 +26,7 @@ export interface ICustomRemixApi extends IRemixApi { pinnedPanel: IPinnedPanelApi layout: ILayoutApi matomo: IMatomoApi + menuicons: IMenuIconsApi } export declare type CustomRemixApi = Readonly \ No newline at end of file diff --git a/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx b/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx index 78ccb7cedd..51e611edc3 100644 --- a/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx +++ b/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx @@ -3,16 +3,19 @@ import { Accordion, Card, Button } from "react-bootstrap"; import axios from "axios"; import { customScriptRunnerConfig, Dependency, ProjectConfiguration } from "../types"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faTrash } from "@fortawesome/free-solid-svg-icons"; +import { faToggleOff, faToggleOn, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { CustomTooltip } from "@remix-ui/helper"; +import { use } from "chai"; export interface ScriptRunnerUIProps { // build custom script runner buildScriptRunner: (dependencies: Dependency[]) => void; publishedConfigurations: ProjectConfiguration[]; - loadCustomConfig: () => any; + openCustomConfig: () => any; saveCustomConfig(content: customScriptRunnerConfig): void; activateCustomScriptRunner(config: customScriptRunnerConfig): string; - addCustomConfig(config: ProjectConfiguration) : void; + addCustomConfig(config: ProjectConfiguration): void; + customConfig: customScriptRunnerConfig; } export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { @@ -22,10 +25,24 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { const [version, setVersion] = useState(''); const [baseConfig, setBaseConfig] = useState('default'); const [loading, setLoading] = useState(false); + const [useRequire, setUseRequire] = useState(false) + + const { customConfig } = props; + + useEffect(() =>{ + console.log('CustomScriptRunner', props.customConfig) + },[]) + + useEffect(() => { + console.log('CustomScriptRunner', props.customConfig) + if(!customConfig) return; + setDependencies(customConfig.dependencies); + setBaseConfig(customConfig.baseConfiguration); + },[customConfig]) const handleAddDependency = () => { if (name.trim() && version.trim()) { - const newDependency: Dependency = { name, version, import: true, alias }; + const newDependency: Dependency = { name, version, require: useRequire, alias }; setDependencies([...dependencies, newDependency]); setName(''); setVersion(''); @@ -39,6 +56,14 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { setDependencies(updatedDependencies); }; + useEffect(() => { + async function saveData() { + //await handleSaveToFile(); + } + + saveData(); + },[dependencies]) + const handleSaveToFile = () => { const fileData = JSON.stringify(dependencies, null, 2); console.log(fileData, baseConfig); @@ -47,11 +72,8 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { props.saveCustomConfig(customConfig); }; - const loadFromFile = async () => { - const fileData: customScriptRunnerConfig = await props.loadCustomConfig(); - console.log(fileData); - setDependencies(fileData.dependencies); - setBaseConfig(fileData.baseConfiguration); + const openConfig = async () => { + const fileData: customScriptRunnerConfig = await props.openCustomConfig(); } const activateCustomConfig = async () => { @@ -59,24 +81,34 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { const customConfig: customScriptRunnerConfig = { baseConfiguration: baseConfig, dependencies }; console.log(customConfig); setLoading(true); - const loadedConfig = await props.activateCustomScriptRunner(customConfig); - console.log(loadedConfig); - const newConfig: ProjectConfiguration = { - name: loadedConfig, - publish: true, - description: `Extension of ${baseConfig}`, - dependencies: dependencies, - replacements: {} - }; - console.log(newConfig); - props.addCustomConfig(newConfig); - setLoading(false); + try { + const loadedConfig = await props.activateCustomScriptRunner(customConfig); + console.log(loadedConfig); + const newConfig: ProjectConfiguration = { + name: loadedConfig, + publish: true, + description: `Extension of ${baseConfig}`, + dependencies: dependencies, + replacements: {} + }; + console.log(newConfig); + props.addCustomConfig(newConfig); + + } catch (e) { + console.log(e) + } finally { + setLoading(false); + } } const onSelectBaseConfig = (e: React.ChangeEvent) => { setBaseConfig(e.target.value); } + const toggleRequire = () => { + setUseRequire((prev) => !prev) + } + if (loading) { return
@@ -120,6 +152,15 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { value={version} onChange={(e) => setVersion(e.target.value)} /> + +
+ + +
+
+ {dependency.name} - {dependency.version} +
))} {dependencies.length > 0 && ( )} - {dependencies.length > 0 && ( - )} + )}
); } \ No newline at end of file diff --git a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx index bde50bac7b..7455aa60b1 100644 --- a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx +++ b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx @@ -15,29 +15,21 @@ export interface ScriptRunnerUIProps { loadScriptRunner: (name: string) => void; // build custom script runner buildScriptRunner: (dependencies: Dependency[]) => void; - loadCustomConfig: () => any; + openCustomConfig: () => any; saveCustomConfig(content: customScriptRunnerConfig): void; activateCustomScriptRunner(config: customScriptRunnerConfig): string; + customConfig: customScriptRunnerConfig; + configurations: ProjectConfiguration[]; } export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => { - const { loadScriptRunner } = props; - const [configurations, setConfigurations] = useState([]); + const { loadScriptRunner, configurations } = props; const [activeKey, setActiveKey] = useState('default'); const [activeConfig, setActiveConfig] = useState('default'); useEffect(() => { // Fetch the JSON data from the localhost server using Axios - const fetchData = async () => { - try { - const response = await axios.get('http://localhost:3000/projects.json?timestamp=' + Date.now()); - setConfigurations(response.data); - } catch (error) { - console.error("Error fetching the projects data:", error); - } - }; - fetchData(); }, []); // Empty array ensures this effect runs once when the component mounts const handleSelect = (key) => { @@ -51,10 +43,14 @@ export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => { if(configurations.find((c) => c.name === config.name)) { return; } - setConfigurations([...configurations, config]); + //setConfigurations([...configurations, config]); setActiveConfig(config.name); } + if (!configurations) { + return
Loading...
; + } + return (
@@ -93,10 +89,11 @@ export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => {
))} config.publish)} buildScriptRunner={props.buildScriptRunner} />
diff --git a/libs/remix-ui/scriptrunner/src/types/index.ts b/libs/remix-ui/scriptrunner/src/types/index.ts index 85a88ce447..b474249ca6 100644 --- a/libs/remix-ui/scriptrunner/src/types/index.ts +++ b/libs/remix-ui/scriptrunner/src/types/index.ts @@ -2,8 +2,8 @@ export interface Dependency { version: string; name: string; alias?: string; - import: boolean; - require?: boolean; + import?: boolean; + require: boolean; windowImport?: boolean; } From 4983d741eada8caebfedb34191c2a176c81b883b Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Sat, 19 Oct 2024 12:27:01 +0200 Subject: [PATCH 13/36] add errors --- .../src/app/tabs/script-runner-ui.tsx | 208 ++++++++++++++---- apps/remix-ide/src/assets/list.json | 14 +- .../src/lib/custom-script-runner.tsx | 16 +- .../scriptrunner/src/lib/script-runner-ui.tsx | 71 +++--- libs/remix-ui/scriptrunner/src/types/index.ts | 14 +- 5 files changed, 220 insertions(+), 103 deletions(-) diff --git a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx index b20cf820a3..2064b2de8c 100644 --- a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx +++ b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx @@ -1,7 +1,7 @@ import { IframePlugin, IframeProfile, ViewPlugin } from '@remixproject/engine-web' import * as packageJson from '../../../../../package.json' import React from 'react' // eslint-disable-line -import { customScriptRunnerConfig, Dependency, ProjectConfiguration, ScriptRunnerUI } from '@remix-scriptrunner' // eslint-disable-line +import { customScriptRunnerConfig, Dependency, ProjectConfiguration, ScriptRunnerConfig, ScriptRunnerUI } from '@remix-scriptrunner' // eslint-disable-line import { Profile } from '@remixproject/plugin-utils' import { Engine, Plugin } from '@remixproject/engine' import axios from 'axios' @@ -25,50 +25,62 @@ const profile = { const configFileName = 'script.config.json' +let baseUrl = 'http://localhost:3000' +let customBuildUrl = 'http://localhost:4000/build' + +interface IScriptRunnerState { + customConfig: customScriptRunnerConfig + configurations: ProjectConfiguration[] + activeConfig: ProjectConfiguration + enableCustomScriptRunner: boolean +} + export class ScriptRunnerUIPlugin extends ViewPlugin { engine: Engine - current: string - currentTemplate: string dispatch: React.Dispatch = () => { } workspaceScriptRunnerDefaults: Record - customConfig: customScriptRunnerConfig + customConfig: ScriptRunnerConfig configurations: ProjectConfiguration[] + activeConfig: ProjectConfiguration + enableCustomScriptRunner: boolean plugin: Plugin + scriptRunnerProfileName: string constructor(engine: Engine) { super(profile) console.log('ScriptRunnerUIPlugin', this) this.engine = engine this.workspaceScriptRunnerDefaults = {} this.plugin = this + this.enableCustomScriptRunner = false } async onActivation() { - console.log('onActivation', this) - - console.log('onActivation', this.customConfig) this.on('filePanel', 'setWorkspace', async (workspace: string) => { - console.log('setWorkspace', workspace, this) - this.customConfig = { - baseConfiguration: 'default', - dependencies: [] + console.log('setWorkspace', workspace) + this.activeConfig = null + this.customConfig = + { + defaultConfig: 'default', + customConfig: { + baseConfiguration: 'default', + dependencies: [] + } } await this.loadCustomConfig() - this.loadConfigurations() + await this.loadConfigurations() this.renderComponent() - console.log('setWorkspace', this.customConfig) }) - this.plugin.on('fileManager','fileSaved', async (file: string) =>{ - console.log(file) - if(file === configFileName) { + this.plugin.on('fileManager', 'fileSaved', async (file: string) => { + + if (file === configFileName && this.enableCustomScriptRunner) { await this.loadCustomConfig() this.renderComponent() } }) - await this.loadCustomConfig() - this.loadConfigurations() + await this.loadConfigurations() this.renderComponent() } @@ -89,69 +101,137 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { this.dispatch({ customConfig: this.customConfig, configurations: this.configurations, + activeConfig: this.activeConfig, + enableCustomScriptRunner: this.enableCustomScriptRunner }) } - updateComponent(state: any) { - console.log('updateComponent', state) + updateComponent(state: IScriptRunnerState) { return ( + loadScriptRunner={this.selectScriptRunner.bind(this)} /> ) } - async loadScriptRunner(name: string) { - console.log('loadScriptRunner', name) + async selectScriptRunner(config: ProjectConfiguration) { + console.log('selectScriptRunner', config) + await this.loadScriptRunner(config) + await this.saveCustomConfig(this.customConfig) + } + + async loadScriptRunner(config: ProjectConfiguration): Promise { + console.log('loadScriptRunner', config) const profile: Profile = await this.plugin.call('manager', 'getProfile', 'scriptRunner') + this.scriptRunnerProfileName = profile.name const testPluginName = localStorage.getItem('test-plugin-name') const testPluginUrl = localStorage.getItem('test-plugin-url') - let baseUrl = 'http://localhost:3000' - let url = `${baseUrl}?template=${name}×tamp=${Date.now()}` + + let url = `${baseUrl}?template=${config.name}×tamp=${Date.now()}` if (testPluginName === 'scriptRunner') { // if testpluginurl has template specified only use that if (testPluginUrl.indexOf('template') > -1) { url = testPluginUrl } else { baseUrl = `//${new URL(testPluginUrl).host}` - url = `${baseUrl}?template=${name}×tamp=${Date.now()}` + url = `${baseUrl}?template=${config.name}×tamp=${Date.now()}` } } - + console.log('loadScriptRunner', profile) const newProfile: IframeProfile = { ...profile, - name: profile.name + name, - location: 'hidden', + name: profile.name + config.name, + location: 'hiddenPanel', url: url } console.log('loadScriptRunner', newProfile) + let result = null try { + this.setIsLoading(config.name, true) const plugin: IframePlugin = new IframePlugin(newProfile) - await this.engine.register(plugin) + if (!this.engine.isRegistered(newProfile.name)) { + console.log('registering plugin', plugin) + await this.engine.register(plugin) + } await this.plugin.call('manager', 'activatePlugin', newProfile.name) - this.current = newProfile.name - this.currentTemplate = name + + this.activeConfig = config this.on(newProfile.name, 'log', this.log.bind(this)) this.on(newProfile.name, 'info', this.info.bind(this)) this.on(newProfile.name, 'warn', this.warn.bind(this)) this.on(newProfile.name, 'error', this.error.bind(this)) this.on(newProfile.name, 'dependencyError', this.dependencyError.bind(this)) + this.customConfig.defaultConfig = config.name + this.setErrorStatus(config.name, false, '') + result = true } catch (e) { - this.current = newProfile.name - this.currentTemplate = name - console.log('Already loaded') + + this.engine.remove(newProfile.name) + console.log('is registered', newProfile.name, this.engine.isRegistered(newProfile.name)) + console.log('Error loading script runner: ', newProfile.name, e) + this.setErrorStatus(config.name, true, e) + result = false } + this.setIsLoading(config.name, false) + this.renderComponent() + return result + } async execute(script: string, filePath: string) { - if (!this.current) await this.loadScriptRunner('default') - console.log('execute', this.current) - await this.call(this.current, 'execute', script, filePath) + console.log('is registered', `${this.scriptRunnerProfileName}${this.activeConfig.name}`, this.engine.isRegistered(`${this.scriptRunnerProfileName}${this.activeConfig.name}`)) + if (!this.scriptRunnerProfileName || !this.engine.isRegistered(`${this.scriptRunnerProfileName}${this.activeConfig.name}`)) { + if (!await this.loadScriptRunner(this.activeConfig)) { + console.error('Error loading script runner') + return + } + } + console.log('execute', this.activeConfig) + try { + await this.call(`${this.scriptRunnerProfileName}${this.activeConfig.name}`, 'execute', script, filePath) + } catch (e) { + console.error('Error executing script', e) + } + + } + + async setErrorStatus(name: string, status: boolean, error: string) { + console.log('setLoadingStatus', name, status, error) + this.configurations.forEach((config) => { + if (config.name === name) { + config.errorStatus = status + config.error = error + } + }) + this.renderComponent() + } + + async setIsLoading(name: string, status: boolean) { + console.log('setLoadingStatus', name, status) + if (status) { + this.emit('statusChanged', { + key: 'loading', + type: 'info', + title: 'loading...' + }) + } else { + this.emit('statusChanged', { + key: 'none' + }) + } + this.configurations.forEach((config) => { + if (config.name === name) { + config.isLoading = status + } + }) + this.renderComponent() } async dependencyError(data: any) { @@ -202,40 +282,58 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { console.log('buildScriptRunner', dependencies) } - async loadCustomConfig(): Promise { + async loadCustomConfig(): Promise { console.log('loadCustomConfig') //await this.plugin.call('fileManager', 'open', 'script.config.json') try { const content = await this.plugin.call('fileManager', 'readFile', configFileName) + console.log('loadCustomConfig', content) const parsed = JSON.parse(content) this.customConfig = parsed + console.log('loadCustomConfig', this.customConfig) } catch (e) { return { - baseConfiguration: 'default', - dependencies: [] + defaultConfig: 'default', + customConfig: { + baseConfiguration: 'default', + dependencies: [] + } } } + } async openCustomConfig() { - try { - await this.plugin.call('fileManager', 'open', 'script.config.json') - }catch(e){ + try { + await this.plugin.call('fileManager', 'open', 'script.config.json') + } catch (e) { - } + } } async loadConfigurations() { try { - const response = await axios.get('http://localhost:3000/projects.json?timestamp=' + Date.now()); + const response = await axios.get(`${baseUrl}/projects.json?timestamp=${Date.now()}`); this.configurations = response.data; + // find the default otherwise pick the first one as the active + this.configurations.forEach((config) => { + console.log('loadConfigurations', config.name, this.customConfig.defaultConfig) + if (config.name === (this.customConfig.defaultConfig)) { + this.activeConfig = config; + console.log('loadConfigurations found', this.activeConfig) + } + }); + if (!this.activeConfig) { + this.activeConfig = this.configurations[0]; + } + console.log('active config', this.configurations, this.activeConfig) } catch (error) { console.error("Error fetching the projects data:", error); } } - async saveCustomConfig(content: customScriptRunnerConfig) { + async saveCustomConfig(content: ScriptRunnerConfig) { console.log('saveCustomConfig', content) await this.plugin.call('fileManager', 'writeFile', 'script.config.json', JSON.stringify(content, null, 2)) } @@ -244,9 +342,23 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { console.log('activateCustomScriptRunner', config) // post config to localhost:4000 using axios try { - const result = await axios.post('http://localhost:4000/build', config) + const result = await axios.post(customBuildUrl, config) console.log(result) if (result.data.hash) { + + const newConfig: ProjectConfiguration = { + name: result.data.hash, + title: 'Custom configuration', + publish: true, + description: `Extension of ${config.baseConfiguration}`, + dependencies: config.dependencies, + replacements: {}, + errorStatus: false, + error: '', + isLoading: false + }; + this.configurations.push(newConfig) + this.renderComponent() await this.loadScriptRunner(result.data.hash) } return result.data.hash diff --git a/apps/remix-ide/src/assets/list.json b/apps/remix-ide/src/assets/list.json index 82f8fe17bb..3d7795ee81 100644 --- a/apps/remix-ide/src/assets/list.json +++ b/apps/remix-ide/src/assets/list.json @@ -1033,9 +1033,21 @@ "urls": [ "dweb:/ipfs/QmVTALD1WUQwRvEL19jgwrEFyBJMQmy9z32zvT6TAtYPY1" ] + }, + { + "path": "soljson-v0.8.28+commit.7893614a.js", + "version": "0.8.28", + "build": "commit.7893614a", + "longVersion": "0.8.28+commit.7893614a", + "keccak256": "0x8e01bd0cafb8a8bab060453637101a88e4ab6d41c32645a26eaca541fb169c8e", + "sha256": "0x72ef580a6ec5943130028e5294313f24e9435520acc89f8c9dbfd0139d9ae146", + "urls": [ + "dweb:/ipfs/QmVtdNYdUC4aX6Uk5LrxDT55B7NgGLnLcA2wTecF5xUbSS" + ] } ], "releases": { + "0.8.28": "soljson-v0.8.28+commit.7893614a.js", "0.8.27": "soljson-v0.8.27+commit.40a35a09.js", "0.8.26": "soljson-v0.8.26+commit.8a97fa7a.js", "0.8.25": "soljson-v0.8.25+commit.b61c2a91.js", @@ -1131,5 +1143,5 @@ "0.4.0": "soljson-v0.4.0+commit.acd334c9.js", "0.3.6": "soljson-v0.3.6+commit.3fc68da5.js" }, - "latestRelease": "0.8.27" + "latestRelease": "0.8.28" } \ No newline at end of file diff --git a/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx b/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx index 51e611edc3..8c0cac1c67 100644 --- a/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx +++ b/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx @@ -13,8 +13,7 @@ export interface ScriptRunnerUIProps { publishedConfigurations: ProjectConfiguration[]; openCustomConfig: () => any; saveCustomConfig(content: customScriptRunnerConfig): void; - activateCustomScriptRunner(config: customScriptRunnerConfig): string; - addCustomConfig(config: ProjectConfiguration): void; + activateCustomScriptRunner(config: customScriptRunnerConfig): Promise; customConfig: customScriptRunnerConfig; } @@ -82,18 +81,7 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { console.log(customConfig); setLoading(true); try { - const loadedConfig = await props.activateCustomScriptRunner(customConfig); - console.log(loadedConfig); - const newConfig: ProjectConfiguration = { - name: loadedConfig, - publish: true, - description: `Extension of ${baseConfig}`, - dependencies: dependencies, - replacements: {} - }; - console.log(newConfig); - props.addCustomConfig(newConfig); - + await props.activateCustomScriptRunner(customConfig); } catch (e) { console.log(e) } finally { diff --git a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx index 7455aa60b1..40877b1716 100644 --- a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx +++ b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx @@ -3,55 +3,36 @@ import { Accordion, Card, Button } from "react-bootstrap"; import axios from "axios"; import { customScriptRunnerConfig, Dependency, ProjectConfiguration } from "../types"; import { FormattedMessage } from "react-intl"; -import { faCheck, faToggleOn } from "@fortawesome/free-solid-svg-icons"; +import { faCheck, faExclamationCircle, faToggleOn } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Profile } from "@remixproject/plugin-utils"; import { IframeProfile, ViewProfile } from "@remixproject/engine-web"; import { Plugin } from "@remixproject/engine"; import { CustomScriptRunner } from "./custom-script-runner"; +import { CustomTooltip } from "@remix-ui/helper"; export interface ScriptRunnerUIProps { // Add your props here - loadScriptRunner: (name: string) => void; + loadScriptRunner: (config: ProjectConfiguration) => void; // build custom script runner buildScriptRunner: (dependencies: Dependency[]) => void; openCustomConfig: () => any; saveCustomConfig(content: customScriptRunnerConfig): void; - activateCustomScriptRunner(config: customScriptRunnerConfig): string; + activateCustomScriptRunner(config: customScriptRunnerConfig): Promise; customConfig: customScriptRunnerConfig; configurations: ProjectConfiguration[]; + activeConfig: ProjectConfiguration; + enableCustomScriptRunner: boolean; } export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => { - const { loadScriptRunner, configurations } = props; + const { loadScriptRunner, configurations, activeConfig, enableCustomScriptRunner } = props; const [activeKey, setActiveKey] = useState('default'); - const [activeConfig, setActiveConfig] = useState('default'); - - useEffect(() => { - // Fetch the JSON data from the localhost server using Axios - - }, []); // Empty array ensures this effect runs once when the component mounts - - const handleSelect = (key) => { - console.log("Selected key:", key, activeKey); - setActiveConfig(key); - console.log(loadScriptRunner) - loadScriptRunner(key) - }; - - const addCustomConfig = (config: ProjectConfiguration) => { - if(configurations.find((c) => c.name === config.name)) { - return; - } - //setConfigurations([...configurations, config]); - setActiveConfig(config.name); - } if (!configurations) { return
Loading...
; } - return (
@@ -64,12 +45,24 @@ export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => { textOverflow: 'ellipsis' }} > -
{config.name}
+
{config.title || config.name}
-
handleSelect(config.name)} className="pointer px-2"> - {activeConfig !== config.name ? - : - +
+ {config.isLoading &&
+ +
} + {config.errorStatus && config.error &&
+ + + +
} + {!config.isLoading && +
loadScriptRunner(config)} className="pointer px-2"> + {activeConfig && activeConfig.name !== config.name ? + : + + } +
}
@@ -88,14 +81,14 @@ export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => {
))} - config.publish)} - buildScriptRunner={props.buildScriptRunner} /> + {enableCustomScriptRunner && + config.publish)} + buildScriptRunner={props.buildScriptRunner} />} ); }; diff --git a/libs/remix-ui/scriptrunner/src/types/index.ts b/libs/remix-ui/scriptrunner/src/types/index.ts index b474249ca6..99e453bd37 100644 --- a/libs/remix-ui/scriptrunner/src/types/index.ts +++ b/libs/remix-ui/scriptrunner/src/types/index.ts @@ -1,3 +1,5 @@ +import { defaultConfig } from "@web3modal/ethers5/react"; + export interface Dependency { version: string; name: string; @@ -17,9 +19,19 @@ export interface Dependency { description: string; dependencies: Dependency[]; replacements: Replacements; + title: string; + errorStatus: boolean; + error: string; + isLoading: boolean; } export interface customScriptRunnerConfig { baseConfiguration: string; dependencies: Dependency[]; - } \ No newline at end of file + } + + export interface ScriptRunnerConfig { + defaultConfig: string, + customConfig: customScriptRunnerConfig + } + From 027181a7b249849b5afaacbd3d433f26d29676bb Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Wed, 23 Oct 2024 16:52:41 +0200 Subject: [PATCH 14/36] reload runner --- .../src/app/components/hidden-panel.tsx | 7 +++++ apps/remix-ide/src/app/components/panel.ts | 2 ++ .../src/app/tabs/script-runner-ui.tsx | 30 +++++++++++++------ apps/remix-ide/src/assets/img/ts-logo.svg | 1 + apps/remix-ide/src/remixEngine.js | 2 +- .../scriptrunner/src/lib/script-runner-ui.tsx | 24 ++++++++++++--- 6 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 apps/remix-ide/src/assets/img/ts-logo.svg diff --git a/apps/remix-ide/src/app/components/hidden-panel.tsx b/apps/remix-ide/src/app/components/hidden-panel.tsx index 27993313c0..fc492475ba 100644 --- a/apps/remix-ide/src/app/components/hidden-panel.tsx +++ b/apps/remix-ide/src/app/components/hidden-panel.tsx @@ -23,11 +23,18 @@ export class HiddenPanel extends AbstractPanel { } addView(profile: any, view: any): void { + console.log('addView', profile, view) super.removeView(profile) + this.renderComponent() super.addView(profile, view) this.renderComponent() } + removeView(profile: any): void { + super.removeView(profile) + this.renderComponent() + } + updateComponent(state: any) { return } plugins={state.plugins} /> } diff --git a/apps/remix-ide/src/app/components/panel.ts b/apps/remix-ide/src/app/components/panel.ts index bf80f3614d..fa025ca175 100644 --- a/apps/remix-ide/src/app/components/panel.ts +++ b/apps/remix-ide/src/app/components/panel.ts @@ -23,6 +23,8 @@ export class AbstractPanel extends HostPlugin { } addView (profile, view) { + console.log('addView', profile, view) + console.log('this', this.plugins) if (this.plugins[profile.name]) throw new Error(`Plugin ${profile.name} already rendered`) this.plugins[profile.name] = { profile: profile, diff --git a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx index 2064b2de8c..ebac652b33 100644 --- a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx +++ b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx @@ -15,7 +15,7 @@ const profile = { displayName: 'Script configuration', methods: ['execute'], events: ['log', 'info', 'warn', 'error'], - icon: 'assets/img/settings.webp', + icon: 'assets/img/ts-logo.svg', description: 'Set up a script runner', kind: '', location: 'sidePanel', @@ -122,13 +122,12 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { } async selectScriptRunner(config: ProjectConfiguration) { - console.log('selectScriptRunner', config) - await this.loadScriptRunner(config) - await this.saveCustomConfig(this.customConfig) + if (await this.loadScriptRunner(config)) + await this.saveCustomConfig(this.customConfig) } async loadScriptRunner(config: ProjectConfiguration): Promise { - console.log('loadScriptRunner', config) + //console.log('loadScriptRunner', config) const profile: Profile = await this.plugin.call('manager', 'getProfile', 'scriptRunner') this.scriptRunnerProfileName = profile.name const testPluginName = localStorage.getItem('test-plugin-name') @@ -144,13 +143,14 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { url = `${baseUrl}?template=${config.name}×tamp=${Date.now()}` } } - console.log('loadScriptRunner', profile) + //console.log('loadScriptRunner', profile) const newProfile: IframeProfile = { ...profile, name: profile.name + config.name, location: 'hiddenPanel', url: url } + console.log('loadScriptRunner', newProfile) let result = null try { @@ -161,7 +161,7 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { await this.engine.register(plugin) } await this.plugin.call('manager', 'activatePlugin', newProfile.name) - + console.log('activate done', newProfile.name) this.activeConfig = config this.on(newProfile.name, 'log', this.log.bind(this)) this.on(newProfile.name, 'info', this.info.bind(this)) @@ -172,13 +172,22 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { this.setErrorStatus(config.name, false, '') result = true } catch (e) { - - this.engine.remove(newProfile.name) + console.log('Error loading script runner: ', newProfile.name, e) + const iframe = document.getElementById(`plugin-${newProfile.name}`); + if (iframe) { + console.log('remove iframe', iframe) + await this.call('hiddenPanel', 'removeView', newProfile) + } + console.log('deactivate', (this.engine as any)) + delete (this.engine as any).manager.profiles[newProfile.name] + delete (this.engine as any).plugins[newProfile.name] console.log('is registered', newProfile.name, this.engine.isRegistered(newProfile.name)) console.log('Error loading script runner: ', newProfile.name, e) + console.log('REMOVE', newProfile.name) this.setErrorStatus(config.name, true, e) result = false } + console.log('ENGINE', this.engine) this.setIsLoading(config.name, false) this.renderComponent() return result @@ -186,6 +195,7 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { } async execute(script: string, filePath: string) { + console.log(this.engine) console.log('is registered', `${this.scriptRunnerProfileName}${this.activeConfig.name}`, this.engine.isRegistered(`${this.scriptRunnerProfileName}${this.activeConfig.name}`)) if (!this.scriptRunnerProfileName || !this.engine.isRegistered(`${this.scriptRunnerProfileName}${this.activeConfig.name}`)) { if (!await this.loadScriptRunner(this.activeConfig)) { @@ -195,10 +205,12 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { } console.log('execute', this.activeConfig) try { + this.setIsLoading(this.activeConfig.name, true) await this.call(`${this.scriptRunnerProfileName}${this.activeConfig.name}`, 'execute', script, filePath) } catch (e) { console.error('Error executing script', e) } + this.setIsLoading(this.activeConfig.name, false) } diff --git a/apps/remix-ide/src/assets/img/ts-logo.svg b/apps/remix-ide/src/assets/img/ts-logo.svg new file mode 100644 index 0000000000..dc26789c4b --- /dev/null +++ b/apps/remix-ide/src/assets/img/ts-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/remix-ide/src/remixEngine.js b/apps/remix-ide/src/remixEngine.js index f8bd8d6641..97ef962d48 100644 --- a/apps/remix-ide/src/remixEngine.js +++ b/apps/remix-ide/src/remixEngine.js @@ -29,7 +29,7 @@ export class RemixEngine extends Engine { if (name === 'solcoder') return { queueTimeout: 60000 * 2 } if (name === 'cookbookdev') return { queueTimeout: 60000 * 3 } if (name === 'contentImport') return { queueTimeout: 60000 * 3 } - + if (name === 'scriptRunnerBridge') return { queueTimeout: 2000 } return { queueTimeout: 10000 } } diff --git a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx index 40877b1716..e953b93e4e 100644 --- a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx +++ b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx @@ -3,7 +3,7 @@ import { Accordion, Card, Button } from "react-bootstrap"; import axios from "axios"; import { customScriptRunnerConfig, Dependency, ProjectConfiguration } from "../types"; import { FormattedMessage } from "react-intl"; -import { faCheck, faExclamationCircle, faToggleOn } from "@fortawesome/free-solid-svg-icons"; +import { faAngleDown, faAngleRight, faCaretDown, faCaretRight, faCheck, faChevronLeft, faChevronUp, faExclamationCircle, faRedoAlt, faToggleOn } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Profile } from "@remixproject/plugin-utils"; import { IframeProfile, ViewProfile } from "@remixproject/engine-web"; @@ -29,13 +29,18 @@ export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => { const { loadScriptRunner, configurations, activeConfig, enableCustomScriptRunner } = props; const [activeKey, setActiveKey] = useState('default'); + useEffect(() => { + }, [activeKey]) + if (!configurations) { return
Loading...
; } + + return (
- + {configurations.filter((config) => config.publish).map((config: ProjectConfiguration, index) => (
@@ -44,8 +49,14 @@ export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => { overflowX: 'hidden', textOverflow: 'ellipsis' }} + onClick={() => setActiveKey(activeKey === config.name ? '' : config.name)} > -
{config.title || config.name}
+
+ {activeKey === config.name ? + : + } +
{config.title || config.name}
+
{config.isLoading &&
@@ -55,8 +66,13 @@ export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => { +
} - {!config.isLoading && + {!config.isLoading && config.errorStatus && config.error && +
loadScriptRunner(config)} className="pointer px-2"> + +
} + {!config.isLoading && !config.errorStatus && !config.error &&
loadScriptRunner(config)} className="pointer px-2"> {activeConfig && activeConfig.name !== config.name ? : From 31920ef0658344e4612d1b70c472513232d6dc1b Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Thu, 24 Oct 2024 07:39:46 +0200 Subject: [PATCH 15/36] merge from master --- .circleci/config.yml | 373 +- .github/workflows/pr-reminder.yml | 2 +- .gitignore | 4 +- .nvmrc | 1 + .prettierrc.json | 4 +- CONTRIBUTING.md | 6 +- README.md | 4 +- .../src/app/components/container.tsx | 6 +- .../.babelrc | 10 +- .../.browserslistrc | 0 apps/contract-verification/.eslintrc | 3 + .../.eslintrc.json | 0 apps/contract-verification/project.json | 69 + apps/contract-verification/src/app/App.css | 14 + .../src/app/AppContext.tsx | 34 + .../app/ContractVerificationPluginClient.ts | 18 + .../src/app/Verifiers/AbstractVerifier.ts | 16 + .../src/app/Verifiers/BlockscoutVerifier.ts | 50 + .../src/app/Verifiers/EtherscanVerifier.ts | 289 + .../src/app/Verifiers/SourcifyVerifier.ts | 169 + .../src/app/Verifiers/index.ts | 30 + .../src/app/VerifyFormContext.tsx | 46 + apps/contract-verification/src/app/app.tsx | 154 + .../src/app/components/AccordionReceipt.tsx | 105 + .../src/app/components/ConfigInput.tsx | 69 + .../app/components/ConstructorArguments.tsx | 135 + .../app/components/ContractAddressInput.tsx | 33 + .../src/app/components/ContractDropdown.css | 3 + .../src/app/components/ContractDropdown.tsx | 73 + .../src/app/components/NavMenu.tsx | 33 + .../components/SearchableChainDropdown.tsx | 113 + .../src/app/components/index.tsx | 5 + .../src/app/hooks/useLocalStorage.tsx | 9 +- .../src/app/hooks/useSourcifySupported.tsx | 33 + .../src/app/layouts/Default.tsx | 25 + .../src/app/layouts/index.ts | 1 + apps/contract-verification/src/app/routes.tsx | 49 + .../src/app/types/SettingsTypes.ts | 19 + .../src/app/types/ThemeType.ts | 1 + .../src/app/types/VerificationTypes.ts | 80 + .../src/app/types/index.ts | 3 + .../src/app/utils/default-apis.json | 576 + .../src/app/utils/default-settings.ts | 28 + .../src/app/utils/index.ts | 1 + .../src/app/views/LookupView.tsx | 167 + .../src/app/views/ReceiptsView.tsx | 16 + .../src/app/views/SettingsView.tsx | 54 + .../src/app/views/VerifyView.tsx | 279 + .../src/app/views/index.ts | 4 + .../src/assets/.gitkeep | 0 .../src/environments/environment.prod.ts | 4 +- .../src/environments/environment.ts | 4 +- .../src/favicon.ico | Bin .../src/index.html | 5 +- apps/contract-verification/src/main.tsx | 10 + .../src/polyfills.ts | 4 +- apps/contract-verification/src/profile.json | 16 + .../src/styles.css | 0 .../tsconfig.app.json | 0 apps/contract-verification/tsconfig.json | 18 + .../webpack.config.js | 26 +- apps/etherscan/project.json | 69 - apps/etherscan/src/app/App.css | 7 - apps/etherscan/src/app/AppContext.tsx | 25 - .../src/app/EtherscanPluginClient.ts | 70 - apps/etherscan/src/app/app.tsx | 136 - .../src/app/components/HeaderWithSettings.tsx | 81 - .../src/app/components/SubmitButton.tsx | 34 - apps/etherscan/src/app/components/index.ts | 2 - apps/etherscan/src/app/layouts/Default.tsx | 17 - apps/etherscan/src/app/layouts/index.ts | 1 - apps/etherscan/src/app/routes.tsx | 37 - apps/etherscan/src/app/types/Receipt.ts | 9 - apps/etherscan/src/app/types/ThemeType.ts | 1 - apps/etherscan/src/app/types/index.ts | 2 - apps/etherscan/src/app/utils/index.ts | 1 - apps/etherscan/src/app/utils/networks.ts | 46 - apps/etherscan/src/app/utils/scripts.ts | 30 - apps/etherscan/src/app/utils/utilities.ts | 69 - apps/etherscan/src/app/utils/verify.ts | 206 - .../src/app/views/CaptureKeyView.tsx | 63 - apps/etherscan/src/app/views/ErrorView.tsx | 16 - apps/etherscan/src/app/views/HomeView.tsx | 31 - apps/etherscan/src/app/views/ReceiptsView.tsx | 170 - apps/etherscan/src/app/views/VerifyView.tsx | 235 - apps/etherscan/src/app/views/index.ts | 4 - apps/etherscan/src/main.tsx | 14 - apps/etherscan/src/profile.json | 16 - apps/learneth/src/App.tsx | 38 +- .../src/components/LoadingScreen/index.tsx | 2 +- .../src/components/RepoImporter/index.tsx | 21 +- apps/learneth/src/pages/Home/index.tsx | 15 +- apps/learneth/src/pages/Logo/index.tsx | 9 +- apps/learneth/src/redux/models/remixide.ts | 1 + apps/learneth/src/redux/models/workshop.ts | 51 +- apps/learneth/src/redux/store.ts | 13 +- apps/quick-dapp/src/actions/index.ts | 25 +- apps/{etherscan => remix-dapp}/.eslintrc | 0 apps/remix-dapp/README.md | 1 + apps/remix-dapp/package.json | 9 + apps/remix-dapp/project.json | 69 + apps/remix-dapp/src/App.css | 25 + apps/remix-dapp/src/App.tsx | 65 + apps/remix-dapp/src/actions/index.ts | 234 + apps/remix-dapp/src/assets/instance.json | 507 + apps/remix-dapp/src/assets/logo.png | Bin 0 -> 15967 bytes .../src/components/ContractGUI/index.tsx | 361 + .../src/components/DappTop/index.tsx | 74 + .../src/components/DragBar/index.tsx | 62 + .../remix-dapp/src/components/Home/mobile.tsx | 88 + apps/remix-dapp/src/components/Home/pc.tsx | 47 + .../src/components/SettingsUI/account.tsx | 58 + .../src/components/SettingsUI/gasPrice.tsx | 35 + .../src/components/SettingsUI/index.tsx | 94 + .../src/components/SettingsUI/locale.tsx | 53 + .../src/components/SettingsUI/network.tsx | 47 + .../src/components/SettingsUI/theme.tsx | 88 + .../src/components/SettingsUI/value.tsx | 108 + .../components/UiTerminal/ChechTxStatus.tsx | 29 + .../src/components/UiTerminal/Context.tsx | 83 + .../src/components/UiTerminal/RenderCall.tsx | 84 + .../UiTerminal/RenderKnownTransactions.tsx | 76 + .../src/components/UiTerminal/Table.tsx | 327 + .../src/components/UiTerminal/TxList.tsx | 209 + .../src/components/UiTerminal/index.css | 92 + .../src/components/UiTerminal/index.tsx | 174 + .../src/components/UiTerminal/types.ts | 32 + .../src/components/UiTerminal/utils.ts | 68 + .../src/components/UniversalDappUI/index.css | 41 + .../src/components/UniversalDappUI/index.tsx | 247 + .../UniversalDappUI/lowLevelInteractions.tsx | 211 + apps/remix-dapp/src/contexts/index.ts | 3 + apps/remix-dapp/src/index.css | 4 + apps/remix-dapp/src/index.html | 17 + apps/remix-dapp/src/locales/en/index.ts | 14 + apps/remix-dapp/src/locales/en/terminal.json | 44 + apps/remix-dapp/src/locales/en/udapp.json | 155 + apps/remix-dapp/src/locales/es/index.ts | 18 + apps/remix-dapp/src/locales/es/terminal.json | 43 + apps/remix-dapp/src/locales/es/udapp.json | 141 + apps/remix-dapp/src/locales/fr/index.ts | 18 + apps/remix-dapp/src/locales/fr/terminal.json | 43 + apps/remix-dapp/src/locales/fr/udapp.json | 141 + apps/remix-dapp/src/locales/it/index.ts | 18 + apps/remix-dapp/src/locales/it/terminal.json | 43 + apps/remix-dapp/src/locales/it/udapp.json | 141 + apps/remix-dapp/src/locales/zh/index.ts | 18 + apps/remix-dapp/src/locales/zh/terminal.json | 43 + apps/remix-dapp/src/locales/zh/udapp.json | 141 + apps/remix-dapp/src/main.tsx | 9 + apps/remix-dapp/src/reducers/state.ts | 51 + apps/remix-dapp/src/types.ts | 13 + apps/remix-dapp/src/utils/buildData.ts | 41 + apps/remix-dapp/src/utils/chains.ts | 151 + apps/remix-dapp/src/utils/constants.ts | 53 + apps/remix-dapp/src/utils/metamask.ts | 96 + apps/remix-dapp/src/utils/tools.ts | 17 + apps/remix-dapp/src/utils/txRunner.ts | 277 + apps/remix-dapp/src/utils/walletConnect.ts | 129 + apps/remix-dapp/src/vite-env.d.ts | 1 + apps/remix-dapp/tsconfig.app.json | 23 + apps/{etherscan => remix-dapp}/tsconfig.json | 0 apps/remix-dapp/webpack.config.js | 108 + apps/remix-dapp/yarn.lock | 34 + .../src/commands/addLocalPlugin.ts | 2 +- .../src/githttpbackend/package.json | 4 +- .../src/githttpbackend/yarn.lock | 105 +- .../src/tests/dgit_github.test.ts | 7 + .../src/tests/dgit_local.test.ts | 1 + apps/remix-ide-e2e/src/tests/editor.test.ts | 10 +- .../src/tests/etherscan_api.test.ts | 2 +- apps/remix-ide-e2e/src/tests/matomo.test.ts | 492 + .../src/tests/pinned_contracts.test.ts | 59 +- .../remix-ide-e2e/src/tests/quickDapp.test.ts | 4 +- .../src/tests/runAndDeploy_injected.test.ts | 30 + apps/remix-ide-e2e/src/tests/search.test.ts | 6 +- .../src/tests/signingMessage.test.ts | 19 +- apps/remix-ide-e2e/src/tests/terminal.test.ts | 22 +- .../remix-ide-e2e/src/tests/vyper_api.test.ts | 161 +- .../remix-ide-e2e/src/tests/workspace.test.ts | 20 +- .../src/tests/workspace_git.test.ts | 5 +- apps/remix-ide/ci/downloadsoljson.sh | 32 +- .../ci/update_desktop_release_assets.ts | 242 + apps/remix-ide/contracts/ballot.sol | 55 +- apps/remix-ide/project.json | 2 +- apps/remix-ide/research-project.md | 2 +- apps/remix-ide/src/app.js | 148 +- .../components/plugin-manager-component.js | 3 +- apps/remix-ide/src/app/components/preload.tsx | 2 + .../src/app/components/vertical-icons.tsx | 7 - apps/remix-ide/src/app/files/dgitProvider.ts | 621 +- apps/remix-ide/src/app/files/fileManager.ts | 2 +- apps/remix-ide/src/app/panels/file-panel.js | 6 - apps/remix-ide/src/app/panels/layout.ts | 37 +- .../app/plugins/electron/appUpdaterPlugin.ts | 54 + .../src/app/plugins/electron/foundryPlugin.ts | 13 + .../src/app/plugins/electron/hardhatPlugin.ts | 13 + .../plugins/electron/remixAIDesktopPlugin.tsx | 33 + .../src/app/plugins/electron/slitherPlugin.ts | 13 + apps/remix-ide/src/app/plugins/matomo.ts | 2 +- .../app/plugins/permission-handler-plugin.tsx | 2 +- .../src/app/plugins/remix-templates.ts | 23 +- .../src/app/plugins/remixAIPlugin.tsx | 169 + apps/remix-ide/src/app/plugins/remixGuide.css | 6 + apps/remix-ide/src/app/plugins/remixGuide.tsx | 38 +- .../src/app/plugins/remixGuideData.json | 253 +- apps/remix-ide/src/app/plugins/solcoderAI.tsx | 283 - .../templates-selection-plugin.tsx | 24 +- .../plugins/templates-selection/templates.ts | 293 +- .../src/app/providers/abstract-provider.tsx | 2 +- .../injected-arbitrum-one-provider.tsx | 17 - .../providers/injected-custom-provider.tsx | 10 +- .../injected-ephemery-testnet-provider.tsx | 40 - .../providers/injected-optimism-provider.tsx | 17 - .../injected-provider-trustwallet.tsx | 26 - .../injected-skale-chaos-testnet-provider.tsx | 27 - apps/remix-ide/src/app/tabs/compile-tab.js | 6 +- .../src/app/tabs/locales/en/editor.json | 2 +- .../src/app/tabs/locales/en/electron.json | 4 +- .../src/app/tabs/locales/en/filePanel.json | 16 +- .../src/app/tabs/locales/en/gitui.json | 3 + .../src/app/tabs/locales/en/home.json | 7 +- .../src/app/tabs/locales/en/settings.json | 18 +- .../src/app/tabs/locales/en/udapp.json | 21 +- .../src/app/tabs/locales/en/vyper.json | 3 + .../src/app/tabs/runTab/model/recorder.js | 4 +- .../src/app/tabs/script-runner-ui.tsx | 14 +- apps/remix-ide/src/app/tabs/settings-tab.tsx | 2 + .../src/app/udapp/{run-tab.js => run-tab.tsx} | 93 +- .../css/themes/bootstrap-cerulean.min.css | 4 +- .../css/themes/bootstrap-cyborg.min.css | 3 +- .../css/themes/bootstrap-flatly.min.css | 2 +- .../css/themes/bootstrap-spacelab.min.css | 3 +- .../assets/css/themes/remix-black_undtds.css | 1 + .../assets/css/themes/remix-candy_ikhg4m.css | 1 + .../assets/css/themes/remix-dark_tvx1s2.css | 1 + .../assets/css/themes/remix-hacker_owl.css | 1 + .../assets/css/themes/remix-light_powaqg.css | 1 + .../css/themes/remix-midcentury_hrzph3.css | 1 + .../src/assets/css/themes/remix-unicorn.css | 1 + .../src/assets/css/themes/remix-violet.css | 1 + .../remix-ide/src/assets/img/gnosis_chain.png | Bin 0 -> 7928 bytes apps/remix-ide/src/assets/img/remi-prof.webp | Bin 0 -> 63410 bytes apps/remix-ide/src/assets/js/loader.js | 67 +- apps/remix-ide/src/assets/list.json | 2 +- apps/remix-ide/src/blockchain/blockchain.tsx | 2 + .../src/blockchain/providers/injected.ts | 6 +- apps/remix-ide/src/blockchain/providers/vm.ts | 21 +- .../src/blockchain/providers/worker-vm.ts | 31 + apps/remix-ide/src/remixAppManager.js | 50 +- apps/remix-ide/src/remixEngine.js | 4 +- apps/remixdesktop/README.md | 152 +- apps/remixdesktop/TEST.md | 124 + apps/remixdesktop/after-pack.js | 14 + apps/remixdesktop/afterbuild.js | 30 + apps/remixdesktop/aftersign.js | 104 + apps/remixdesktop/alpha.json | 61 + apps/remixdesktop/beta.json | 61 + .../circom-download/latest/circom-linux-amd64 | Bin 0 -> 11786424 bytes apps/remixdesktop/entitlements.mac.plist | 13 + apps/remixdesktop/esbuild.js | 12 + apps/remixdesktop/insiders.json | 62 + apps/remixdesktop/latest.json | 61 + apps/remixdesktop/nightwatch.conf.js | 344 + apps/remixdesktop/notarizedmg.sh | 39 + apps/remixdesktop/package-lock.json | 14684 ++++ apps/remixdesktop/package.json | 116 +- apps/remixdesktop/run_ci_test.sh | 19 + apps/remixdesktop/run_git_ui_isogit_tests.sh | 14 + apps/remixdesktop/rundist.bash | 24 + apps/remixdesktop/rundist_esbuild.bash | 24 + apps/remixdesktop/rundist_tsc.bash | 24 + apps/remixdesktop/rundist_webpack.bash | 24 + apps/remixdesktop/splice_tests.js | 35 + apps/remixdesktop/src/engine.ts | 34 +- .../src/lib/InferenceServerManager.ts | 525 + apps/remixdesktop/src/lib/databatcher.ts | 2 +- apps/remixdesktop/src/lib/remixd.ts | 37 + apps/remixdesktop/src/lib/utils.ts | 24 + apps/remixdesktop/src/main.ts | 73 +- apps/remixdesktop/src/menus/file.ts | 31 - apps/remixdesktop/src/plugins/appUpdater.ts | 122 + .../src/plugins/compilerLoader.ts | 49 +- .../remixdesktop/src/plugins/foundryPlugin.ts | 248 + apps/remixdesktop/src/plugins/fsPlugin.ts | 130 +- .../remixdesktop/src/plugins/hardhatPlugin.ts | 220 + apps/remixdesktop/src/plugins/isoGitPlugin.ts | 285 +- .../remixdesktop/src/plugins/remixAIDektop.ts | 115 + .../remixdesktop/src/plugins/ripgrepPlugin.ts | 29 +- .../remixdesktop/src/plugins/slitherPlugin.ts | 197 + apps/remixdesktop/src/plugins/templates.ts | 24 +- apps/remixdesktop/src/plugins/xtermPlugin.ts | 92 +- apps/remixdesktop/src/preload.ts | 18 +- apps/remixdesktop/src/tools/git.ts | 84 +- apps/remixdesktop/src/types/index.ts | 9 - apps/remixdesktop/src/utils/config.ts | 8 +- apps/remixdesktop/src/utils/matamo.ts | 51 + .../test/cache_dir/remixdesktop.json | 2 + apps/remixdesktop/test/lib/git.ts | 195 + apps/remixdesktop/test/nighwatch.app.ts | 101 + .../test/tests/app/compiler.test.ts | 37 + .../test/tests/app/externaleditor.test.ts | 122 + .../test/tests/app/foundry.test.ts | 157 + apps/remixdesktop/test/tests/app/gist.test.ts | 36 + .../test/tests/app/git-ui.test.ts | 203 + .../test/tests/app/git-ui_2.test.ts | 181 + .../test/tests/app/git-ui_3.test.ts | 153 + .../test/tests/app/git-ui_4.test.ts | 200 + apps/remixdesktop/test/tests/app/git.test.ts | 28 + .../test/tests/app/github.test.ts | 255 + .../test/tests/app/github_2.test.ts | 190 + .../test/tests/app/github_3.test.ts | 180 + .../test/tests/app/hardhat.test.ts | 90 + .../test/tests/app/offline.test.ts | 48 + .../test/tests/app/search.test.ts | 269 + .../test/tests/app/slitherlinux.test.ts | 107 + .../test/tests/app/templates.test.ts | 35 + .../remixdesktop/test/tests/app/xterm.test.ts | 246 + .../test/tests/app/xtermwin.test.ts | 249 + apps/remixdesktop/test/types/index.d.ts | 108 + apps/remixdesktop/tsconfig.e2e.json | 8 + apps/remixdesktop/tsconfig.json | 43 +- apps/remixdesktop/webpack.config.js | 44 + apps/remixdesktop/yarn.lock | 3523 +- apps/solidity-compiler/src/app/compiler.ts | 4 + apps/vyper/README.md | 2 +- apps/vyper/src/app/app.css | 6 + apps/vyper/src/app/app.tsx | 67 +- .../src/app/components/CompileErrorCard.tsx | 3 +- .../src/app/components/CompilerButton.tsx | 39 +- apps/vyper/src/app/utils/compiler.tsx | 147 +- apps/vyper/src/app/utils/index.ts | 17 + apps/vyper/src/app/utils/remix-client.tsx | 27 +- apps/vyper/src/app/utils/types.ts | 39 +- libs/ghaction-helper/package.json | 8 +- libs/remix-ai-core/.eslintrc | 1 + libs/remix-ai-core/README.md | 7 + libs/remix-ai-core/package-lock.json | 5344 ++ libs/remix-ai-core/project.json | 32 + .../src/agents/codeExplainAgent.ts | 29 + .../src/agents/completionAgent.ts | 23 + .../remix-ai-core/src/agents/securityAgent.ts | 29 + .../src/helpers/dowload_model.ts | 0 .../src/helpers/inferenceServerReleases.ts | 55 + libs/remix-ai-core/src/index.ts | 20 + .../src/inferencers/remote/remoteInference.ts | 141 + libs/remix-ai-core/src/prompts/chat.ts | 21 + .../src/prompts/completionPrompts.ts | 18 + .../src/prompts/promptBuilder.ts | 28 + libs/remix-ai-core/src/types/constants.ts | 9 + libs/remix-ai-core/src/types/models.ts | 81 + libs/remix-ai-core/src/types/types.ts | 87 + libs/remix-ai-core/tsconfig.json | 10 + libs/remix-ai-core/tsconfig.lib.json | 15 + libs/remix-analyzer/package.json | 8 +- libs/remix-api/src/index.ts | 3 +- .../src/lib/plugins/filePanel-api.ts | 2 +- .../src/lib/plugins/fileSystem-api.ts | 5 +- libs/remix-api/src/lib/plugins/fs-api.ts | 13 + libs/remix-api/src/lib/plugins/git-api.ts | 43 + .../src/lib/plugins/remixAIDesktop-api.ts | 23 + libs/remix-api/src/lib/plugins/remixai-api.ts | 21 + .../remix-api/src/lib/plugins/terminal-api.ts | 11 + libs/remix-api/src/lib/remix-api.ts | 13 +- libs/remix-api/src/lib/types/git.ts | 206 + libs/remix-astwalker/package.json | 6 +- .../src/lib/compiler-artefacts.ts | 102 +- .../remix-core-plugin/src/lib/gist-handler.ts | 61 +- libs/remix-debug/package.json | 12 +- libs/remix-git/index.ts | 1 + libs/remix-git/src/isogit.ts | 345 + libs/remix-lib/package.json | 4 +- libs/remix-lib/src/execution/txExecution.ts | 2 +- libs/remix-lib/src/execution/txRunnerWeb3.ts | 3 +- libs/remix-lib/src/types/ICompilerApi.ts | 1 + libs/remix-simulator/package.json | 6 +- libs/remix-solidity/package.json | 6 +- libs/remix-tests/package.json | 10 +- libs/remix-tests/src/runTestFiles.ts | 6 +- libs/remix-tests/src/runTestSources.ts | 6 +- libs/remix-ui/app/src/index.ts | 2 +- .../app/src/lib/remix-app/actions/app.ts | 2 +- .../remix-app/components/dragbar/dragbar.tsx | 43 +- .../remix-app/components/modals/matomo.tsx | 19 +- .../components/modals/modal-wrapper.tsx | 8 +- .../app/src/lib/remix-app/context/context.tsx | 2 +- .../src/lib/remix-app/context/provider.tsx | 5 +- .../app/src/lib/remix-app/interface/index.ts | 9 +- .../app/src/lib/remix-app/reducer/modals.ts | 3 +- .../app/src/lib/remix-app/remix-app.tsx | 35 +- .../app/src/lib/remix-app/state/app.ts | 2 +- .../app/src/lib/remix-app/types/index.ts | 17 +- .../src/lib/providers/completionTimer.ts | 31 - .../lib/providers/inlineCompletionProvider.ts | 95 +- .../editor/src/lib/remix-plugin-types.ts | 1 - .../editor/src/lib/remix-ui-editor.tsx | 20 +- .../components/buttons/sourceControlBase.tsx | 2 +- .../buttons/sourcecontrolbuttons.tsx | 108 +- libs/remix-ui/git/src/components/disabled.tsx | 32 +- .../components/github/repositoryselect.tsx | 3 +- .../github/selectandclonerepositories.tsx | 2 +- libs/remix-ui/git/src/components/gitui.tsx | 18 +- .../components/navigation/branchedetails.tsx | 2 +- .../src/components/navigation/branches.tsx | 4 + .../components/navigation/commitdetails.tsx | 2 +- .../git/src/components/navigation/commits.tsx | 5 +- .../components/navigation/remotesdetails.tsx | 3 +- .../components/navigation/sourcecontrol.tsx | 3 +- .../git/src/components/panels/branches.tsx | 4 +- .../branches/branchdifferencedetails.tsx | 2 +- .../panels/branches/branchdifferences.tsx | 2 +- .../panels/branches/localbranchdetails.tsx | 3 +- .../panels/branches/remotebranchedetails.tsx | 4 +- .../components/panels/commands/pushpull.tsx | 3 +- .../git/src/components/panels/commits.tsx | 2 - .../panels/commits/commitdetailsitem.tsx | 2 +- .../src/components/panels/remoteselect.tsx | 3 +- .../src/components/panels/remotesimport.tsx | 10 +- .../sourcecontrol/sourcecontrolitem.tsx | 3 +- .../sourcecontrol/sourcontrolitembuttons.tsx | 2 +- .../git/src/components/panels/version.tsx | 10 + libs/remix-ui/git/src/index.ts | 2 +- libs/remix-ui/git/src/lib/gitactions.ts | 32 +- libs/remix-ui/git/src/lib/listeners.ts | 42 +- libs/remix-ui/git/src/lib/pluginActions.ts | 12 +- libs/remix-ui/git/src/state/actions.ts | 6 +- libs/remix-ui/git/src/state/context.tsx | 10 +- libs/remix-ui/git/src/state/gitpayload.ts | 17 +- libs/remix-ui/git/src/state/gitreducer.tsx | 17 +- libs/remix-ui/git/src/types/index.ts | 238 +- .../grid-view/src/lib/remix-ui-grid-cell.tsx | 14 +- .../src/lib/remix-ui-grid-section.css | 4 +- .../src/lib/remix-ui-grid-section.tsx | 3 - .../src/lib/components/homeTabFeatured.tsx | 74 +- .../lib/components/homeTabFeaturedPlugins.tsx | 27 +- .../src/lib/components/homeTabFile.tsx | 2 +- .../lib/components/homeTabFileElectron.tsx | 6 +- .../src/lib/components/homeTabGetStarted.tsx | 26 +- .../src/lib/components/homeTabScamAlert.tsx | 11 +- .../src/lib/components/homeTablangOptions.tsx | 2 + .../home-tab/src/lib/remix-ui-home-tab.css | 6 - .../home-tab/src/lib/remix-ui-home-tab.tsx | 2 +- .../src/lib/remix-ui-modal-dialog.tsx | 23 +- .../modal-dialog/src/lib/types/index.ts | 7 +- .../panel/src/lib/plugins/panel-header.tsx | 3 + .../src/lib/permission-dialog.tsx | 4 +- .../InactivePluginCardContainer.tsx | 7 +- libs/remix-ui/plugin-manager/src/types.d.ts | 22 +- libs/remix-ui/remix-ai/src/index.ts | 1 + .../remix-ai/src/lib/components/Default.tsx | 84 + .../src/lib/components/ModelSelection.tsx | 78 + .../remix-ai/src/lib/components/RemixAI.tsx | 15 + libs/remix-ui/remix-ai/src/lib/remix-ai.css | 167 + libs/remix-ui/renderer/src/lib/renderer.tsx | 39 +- .../run-tab/src/lib/actions/account.ts | 5 + .../run-tab/src/lib/actions/actions.ts | 22 +- .../run-tab/src/lib/actions/deploy.ts | 8 +- .../run-tab/src/lib/actions/events.ts | 28 +- .../remix-ui/run-tab/src/lib/actions/index.ts | 11 +- .../run-tab/src/lib/actions/payload.ts | 38 +- .../run-tab/src/lib/components/account.tsx | 55 +- .../src/lib/components/contractDropdownUI.tsx | 2 +- .../src/lib/components/environment.tsx | 2 +- .../lib/components/instanceContainerUI.tsx | 68 +- .../src/lib/components/recorderCardUI.tsx | 3 +- .../run-tab/src/lib/components/settingsUI.tsx | 1 + .../src/lib/components/universalDappUI.tsx | 41 +- .../run-tab/src/lib/constants/index.ts | 4 +- libs/remix-ui/run-tab/src/lib/css/run-tab.css | 5 - .../run-tab/src/lib/reducers/runTab.ts | 99 +- libs/remix-ui/run-tab/src/lib/run-tab.tsx | 9 +- .../run-tab/src/lib/types/blockchain.d.ts | 8 +- .../src/lib/types/execution-context.d.ts | 1 - libs/remix-ui/run-tab/src/lib/types/index.ts | 43 +- .../run-tab/src/lib/types/run-tab.d.ts | 21 +- .../settings/src/lib/remix-ui-settings.tsx | 31 +- .../settings/src/lib/settingsAction.ts | 1 + .../src/lib/components/solidityCompile.tsx | 2 +- .../src/lib/api/compiler-api.ts | 17 +- .../src/lib/compiler-container.tsx | 6 +- .../src/lib/logic/compileTabLogic.ts | 8 +- .../src/lib/solidity-compiler.tsx | 18 +- .../src/lib/components/UmlDownload.tsx | 98 +- .../src/lib/solidity-uml-gen.tsx | 37 +- .../src/lib/remix-ui-static-analyser.tsx | 8 +- libs/remix-ui/tabs/src/lib/remix-ui-tabs.css | 4 +- libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx | 6 +- .../remix-ui-terminal-menu-buttons.tsx | 19 +- .../terminal/src/lib/remix-ui-terminal.tsx | 8 +- .../src/lib/actions/{index.ts => index.tsx} | 29 +- .../workspace/src/lib/actions/workspace.ts | 16 +- .../src/lib/components/createModal.tsx | 51 + .../src/lib/components/electron-menu.tsx | 2 +- .../components/electron-workspace-name.tsx | 2 +- .../components/file-explorer-context-menu.tsx | 5 + .../src/lib/components/file-explorer-menu.tsx | 30 + .../src/lib/components/file-explorer.tsx | 7 + .../components/workspace-hamburger-item.tsx | 2 +- .../lib/components/workspace-hamburger.tsx | 23 + .../workspace/src/lib/contexts/index.ts | 3 + .../src/lib/providers/FileSystemProvider.tsx | 6 + .../workspace/src/lib/remix-ui-workspace.tsx | 213 +- .../remix-ui/workspace/src/lib/types/index.ts | 7 +- .../workspace/src/lib/utils/constants.ts | 30 + .../remix-ui/workspace/src/lib/utils/index.ts | 7 + libs/remix-ui/xterm/src/lib/actions/index.ts | 2 - .../remix-ui-terminal-menu-xterm.tsx | 10 +- .../lib/components/remix-ui-xterminals.tsx | 22 +- libs/remix-url-resolver/package.json | 4 +- libs/remix-ws-templates/package.json | 4 +- .../src/templates/hashchecker/README.md | 8 +- .../remixDefault/contracts/3_Ballot.sol | 54 +- .../src/templates/rln/README.md | 6 +- .../src/templates/semaphore/README.md | 6 +- libs/remixd/package.json | 2 +- libs/remixd/src/bin/remixd.ts | 2 +- package-lock.json | 68610 ++++++++++++++++ package.json | 8 +- release-management.md | 12 +- release-process.md | 4 +- releaseDetails.json | 14 +- team-best-practices.md | 8 +- tsconfig.paths.json | 10 + yarn.lock | 309 +- 524 files changed, 112994 insertions(+), 5766 deletions(-) create mode 100644 .nvmrc rename apps/{etherscan => contract-verification}/.babelrc (51%) rename apps/{etherscan => contract-verification}/.browserslistrc (100%) create mode 100644 apps/contract-verification/.eslintrc rename apps/{etherscan => contract-verification}/.eslintrc.json (100%) create mode 100644 apps/contract-verification/project.json create mode 100644 apps/contract-verification/src/app/App.css create mode 100644 apps/contract-verification/src/app/AppContext.tsx create mode 100644 apps/contract-verification/src/app/ContractVerificationPluginClient.ts create mode 100644 apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts create mode 100644 apps/contract-verification/src/app/Verifiers/BlockscoutVerifier.ts create mode 100644 apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts create mode 100644 apps/contract-verification/src/app/Verifiers/SourcifyVerifier.ts create mode 100644 apps/contract-verification/src/app/Verifiers/index.ts create mode 100644 apps/contract-verification/src/app/VerifyFormContext.tsx create mode 100644 apps/contract-verification/src/app/app.tsx create mode 100644 apps/contract-verification/src/app/components/AccordionReceipt.tsx create mode 100644 apps/contract-verification/src/app/components/ConfigInput.tsx create mode 100644 apps/contract-verification/src/app/components/ConstructorArguments.tsx create mode 100644 apps/contract-verification/src/app/components/ContractAddressInput.tsx create mode 100644 apps/contract-verification/src/app/components/ContractDropdown.css create mode 100644 apps/contract-verification/src/app/components/ContractDropdown.tsx create mode 100644 apps/contract-verification/src/app/components/NavMenu.tsx create mode 100644 apps/contract-verification/src/app/components/SearchableChainDropdown.tsx create mode 100644 apps/contract-verification/src/app/components/index.tsx rename apps/{etherscan => contract-verification}/src/app/hooks/useLocalStorage.tsx (78%) create mode 100644 apps/contract-verification/src/app/hooks/useSourcifySupported.tsx create mode 100644 apps/contract-verification/src/app/layouts/Default.tsx create mode 100644 apps/contract-verification/src/app/layouts/index.ts create mode 100644 apps/contract-verification/src/app/routes.tsx create mode 100644 apps/contract-verification/src/app/types/SettingsTypes.ts create mode 100644 apps/contract-verification/src/app/types/ThemeType.ts create mode 100644 apps/contract-verification/src/app/types/VerificationTypes.ts create mode 100644 apps/contract-verification/src/app/types/index.ts create mode 100644 apps/contract-verification/src/app/utils/default-apis.json create mode 100644 apps/contract-verification/src/app/utils/default-settings.ts create mode 100644 apps/contract-verification/src/app/utils/index.ts create mode 100644 apps/contract-verification/src/app/views/LookupView.tsx create mode 100644 apps/contract-verification/src/app/views/ReceiptsView.tsx create mode 100644 apps/contract-verification/src/app/views/SettingsView.tsx create mode 100644 apps/contract-verification/src/app/views/VerifyView.tsx create mode 100644 apps/contract-verification/src/app/views/index.ts rename apps/{etherscan => contract-verification}/src/assets/.gitkeep (100%) rename apps/{etherscan => contract-verification}/src/environments/environment.prod.ts (56%) rename apps/{etherscan => contract-verification}/src/environments/environment.ts (89%) rename apps/{etherscan => contract-verification}/src/favicon.ico (100%) rename apps/{etherscan => contract-verification}/src/index.html (71%) create mode 100644 apps/contract-verification/src/main.tsx rename apps/{etherscan => contract-verification}/src/polyfills.ts (71%) create mode 100644 apps/contract-verification/src/profile.json rename apps/{etherscan => contract-verification}/src/styles.css (100%) rename apps/{etherscan => contract-verification}/tsconfig.app.json (100%) create mode 100644 apps/contract-verification/tsconfig.json rename apps/{etherscan => contract-verification}/webpack.config.js (79%) delete mode 100644 apps/etherscan/project.json delete mode 100644 apps/etherscan/src/app/App.css delete mode 100644 apps/etherscan/src/app/AppContext.tsx delete mode 100644 apps/etherscan/src/app/EtherscanPluginClient.ts delete mode 100644 apps/etherscan/src/app/app.tsx delete mode 100644 apps/etherscan/src/app/components/HeaderWithSettings.tsx delete mode 100644 apps/etherscan/src/app/components/SubmitButton.tsx delete mode 100644 apps/etherscan/src/app/components/index.ts delete mode 100644 apps/etherscan/src/app/layouts/Default.tsx delete mode 100644 apps/etherscan/src/app/layouts/index.ts delete mode 100644 apps/etherscan/src/app/routes.tsx delete mode 100644 apps/etherscan/src/app/types/Receipt.ts delete mode 100644 apps/etherscan/src/app/types/ThemeType.ts delete mode 100644 apps/etherscan/src/app/types/index.ts delete mode 100644 apps/etherscan/src/app/utils/index.ts delete mode 100644 apps/etherscan/src/app/utils/networks.ts delete mode 100644 apps/etherscan/src/app/utils/scripts.ts delete mode 100644 apps/etherscan/src/app/utils/utilities.ts delete mode 100644 apps/etherscan/src/app/utils/verify.ts delete mode 100644 apps/etherscan/src/app/views/CaptureKeyView.tsx delete mode 100644 apps/etherscan/src/app/views/ErrorView.tsx delete mode 100644 apps/etherscan/src/app/views/HomeView.tsx delete mode 100644 apps/etherscan/src/app/views/ReceiptsView.tsx delete mode 100644 apps/etherscan/src/app/views/VerifyView.tsx delete mode 100644 apps/etherscan/src/app/views/index.ts delete mode 100644 apps/etherscan/src/main.tsx delete mode 100644 apps/etherscan/src/profile.json rename apps/{etherscan => remix-dapp}/.eslintrc (100%) create mode 100644 apps/remix-dapp/README.md create mode 100644 apps/remix-dapp/package.json create mode 100644 apps/remix-dapp/project.json create mode 100644 apps/remix-dapp/src/App.css create mode 100644 apps/remix-dapp/src/App.tsx create mode 100644 apps/remix-dapp/src/actions/index.ts create mode 100644 apps/remix-dapp/src/assets/instance.json create mode 100644 apps/remix-dapp/src/assets/logo.png create mode 100644 apps/remix-dapp/src/components/ContractGUI/index.tsx create mode 100644 apps/remix-dapp/src/components/DappTop/index.tsx create mode 100644 apps/remix-dapp/src/components/DragBar/index.tsx create mode 100644 apps/remix-dapp/src/components/Home/mobile.tsx create mode 100644 apps/remix-dapp/src/components/Home/pc.tsx create mode 100644 apps/remix-dapp/src/components/SettingsUI/account.tsx create mode 100644 apps/remix-dapp/src/components/SettingsUI/gasPrice.tsx create mode 100644 apps/remix-dapp/src/components/SettingsUI/index.tsx create mode 100644 apps/remix-dapp/src/components/SettingsUI/locale.tsx create mode 100644 apps/remix-dapp/src/components/SettingsUI/network.tsx create mode 100644 apps/remix-dapp/src/components/SettingsUI/theme.tsx create mode 100644 apps/remix-dapp/src/components/SettingsUI/value.tsx create mode 100644 apps/remix-dapp/src/components/UiTerminal/ChechTxStatus.tsx create mode 100644 apps/remix-dapp/src/components/UiTerminal/Context.tsx create mode 100644 apps/remix-dapp/src/components/UiTerminal/RenderCall.tsx create mode 100644 apps/remix-dapp/src/components/UiTerminal/RenderKnownTransactions.tsx create mode 100644 apps/remix-dapp/src/components/UiTerminal/Table.tsx create mode 100644 apps/remix-dapp/src/components/UiTerminal/TxList.tsx create mode 100644 apps/remix-dapp/src/components/UiTerminal/index.css create mode 100644 apps/remix-dapp/src/components/UiTerminal/index.tsx create mode 100644 apps/remix-dapp/src/components/UiTerminal/types.ts create mode 100644 apps/remix-dapp/src/components/UiTerminal/utils.ts create mode 100644 apps/remix-dapp/src/components/UniversalDappUI/index.css create mode 100644 apps/remix-dapp/src/components/UniversalDappUI/index.tsx create mode 100644 apps/remix-dapp/src/components/UniversalDappUI/lowLevelInteractions.tsx create mode 100644 apps/remix-dapp/src/contexts/index.ts create mode 100644 apps/remix-dapp/src/index.css create mode 100644 apps/remix-dapp/src/index.html create mode 100644 apps/remix-dapp/src/locales/en/index.ts create mode 100644 apps/remix-dapp/src/locales/en/terminal.json create mode 100644 apps/remix-dapp/src/locales/en/udapp.json create mode 100644 apps/remix-dapp/src/locales/es/index.ts create mode 100644 apps/remix-dapp/src/locales/es/terminal.json create mode 100644 apps/remix-dapp/src/locales/es/udapp.json create mode 100644 apps/remix-dapp/src/locales/fr/index.ts create mode 100644 apps/remix-dapp/src/locales/fr/terminal.json create mode 100644 apps/remix-dapp/src/locales/fr/udapp.json create mode 100644 apps/remix-dapp/src/locales/it/index.ts create mode 100644 apps/remix-dapp/src/locales/it/terminal.json create mode 100644 apps/remix-dapp/src/locales/it/udapp.json create mode 100644 apps/remix-dapp/src/locales/zh/index.ts create mode 100644 apps/remix-dapp/src/locales/zh/terminal.json create mode 100644 apps/remix-dapp/src/locales/zh/udapp.json create mode 100644 apps/remix-dapp/src/main.tsx create mode 100644 apps/remix-dapp/src/reducers/state.ts create mode 100644 apps/remix-dapp/src/types.ts create mode 100644 apps/remix-dapp/src/utils/buildData.ts create mode 100644 apps/remix-dapp/src/utils/chains.ts create mode 100644 apps/remix-dapp/src/utils/constants.ts create mode 100644 apps/remix-dapp/src/utils/metamask.ts create mode 100644 apps/remix-dapp/src/utils/tools.ts create mode 100644 apps/remix-dapp/src/utils/txRunner.ts create mode 100644 apps/remix-dapp/src/utils/walletConnect.ts create mode 100644 apps/remix-dapp/src/vite-env.d.ts create mode 100644 apps/remix-dapp/tsconfig.app.json rename apps/{etherscan => remix-dapp}/tsconfig.json (100%) create mode 100644 apps/remix-dapp/webpack.config.js create mode 100644 apps/remix-dapp/yarn.lock create mode 100644 apps/remix-ide-e2e/src/tests/matomo.test.ts create mode 100644 apps/remix-ide/ci/update_desktop_release_assets.ts create mode 100644 apps/remix-ide/src/app/plugins/electron/appUpdaterPlugin.ts create mode 100644 apps/remix-ide/src/app/plugins/electron/foundryPlugin.ts create mode 100644 apps/remix-ide/src/app/plugins/electron/hardhatPlugin.ts create mode 100644 apps/remix-ide/src/app/plugins/electron/remixAIDesktopPlugin.tsx create mode 100644 apps/remix-ide/src/app/plugins/electron/slitherPlugin.ts create mode 100644 apps/remix-ide/src/app/plugins/remixAIPlugin.tsx create mode 100644 apps/remix-ide/src/app/plugins/remixGuide.css delete mode 100644 apps/remix-ide/src/app/plugins/solcoderAI.tsx delete mode 100644 apps/remix-ide/src/app/providers/injected-arbitrum-one-provider.tsx delete mode 100644 apps/remix-ide/src/app/providers/injected-ephemery-testnet-provider.tsx delete mode 100644 apps/remix-ide/src/app/providers/injected-optimism-provider.tsx delete mode 100644 apps/remix-ide/src/app/providers/injected-provider-trustwallet.tsx delete mode 100644 apps/remix-ide/src/app/providers/injected-skale-chaos-testnet-provider.tsx create mode 100644 apps/remix-ide/src/app/tabs/locales/en/gitui.json create mode 100644 apps/remix-ide/src/app/tabs/locales/en/vyper.json rename apps/remix-ide/src/app/udapp/{run-tab.js => run-tab.tsx} (77%) create mode 100644 apps/remix-ide/src/assets/img/gnosis_chain.png create mode 100644 apps/remix-ide/src/assets/img/remi-prof.webp create mode 100644 apps/remixdesktop/TEST.md create mode 100644 apps/remixdesktop/after-pack.js create mode 100644 apps/remixdesktop/afterbuild.js create mode 100644 apps/remixdesktop/aftersign.js create mode 100644 apps/remixdesktop/alpha.json create mode 100644 apps/remixdesktop/beta.json create mode 100755 apps/remixdesktop/circom-download/latest/circom-linux-amd64 create mode 100644 apps/remixdesktop/entitlements.mac.plist create mode 100644 apps/remixdesktop/esbuild.js create mode 100644 apps/remixdesktop/insiders.json create mode 100644 apps/remixdesktop/latest.json create mode 100644 apps/remixdesktop/nightwatch.conf.js create mode 100644 apps/remixdesktop/notarizedmg.sh create mode 100644 apps/remixdesktop/package-lock.json create mode 100755 apps/remixdesktop/run_ci_test.sh create mode 100755 apps/remixdesktop/run_git_ui_isogit_tests.sh create mode 100755 apps/remixdesktop/rundist.bash create mode 100755 apps/remixdesktop/rundist_esbuild.bash create mode 100755 apps/remixdesktop/rundist_tsc.bash create mode 100755 apps/remixdesktop/rundist_webpack.bash create mode 100755 apps/remixdesktop/splice_tests.js create mode 100644 apps/remixdesktop/src/lib/InferenceServerManager.ts create mode 100644 apps/remixdesktop/src/lib/remixd.ts create mode 100644 apps/remixdesktop/src/lib/utils.ts create mode 100644 apps/remixdesktop/src/plugins/appUpdater.ts create mode 100644 apps/remixdesktop/src/plugins/foundryPlugin.ts create mode 100644 apps/remixdesktop/src/plugins/hardhatPlugin.ts create mode 100644 apps/remixdesktop/src/plugins/remixAIDektop.ts create mode 100644 apps/remixdesktop/src/plugins/slitherPlugin.ts create mode 100644 apps/remixdesktop/src/utils/matamo.ts create mode 100644 apps/remixdesktop/test/cache_dir/remixdesktop.json create mode 100644 apps/remixdesktop/test/lib/git.ts create mode 100644 apps/remixdesktop/test/nighwatch.app.ts create mode 100644 apps/remixdesktop/test/tests/app/compiler.test.ts create mode 100644 apps/remixdesktop/test/tests/app/externaleditor.test.ts create mode 100644 apps/remixdesktop/test/tests/app/foundry.test.ts create mode 100644 apps/remixdesktop/test/tests/app/gist.test.ts create mode 100644 apps/remixdesktop/test/tests/app/git-ui.test.ts create mode 100644 apps/remixdesktop/test/tests/app/git-ui_2.test.ts create mode 100644 apps/remixdesktop/test/tests/app/git-ui_3.test.ts create mode 100644 apps/remixdesktop/test/tests/app/git-ui_4.test.ts create mode 100644 apps/remixdesktop/test/tests/app/git.test.ts create mode 100644 apps/remixdesktop/test/tests/app/github.test.ts create mode 100644 apps/remixdesktop/test/tests/app/github_2.test.ts create mode 100644 apps/remixdesktop/test/tests/app/github_3.test.ts create mode 100644 apps/remixdesktop/test/tests/app/hardhat.test.ts create mode 100644 apps/remixdesktop/test/tests/app/offline.test.ts create mode 100644 apps/remixdesktop/test/tests/app/search.test.ts create mode 100644 apps/remixdesktop/test/tests/app/slitherlinux.test.ts create mode 100644 apps/remixdesktop/test/tests/app/templates.test.ts create mode 100644 apps/remixdesktop/test/tests/app/xterm.test.ts create mode 100644 apps/remixdesktop/test/tests/app/xtermwin.test.ts create mode 100644 apps/remixdesktop/test/types/index.d.ts create mode 100644 apps/remixdesktop/tsconfig.e2e.json create mode 100644 apps/remixdesktop/webpack.config.js create mode 100644 libs/remix-ai-core/.eslintrc create mode 100644 libs/remix-ai-core/README.md create mode 100644 libs/remix-ai-core/package-lock.json create mode 100644 libs/remix-ai-core/project.json create mode 100644 libs/remix-ai-core/src/agents/codeExplainAgent.ts create mode 100644 libs/remix-ai-core/src/agents/completionAgent.ts create mode 100644 libs/remix-ai-core/src/agents/securityAgent.ts create mode 100644 libs/remix-ai-core/src/helpers/dowload_model.ts create mode 100644 libs/remix-ai-core/src/helpers/inferenceServerReleases.ts create mode 100644 libs/remix-ai-core/src/index.ts create mode 100644 libs/remix-ai-core/src/inferencers/remote/remoteInference.ts create mode 100644 libs/remix-ai-core/src/prompts/chat.ts create mode 100644 libs/remix-ai-core/src/prompts/completionPrompts.ts create mode 100644 libs/remix-ai-core/src/prompts/promptBuilder.ts create mode 100644 libs/remix-ai-core/src/types/constants.ts create mode 100644 libs/remix-ai-core/src/types/models.ts create mode 100644 libs/remix-ai-core/src/types/types.ts create mode 100644 libs/remix-ai-core/tsconfig.json create mode 100644 libs/remix-ai-core/tsconfig.lib.json create mode 100644 libs/remix-api/src/lib/plugins/fs-api.ts create mode 100644 libs/remix-api/src/lib/plugins/git-api.ts create mode 100644 libs/remix-api/src/lib/plugins/remixAIDesktop-api.ts create mode 100644 libs/remix-api/src/lib/plugins/remixai-api.ts create mode 100644 libs/remix-api/src/lib/plugins/terminal-api.ts create mode 100644 libs/remix-api/src/lib/types/git.ts create mode 100644 libs/remix-git/index.ts create mode 100644 libs/remix-git/src/isogit.ts delete mode 100644 libs/remix-ui/editor/src/lib/providers/completionTimer.ts create mode 100644 libs/remix-ui/git/src/components/panels/version.tsx create mode 100644 libs/remix-ui/remix-ai/src/index.ts create mode 100644 libs/remix-ui/remix-ai/src/lib/components/Default.tsx create mode 100644 libs/remix-ui/remix-ai/src/lib/components/ModelSelection.tsx create mode 100644 libs/remix-ui/remix-ai/src/lib/components/RemixAI.tsx create mode 100644 libs/remix-ui/remix-ai/src/lib/remix-ai.css rename libs/remix-ui/workspace/src/lib/actions/{index.ts => index.tsx} (96%) create mode 100644 libs/remix-ui/workspace/src/lib/components/createModal.tsx create mode 100644 package-lock.json diff --git a/.circleci/config.yml b/.circleci/config.yml index afe5c5576b..26bb163e27 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -105,6 +105,51 @@ jobs: paths: - "persist" + test-remixdesktop-linux: + machine: + image: ubuntu-2004:current + resource_class: + xlarge + working_directory: ~/remix-project + parallelism: 10 + steps: + - run: ldd --version + - checkout + - attach_workspace: + at: . + - run: unzip ./persist/desktopbuild.zip + - run: + command: | + nvm install 20.2 + nvm use 20.2 + node -v + npm install --global yarn node-gyp + python -m pip install --upgrade pip + pip install setuptools + mkdir apps/remixdesktop/build + cp -r dist/apps/remix-ide apps/remixdesktop/build + cd apps/remixdesktop/ + yarn add node-pty + yarn --ignore-optional + yarn add @remix-project/remix-ws-templates + ./rundist.bash + - run: + name: "Run tests" + command: | + nvm use 20.2 + cd apps/remixdesktop/ + ./run_ci_test.sh + - run: + name: "Run isogit tests" + command: | + nvm use 20.2 + cd apps/remixdesktop/ + ./run_git_ui_isogit_tests.sh + - store_test_results: + path: ./apps/remixdesktop/reports/tests + - store_artifacts: + path: ./apps/remixdesktop/reports/screenshots + build-remixdesktop-linux: machine: image: ubuntu-2004:current @@ -119,22 +164,37 @@ jobs: - run: unzip ./persist/desktopbuild.zip - run: command: | + nvm install 20.2 + nvm use 20.2 node -v + npm install --global yarn node-gyp + python -m pip install --upgrade pip + pip install setuptools mkdir apps/remixdesktop/build cp -r dist/apps/remix-ide apps/remixdesktop/build cd apps/remixdesktop/ yarn add node-pty yarn --ignore-optional yarn add @remix-project/remix-ws-templates - PUBLISH_FOR_PULL_REQUEST='true' yarn dist + ./rundist.bash rm -rf release/*-unpacked - save_cache: key: remixdesktop-linux-deps-{{ checksum "apps/remixdesktop/yarn.lock" }} paths: - apps/remixdesktop/node_modules + - run: + name: "remove unnecessary files" + command: | + rm -rf ~/remix-project/apps/remixdesktop/release/.icon* + rm -rf ~/remix-project/apps/remixdesktop/release/builder* - store_artifacts: path: apps/remixdesktop/release/ destination: remixdesktop-linux + - persist_to_workspace: + root: apps/remixdesktop + paths: + - "release" + build-remixdesktop-windows: executor: @@ -147,39 +207,107 @@ jobs: - attach_workspace: at: . - run: unzip ./persist/desktopbuild.zip - - restore_cache: - key: node-20-windows-v3 + - run: command: | - nvm install 20.0.0 - nvm use 20.0.0 + nvm install 20.2 + nvm use 20.2 node -v npx -v npm install --global yarn + npm install --global node-gyp yarn -v - - save_cache: - key: node-20-windows-v3 - paths: - - /ProgramData/nvm/v20.0.0 - - restore_cache: - keys: - - remixdesktop-windows-deps-{{ checksum "apps/remixdesktop/yarn.lock" }} + - run: command: | mkdir apps/remixdesktop/build cp -r dist/apps/remix-ide apps/remixdesktop/build cd apps/remixdesktop/ + python -m pip install --upgrade pip + pip install setuptools yarn - PUBLISH_FOR_PULL_REQUEST='true' yarn dist + ./rundist.bash rm -rf release/*-unpacked - - save_cache: - key: remixdesktop-windows-deps-{{ checksum "apps/remixdesktop/yarn.lock" }} - paths: - - apps/remixdesktop/node_modules + - persist_to_workspace: root: apps/remixdesktop paths: - "release" + + test-remixdesktop-windows: + executor: + name: win/default # executor type + size: xlarge # can be medium, large, xlarge, 2xlarge + shell: bash.exe + parallelism: 10 + working_directory: ~/remix-project + steps: + - run: + name: Restart local mstsc + command: psexec64.exe -accepteula -nobanner -i 0 mstsc /v:localhost /w:2560 /h:1140 + background: true + shell: powershell.exe + - run: + name: Naive impl to wait until the screen stretches + command: Start-Sleep 5 + shell: powershell.exe + - run: + name: Get screen info + command: | + Add-Type -AssemblyName System.Windows.Forms + [System.Windows.Forms.Screen]::AllScreens | fl * + shell: powershell.exe + - checkout + - attach_workspace: + at: . + - run: unzip ./persist/desktopbuild.zip + + - run: + command: | + nvm install 20.2 + nvm use 20.2 + node -v + npx -v + npm install --global yarn + npm install --global node-gyp + yarn -v + - run: + name: start selenium + command: | + cd "apps/remixdesktop/" + yarn -v + shell: powershell.exe + + - run: + command: | + mkdir apps/remixdesktop/build + cp -r dist/apps/remix-ide apps/remixdesktop/build + cd apps/remixdesktop/ + nvm use 20.2 + node -v + python -m pip install --upgrade pip + pip install setuptools + yarn + ./rundist.bash + - run: + name: run tests + command: | + cd "apps/remixdesktop/" + yarn -v + sleep 15 + ./run_ci_test.sh + - run: + name: "Run isogit tests" + command: | + cd apps/remixdesktop/ + yarn -v + sleep 15 + ./run_git_ui_isogit_tests.sh + - store_test_results: + path: ./apps/remixdesktop/reports/tests + - store_artifacts: + path: ./apps/remixdesktop/reports/screenshots + # see https://docs.digicert.com/en/software-trust-manager/ci-cd-integrations/script-integrations/github-integration-ksp.html sign-remixdesktop-windows: executor: win/default # executor type @@ -230,28 +358,66 @@ jobs: command: | Get-ChildItem -Path 'C:\Program Files (x86)\Windows Kits\10\App Certification Kit' -Filter signtool.exe -Recurse - run: - name: "Signtool-Signing" + name: read env shell: powershell.exe command: | - & $env:Signtool sign /sha1 $env:SM_CODE_SIGNING_CERT_SHA1_HASH /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 $env:RemixSetupExe + # Specify the path to your package.json file + $packageJsonPath = "C:\Users\circleci\remix-project\apps\remixdesktop\package.json" + + # Check if the file exists + if (Test-Path $packageJsonPath) { + # Read the content of the package.json file + $packageJsonContent = Get-Content $packageJsonPath -Raw | ConvertFrom-Json + + # Check if the 'version' field exists in the package.json + if ($packageJsonContent.'version' -ne $null) { + # Store the version value in an environment variable + $version = $packageJsonContent.version + $file = "C:\Users\circleci\remix-project\release\Remix-Desktop-Setup-$($version).exe" + Write-Host "Version $(file) stored in PACKAGE_VERSION environment variable." + "Set-Variable -Name 'PACKAGE_VERSION' -Value '$file' -Scope Global" > SetEnvVars.ps1 + dir Env: + } else { + Write-Host "Error: 'version' field not found in package.json." + } + } else { + Write-Host "Error: package.json file not found at $packageJsonPath." + } + - run: + name: "Signtool-Signing" + shell: powershell.exe + command: | + . .\SetEnvVars.ps1 + dir Env: + & $env:Signtool sign /sha1 $env:SM_CODE_SIGNING_CERT_SHA1_HASH /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 $PACKAGE_VERSION - run: name: "Signtool-Verification" shell: powershell.exe command: | - $verify_output = $(& $env:Signtool verify /v /pa $env:RemixSetupExe) + . .\SetEnvVars.ps1 + $verify_output = $(& $env:Signtool verify /v /pa $PACKAGE_VERSION) echo ${verify_output} if (!$verify_output.Contains("Number of files successfully Verified: 1")) { echo 'Verification failed' exit 1 } + - run: + name: "remove unnecessary files" + shell: bash.exe + command: | + rm -rf ~/remix-project/release/.icon* + rm -rf ~/remix-project/release/builder* - store_artifacts: path: ~/remix-project/release/ destination: remixdesktop-windows + - persist_to_workspace: + root: ~/remix-project/ + paths: + - "release" environment: SM_CLIENT_CERT_FILE: 'C:\Certificate_pkcs12.p12' Signtool: 'C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe' SSM: 'C:\Program Files\DigiCert\DigiCert One Signing Manager Tools' - RemixSetupExe: 'C:\Users\circleci\remix-project\release\Remix IDE.exe' build-remixdesktop-mac: macos: @@ -259,23 +425,41 @@ jobs: resource_class: macos.m1.large.gen1 working_directory: ~/remix-project + parameters: + arch: + type: string steps: - checkout - attach_workspace: at: . + - run: + name: Install Apple Certificate + command: | + echo $APPLE_CERTIFICATE_BASE64 | base64 --decode > /tmp/certificate.p12 + security create-keychain -p ci-password build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p ci-password build.keychain + curl -o DeveloperIDG2CA.cer "https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer" + sudo security import DeveloperIDG2CA.cer -k /Library/Keychains/System.keychain -T /usr/bin/codesign + sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain DeveloperIDG2CA.cer + security import /tmp/certificate.p12 -k build.keychain -P $APPLE_CERTIFICATE_PASSWORD -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple: -s -k ci-password build.keychain + security find-identity -v -p codesigning - run: unzip ./persist/desktopbuild.zip - run: command: | ls -la dist/apps/remix-ide - nvm install 20.0.0 - nvm use 20.0.0 + nvm install 20.2 + nvm use 20.2 - restore_cache: keys: - remixdesktop-deps-mac-{{ checksum "apps/remixdesktop/yarn.lock" }} - run: command: | - nvm use 20.0.0 - cd apps/remixdesktop && yarn + nvm use 20.2 + cd apps/remixdesktop + yarn || yarn + find ./node_modules yarn add @remix-project/remix-ws-templates - save_cache: key: remixdesktop-deps-mac-{{ checksum "apps/remixdesktop/yarn.lock" }} @@ -284,20 +468,120 @@ jobs: # use USE_HARD_LINK=false https://github.com/electron-userland/electron-builder/issues/3179 - run: command: | - nvm use 20.0.0 + nvm use 20.2 mkdir apps/remixdesktop/build cp -r dist/apps/remix-ide apps/remixdesktop/build cd apps/remixdesktop yarn - yarn installRipGrepMacOXarm64 - PUBLISH_FOR_PULL_REQUEST='true' USE_HARD_LINKS=false yarn dist --mac --arm64 - yarn installRipGrepMacOXx64 - PUBLISH_FOR_PULL_REQUEST='true' USE_HARD_LINKS=false yarn dist --mac --x64 - rm -rf release/mac* + - run: + command: | + nvm use 20.2 + cd apps/remixdesktop + yarn installRipGrepMacOX<< parameters.arch >> + PUBLISH_FOR_PULL_REQUEST='false' USE_HARD_LINKS=false ./rundist.bash --<< parameters.arch >> + if [ -f release/latest-mac.yml ]; then + cat release/latest-mac.yml + mv release/latest-mac.yml release/latest-mac-<< parameters.arch >>.yml + fi + find build + - run: + name: Notarize the app + command: | + brew install jq + cd apps/remixdesktop + zsh notarizedmg.sh + - run: + name: "remove unnecessary files" + command: | + rm -rf ~/remix-project/apps/remixdesktop/release/.icon* + rm -rf ~/remix-project/apps/remixdesktop/release/builder* + rm -rf ~/remix-project/apps/remixdesktop/release/*.blockmap + rm -rf ~/remix-project/apps/remixdesktop/release/_.* - store_artifacts: path: apps/remixdesktop/release/ destination: remixdesktop-mac - + - persist_to_workspace: + root: apps/remixdesktop + paths: + - "release" + test-remixdesktop-mac: + macos: + xcode: 14.2.0 + resource_class: + macos.m1.large.gen1 + working_directory: ~/remix-project + parallelism: 10 + steps: + - checkout + - attach_workspace: + at: . + - run: unzip ./persist/desktopbuild.zip + - run: + command: | + ls -la dist/apps/remix-ide + nvm install 20.2 + nvm use 20.2 + - restore_cache: + keys: + - remixdesktop-deps-mac-{{ checksum "apps/remixdesktop/yarn.lock" }} + - run: + command: | + nvm use 20.2 + cd apps/remixdesktop + yarn || yarn + yarn add @remix-project/remix-ws-templates + - save_cache: + key: remixdesktop-deps-mac-{{ checksum "apps/remixdesktop/yarn.lock" }} + paths: + - apps/remixdesktop/node_modules + - run: + command: | + nvm use 20.2 + mkdir apps/remixdesktop/build + cp -r dist/apps/remix-ide apps/remixdesktop/build + cd apps/remixdesktop + yarn + - run: + command: | + nvm use 20.2 + cd apps/remixdesktop + yarn installRipGrepMacOXarm64 + PUBLISH_FOR_PULL_REQUEST='false' DO_NOT_NOTARIZE='true' USE_HARD_LINKS=false ./rundist.bash --arm64 + find build + - run: + name: "Run tests" + command: | + nvm use 20.2 + cd apps/remixdesktop + ./run_ci_test.sh + - run: + name: "Run isogit tests" + command: | + nvm use 20.2 + cd apps/remixdesktop + ./run_git_ui_isogit_tests.sh + - store_test_results: + path: ./apps/remixdesktop/reports/tests + - store_artifacts: + path: ./apps/remixdesktop/reports/screenshots + + uploadartifacts: + docker: + - image: cimg/node:20.0.0-browsers + resource_class: + xlarge + working_directory: ~/remix-project + steps: + - checkout + - attach_workspace: + at: . + - restore_cache: + keys: + - v1-deps-{{ checksum "yarn.lock" }} + - run: yarn + - run: + name: "Upload Artifacts" + command: npx ts-node apps/remix-ide/ci/update_desktop_release_assets.ts lint: docker: - image: cimg/node:20.0.0-browsers @@ -504,10 +788,16 @@ workflows: - build-desktop: filters: branches: - only: [/.*desktop.*/] + only: [/.*desktop.*/, 'remix_beta'] - build-remixdesktop-mac: requires: - build-desktop + matrix: + parameters: + arch: ["arm64", "x64"] + - test-remixdesktop-mac: + requires: + - build-desktop - build-remixdesktop-windows: requires: - build-desktop @@ -517,6 +807,23 @@ workflows: - build-remixdesktop-linux: requires: - build-desktop + - test-remixdesktop-linux: + requires: + - build-desktop + - test-remixdesktop-windows: + requires: + - build-desktop + - uploadartifacts: + requires: + - build-remixdesktop-mac + - build-remixdesktop-linux + - sign-remixdesktop-windows + - test-remixdesktop-windows + - test-remixdesktop-linux + - test-remixdesktop-mac + filters: + branches: + only: [/.*desktop.*/] - build-plugin: matrix: parameters: diff --git a/.github/workflows/pr-reminder.yml b/.github/workflows/pr-reminder.yml index eaedd51125..172ffc6011 100644 --- a/.github/workflows/pr-reminder.yml +++ b/.github/workflows/pr-reminder.yml @@ -14,4 +14,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} - freeze-date: '2024-09-09T18:00:00Z' + freeze-date: '2024-10-21T18:00:00Z' diff --git a/.gitignore b/.gitignore index ef8b36916c..9e40832324 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ soljson.js *_group*.*.ts *_group*.ts stats.json +release # compiled output @@ -61,9 +62,10 @@ testem.log apps/remixdesktop/.webpack apps/remixdesktop/out apps/remixdesktop/release/ +apps/remixdesktop/build*/ apps/remix-ide/src/assets/list.json apps/remix-ide/src/assets/esbuild.wasm apps/remixdesktop/build* -apps/remixdesktop/reports/ +apps/remixdesktop/reports apps/remixdesktop/logs/ logs diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..9a2a0e219c --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20 diff --git a/.prettierrc.json b/.prettierrc.json index 07681f5b0a..967e2ecc37 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,9 +1,7 @@ { "tabWidth": 2, "printWidth": 500, - "bracketSpacing": false, "useTabs": false, "semi": false, - "singleQuote": true, - "bracketSpacing": false + "singleQuote": true } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a603101311..e23efaf847 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ Then you can replace the string with a intl component. The `id` prop will be the + ``` -In some cases, jsx maybe not acceptable, you can use `intl.formatMessage` . +In some cases, jsx maybe not be acceptable, you can use `intl.formatMessage` . ```jsx ``` -You can't be sure there is a match key in locale file or not. So it will be better to provide a `defaultMessage` prop. +You can't be sure whether there is a match key in locale file or not. So it will be better to provide a `defaultMessage` prop. ### Should I update the non-english locale json files? You probably will have this question when you are updating the english locale json files. diff --git a/README.md b/README.md index 9059e6318c..113c4b897c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [![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)](https://github.com/ethereum/remix-project/blob/master/LICENSE) [![Discord](https://img.shields.io/badge/join-discord-brightgreen.svg?style=flat&logo=discord)](https://discord.gg/mh9hFCKkEq) [![X Follow](https://img.shields.io/twitter/follow/ethereumremix?style=flat&logo=x&color=green)](https://x.com/ethereumremix) @@ -68,7 +68,7 @@ git clone https://github.com/ethereum/remix-project.git 2. Install dependencies: `yarn install` or simply run `yarn` 3. Build Remix libraries: `yarn run build:libs` 4. Build Remix project: `yarn build` -5. Build and run project server: `yarn serve`. Optionally, run `yarn serve:hot` to enable hot module reload for frontend updates. +5. Build and run project server: `yarn serve`. Optionally, run `yarn serve:hot` to enable hot module to reload for frontend updates. Open `http://127.0.0.1:8080` in your browser to load Remix IDE locally. diff --git a/apps/circuit-compiler/src/app/components/container.tsx b/apps/circuit-compiler/src/app/components/container.tsx index 3a18465fa4..2c7cc41e16 100644 --- a/apps/circuit-compiler/src/app/components/container.tsx +++ b/apps/circuit-compiler/src/app/components/container.tsx @@ -74,7 +74,7 @@ export function Container () { explain why the error occurred and how to fix it. ` // @ts-ignore - await circuitApp.plugin.call('solcoder', 'error_explaining', message) + await circuitApp.plugin.call('remixAI', 'error_explaining', message) } else { const message = ` error message: ${error} @@ -82,7 +82,7 @@ export function Container () { explain why the error occurred and how to fix it. ` // @ts-ignore - await circuitApp.plugin.call('solcoder', 'error_explaining', message) + await circuitApp.plugin.call('remixAI', 'error_explaining', message) } } else { const error = report.message @@ -92,7 +92,7 @@ export function Container () { explain why the error occurred and how to fix it. ` // @ts-ignore - await circuitApp.plugin.call('solcoder', 'error_explaining', message) + await circuitApp.plugin.call('remixAI', 'error_explaining', message) } } diff --git a/apps/etherscan/.babelrc b/apps/contract-verification/.babelrc similarity index 51% rename from apps/etherscan/.babelrc rename to apps/contract-verification/.babelrc index e37036ce66..6df3e5be52 100644 --- a/apps/etherscan/.babelrc +++ b/apps/contract-verification/.babelrc @@ -1,9 +1,5 @@ { - "presets": ["@babel/preset-env", ["@babel/preset-react", - {"runtime": "automatic"} - ]], + "presets": ["@babel/preset-env", ["@babel/preset-react", { "runtime": "automatic" }]], "plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime", "@babel/plugin-proposal-nullish-coalescing-operator"], - "ignore": [ - "**/node_modules/**" - ] -} \ No newline at end of file + "ignore": ["**/node_modules/**"] +} diff --git a/apps/etherscan/.browserslistrc b/apps/contract-verification/.browserslistrc similarity index 100% rename from apps/etherscan/.browserslistrc rename to apps/contract-verification/.browserslistrc diff --git a/apps/contract-verification/.eslintrc b/apps/contract-verification/.eslintrc new file mode 100644 index 0000000000..be97c53fbb --- /dev/null +++ b/apps/contract-verification/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "../../.eslintrc.json" +} diff --git a/apps/etherscan/.eslintrc.json b/apps/contract-verification/.eslintrc.json similarity index 100% rename from apps/etherscan/.eslintrc.json rename to apps/contract-verification/.eslintrc.json diff --git a/apps/contract-verification/project.json b/apps/contract-verification/project.json new file mode 100644 index 0000000000..dee28fe326 --- /dev/null +++ b/apps/contract-verification/project.json @@ -0,0 +1,69 @@ +{ + "name": "contract-verification", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/contract-verification/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@nrwl/webpack:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "development", + "options": { + "compiler": "babel", + "outputPath": "dist/apps/contract-verification", + "index": "apps/contract-verification/src/index.html", + "baseHref": "./", + "main": "apps/contract-verification/src/main.tsx", + "polyfills": "apps/contract-verification/src/polyfills.ts", + "tsConfig": "apps/contract-verification/tsconfig.app.json", + "assets": [ + "apps/contract-verification/src/favicon.ico", + "apps/contract-verification/src/assets", + "apps/contract-verification/src/profile.json" + ], + "styles": ["apps/contract-verification/src/styles.css"], + "scripts": [], + "webpackConfig": "apps/contract-verification/webpack.config.js" + }, + "configurations": { + "development": { + }, + "production": { + "fileReplacements": [ + { + "replace": "apps/contract-verification/src/environments/environment.ts", + "with": "apps/contract-verification/src/environments/environment.prod.ts" + } + ] + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/contract-verification/**/*.ts"], + "eslintConfig": "apps/contract-verification/.eslintrc" + } + }, + "serve": { + "executor": "@nrwl/webpack:dev-server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "contract-verification:build", + "hmr": true, + "baseHref": "/" + }, + "configurations": { + "development": { + "buildTarget": "contract-verification:build:development", + "port": 5003 + }, + "production": { + "buildTarget": "contract-verification:build:production" + } + } + } + }, + "tags": [] +} diff --git a/apps/contract-verification/src/app/App.css b/apps/contract-verification/src/app/App.css new file mode 100644 index 0000000000..e036149047 --- /dev/null +++ b/apps/contract-verification/src/app/App.css @@ -0,0 +1,14 @@ +html, body, #root { + height: 100%; +} + +body { + margin: 0; +} + +a:focus { + background-color: var(bg-light) !important; +} + +.fa-arrow-up-right-from-square::before { content: "\f08e"; } +.fa-xmark::before { content: "\f00d"; } diff --git a/apps/contract-verification/src/app/AppContext.tsx b/apps/contract-verification/src/app/AppContext.tsx new file mode 100644 index 0000000000..e4ffda0cee --- /dev/null +++ b/apps/contract-verification/src/app/AppContext.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import type { ThemeType, Chain, SubmittedContracts, ContractVerificationSettings } from './types' +import { CompilerAbstract } from '@remix-project/remix-solidity' +import { ContractVerificationPluginClient } from './ContractVerificationPluginClient' +import { ContractDropdownSelection } from './components/ContractDropdown' + +// Define the type for the context +type AppContextType = { + themeType: ThemeType + setThemeType: (themeType: ThemeType) => void + clientInstance: ContractVerificationPluginClient + settings: ContractVerificationSettings + setSettings: React.Dispatch> + chains: Chain[] + compilationOutput: { [key: string]: CompilerAbstract } | undefined + submittedContracts: SubmittedContracts + setSubmittedContracts: React.Dispatch> +} + +// Provide a default value with the appropriate types +const defaultContextValue: AppContextType = { + themeType: 'dark', + setThemeType: (themeType: ThemeType) => {}, + clientInstance: {} as ContractVerificationPluginClient, + settings: { chains: {} }, + setSettings: () => {}, + chains: [], + compilationOutput: undefined, + submittedContracts: {}, + setSubmittedContracts: (submittedContracts: SubmittedContracts) => {}, +} + +// Create the context with the type +export const AppContext = React.createContext(defaultContextValue) diff --git a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts new file mode 100644 index 0000000000..8554cb29f6 --- /dev/null +++ b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts @@ -0,0 +1,18 @@ +import { PluginClient } from '@remixproject/plugin' +import { createClient } from '@remixproject/plugin-webview' +import EventManager from 'events' + +export class ContractVerificationPluginClient extends PluginClient { + public internalEvents: EventManager + + constructor() { + super() + this.internalEvents = new EventManager() + createClient(this) + this.onload() + } + + onActivation(): void { + this.internalEvents.emit('verification_activated') + } +} diff --git a/apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts b/apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts new file mode 100644 index 0000000000..480fc5bd9b --- /dev/null +++ b/apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts @@ -0,0 +1,16 @@ +import { CompilerAbstract } from '@remix-project/remix-solidity' +import type { LookupResponse, SubmittedContract, VerificationResponse } from '../types' + +// Optional function definitions +export interface AbstractVerifier { + verifyProxy(submittedContract: SubmittedContract): Promise + checkVerificationStatus?(receiptId: string): Promise + checkProxyVerificationStatus?(receiptId: string): Promise +} + +export abstract class AbstractVerifier { + constructor(public apiUrl: string, public explorerUrl: string) {} + + abstract verify(submittedContract: SubmittedContract, compilerAbstract: CompilerAbstract): Promise + abstract lookup(contractAddress: string, chainId: string): Promise +} diff --git a/apps/contract-verification/src/app/Verifiers/BlockscoutVerifier.ts b/apps/contract-verification/src/app/Verifiers/BlockscoutVerifier.ts new file mode 100644 index 0000000000..fd7b95563f --- /dev/null +++ b/apps/contract-verification/src/app/Verifiers/BlockscoutVerifier.ts @@ -0,0 +1,50 @@ +import { SourceFile } from '../types' +import { EtherscanVerifier } from './EtherscanVerifier' + +// Etherscan and Blockscout return different objects from the getsourcecode method +interface BlockscoutSource { + AdditionalSources: Array<{ SourceCode: string; Filename: string }> + ConstructorArguments: string + OptimizationRuns: number + IsProxy: string + SourceCode: string + ABI: string + ContractName: string + CompilerVersion: string + OptimizationUsed: string + Runs: string + EVMVersion: string + FileName: string + Address: string +} + +export class BlockscoutVerifier extends EtherscanVerifier { + LOOKUP_STORE_DIR = 'blockscout-verified' + + constructor(apiUrl: string) { + // apiUrl and explorerUrl are the same for Blockscout + super(apiUrl, apiUrl, undefined) + } + + getContractCodeUrl(address: string): string { + const url = new URL(this.explorerUrl + `/address/${address}`) + url.searchParams.append('tab', 'contract') + return url.href + } + + processReceivedFiles(source: unknown, contractAddress: string, chainId: string): { sourceFiles: SourceFile[]; targetFilePath?: string } { + const blockscoutSource = source as BlockscoutSource + + const result: SourceFile[] = [] + const filePrefix = `/${this.LOOKUP_STORE_DIR}/${chainId}/${contractAddress}` + + const targetFilePath = `${filePrefix}/${blockscoutSource.FileName}` + result.push({ content: blockscoutSource.SourceCode, path: targetFilePath }) + + for (const additional of blockscoutSource.AdditionalSources ?? []) { + result.push({ content: additional.SourceCode, path: `${filePrefix}/${additional.Filename}` }) + } + + return { sourceFiles: result, targetFilePath } + } +} diff --git a/apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts b/apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts new file mode 100644 index 0000000000..8d5a6198a2 --- /dev/null +++ b/apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts @@ -0,0 +1,289 @@ +import { CompilerAbstract } from '@remix-project/remix-solidity' +import { AbstractVerifier } from './AbstractVerifier' +import type { LookupResponse, SourceFile, SubmittedContract, VerificationResponse, VerificationStatus } from '../types' + +interface EtherscanRpcResponse { + status: '0' | '1' + message: string + result: string +} + +interface EtherscanCheckStatusResponse { + status: '0' | '1' + message: string + result: 'Pending in queue' | 'Pass - Verified' | 'Fail - Unable to verify' | 'Already Verified' | 'Unknown UID' +} + +interface EtherscanSource { + SourceCode: string + ABI: string + ContractName: string + CompilerVersion: string + OptimizationUsed: string + Runs: string + ConstructorArguments: string + EVMVersion: string + Library: string + LicenseType: string + Proxy: string + Implementation: string + SwarmSource: string +} + +interface EtherscanGetSourceCodeResponse { + status: '0' | '1' + message: string + result: EtherscanSource[] +} + +export class EtherscanVerifier extends AbstractVerifier { + LOOKUP_STORE_DIR = 'etherscan-verified' + + constructor(apiUrl: string, explorerUrl: string, protected apiKey?: string) { + super(apiUrl, explorerUrl) + } + + async verify(submittedContract: SubmittedContract, compilerAbstract: CompilerAbstract): Promise { + // TODO: Handle version Vyper contracts. This relies on Solidity metadata. + const metadata = JSON.parse(compilerAbstract.data.contracts[submittedContract.filePath][submittedContract.contractName].metadata) + const formData = new FormData() + formData.append('chainId', submittedContract.chainId) + formData.append('codeformat', 'solidity-standard-json-input') + formData.append('sourceCode', compilerAbstract.input.toString()) + formData.append('contractaddress', submittedContract.address) + formData.append('contractname', submittedContract.filePath + ':' + submittedContract.contractName) + formData.append('compilerversion', `v${metadata.compiler.version}`) + formData.append('constructorArguements', submittedContract.abiEncodedConstructorArgs?.replace('0x', '') ?? '') + + const url = new URL(this.apiUrl + '/api') + url.searchParams.append('module', 'contract') + url.searchParams.append('action', 'verifysourcecode') + if (this.apiKey) { + url.searchParams.append('apikey', this.apiKey) + } + + const response = await fetch(url.href, { + method: 'POST', + body: formData, + }) + + if (!response.ok) { + const responseText = await response.text() + console.error('Error on Etherscan API verification at ' + this.apiUrl + '\nStatus: ' + response.status + '\nResponse: ' + responseText) + throw new Error(responseText) + } + + const verificationResponse: EtherscanRpcResponse = await response.json() + + if (verificationResponse.result.includes('already verified')) { + return { status: 'already verified', receiptId: null, lookupUrl: this.getContractCodeUrl(submittedContract.address) } + } + + if (verificationResponse.status !== '1' || verificationResponse.message !== 'OK') { + console.error('Error on Etherscan API verification at ' + this.apiUrl + '\nStatus: ' + verificationResponse.status + '\nMessage: ' + verificationResponse.message + '\nResult: ' + verificationResponse.result) + throw new Error(verificationResponse.result) + } + + const lookupUrl = this.getContractCodeUrl(submittedContract.address) + return { status: 'pending', receiptId: verificationResponse.result, lookupUrl } + } + + async verifyProxy(submittedContract: SubmittedContract): Promise { + if (!submittedContract.proxyAddress) { + throw new Error('SubmittedContract does not have a proxyAddress') + } + + const formData = new FormData() + formData.append('address', submittedContract.proxyAddress) + formData.append('expectedimplementation', submittedContract.address) + + const url = new URL(this.apiUrl + '/api') + url.searchParams.append('module', 'contract') + url.searchParams.append('action', 'verifyproxycontract') + if (this.apiKey) { + url.searchParams.append('apikey', this.apiKey) + } + + const response = await fetch(url.href, { + method: 'POST', + body: formData, + }) + + if (!response.ok) { + const responseText = await response.text() + console.error('Error on Etherscan API proxy verification at ' + this.apiUrl + '\nStatus: ' + response.status + '\nResponse: ' + responseText) + throw new Error(responseText) + } + + const verificationResponse: EtherscanRpcResponse = await response.json() + + if (verificationResponse.status !== '1' || verificationResponse.message !== 'OK') { + console.error('Error on Etherscan API proxy verification at ' + this.apiUrl + '\nStatus: ' + verificationResponse.status + '\nMessage: ' + verificationResponse.message + '\nResult: ' + verificationResponse.result) + throw new Error(verificationResponse.result) + } + + return { status: 'pending', receiptId: verificationResponse.result } + } + + async checkVerificationStatus(receiptId: string): Promise { + const url = new URL(this.apiUrl + '/api') + url.searchParams.append('module', 'contract') + url.searchParams.append('action', 'checkverifystatus') + url.searchParams.append('guid', receiptId) + if (this.apiKey) { + url.searchParams.append('apikey', this.apiKey) + } + + const response = await fetch(url.href, { method: 'GET' }) + + if (!response.ok) { + const responseText = await response.text() + console.error('Error on Etherscan API check verification status at ' + this.apiUrl + '\nStatus: ' + response.status + '\nResponse: ' + responseText) + throw new Error(responseText) + } + + const checkStatusResponse: EtherscanCheckStatusResponse = await response.json() + + if (checkStatusResponse.result.startsWith('Fail - Unable to verify')) { + return { status: 'failed', receiptId, message: checkStatusResponse.result } + } + if (checkStatusResponse.result === 'Pending in queue') { + return { status: 'pending', receiptId } + } + if (checkStatusResponse.result === 'Pass - Verified') { + return { status: 'verified', receiptId } + } + if (checkStatusResponse.result === 'Already Verified') { + return { status: 'already verified', receiptId } + } + if (checkStatusResponse.result === 'Unknown UID') { + console.error('Error on Etherscan API check verification status at ' + this.apiUrl + '\nStatus: ' + checkStatusResponse.status + '\nMessage: ' + checkStatusResponse.message + '\nResult: ' + checkStatusResponse.result) + return { status: 'failed', receiptId, message: checkStatusResponse.result } + } + + if (checkStatusResponse.status !== '1' || !checkStatusResponse.message.startsWith('OK')) { + console.error('Error on Etherscan API check verification status at ' + this.apiUrl + '\nStatus: ' + checkStatusResponse.status + '\nMessage: ' + checkStatusResponse.message + '\nResult: ' + checkStatusResponse.result) + throw new Error(checkStatusResponse.result) + } + + return { status: 'unknown', receiptId } + } + + async checkProxyVerificationStatus(receiptId: string): Promise { + const url = new URL(this.apiUrl + '/api') + url.searchParams.append('module', 'contract') + url.searchParams.append('action', 'checkproxyverification') + url.searchParams.append('guid', receiptId) + if (this.apiKey) { + url.searchParams.append('apikey', this.apiKey) + } + + const response = await fetch(url.href, { method: 'GET' }) + + if (!response.ok) { + const responseText = await response.text() + console.error('Error on Etherscan API check verification status at ' + this.apiUrl + '\nStatus: ' + response.status + '\nResponse: ' + responseText) + throw new Error(responseText) + } + + const checkStatusResponse: EtherscanRpcResponse = await response.json() + + if (checkStatusResponse.result === 'A corresponding implementation contract was unfortunately not detected for the proxy address.' || checkStatusResponse.result === 'The provided expected results are different than the retrieved implementation address!' || checkStatusResponse.result === 'This contract does not look like it contains any delegatecall opcode sequence.') { + return { status: 'failed', receiptId, message: checkStatusResponse.result } + } + if (checkStatusResponse.result === 'Verification in progress') { + return { status: 'pending', receiptId } + } + if (checkStatusResponse.result.startsWith("The proxy's") && checkStatusResponse.result.endsWith('and is successfully updated.')) { + return { status: 'verified', receiptId } + } + if (checkStatusResponse.result === 'Unknown UID') { + console.error('Error on Etherscan API check proxy verification status at ' + this.apiUrl + '\nStatus: ' + checkStatusResponse.status + '\nMessage: ' + checkStatusResponse.message + '\nResult: ' + checkStatusResponse.result) + return { status: 'failed', receiptId, message: checkStatusResponse.result } + } + + if (checkStatusResponse.status !== '1' || !checkStatusResponse.message.startsWith('OK')) { + console.error('Error on Etherscan API check proxy verification status at ' + this.apiUrl + '\nStatus: ' + checkStatusResponse.status + '\nMessage: ' + checkStatusResponse.message + '\nResult: ' + checkStatusResponse.result) + throw new Error(checkStatusResponse.result) + } + + return { status: 'unknown', receiptId } + } + + async lookup(contractAddress: string, chainId: string): Promise { + const url = new URL(this.apiUrl + '/api') + url.searchParams.append('module', 'contract') + url.searchParams.append('action', 'getsourcecode') + url.searchParams.append('address', contractAddress) + if (this.apiKey) { + url.searchParams.append('apikey', this.apiKey) + } + + const response = await fetch(url.href, { method: 'GET' }) + + if (!response.ok) { + const responseText = await response.text() + console.error('Error on Etherscan API lookup at ' + this.apiUrl + '\nStatus: ' + response.status + '\nResponse: ' + responseText) + throw new Error(responseText) + } + + const lookupResponse: EtherscanGetSourceCodeResponse = await response.json() + + if (lookupResponse.status !== '1' || !lookupResponse.message.startsWith('OK')) { + const errorResponse = lookupResponse as unknown as EtherscanRpcResponse + console.error('Error on Etherscan API lookup at ' + this.apiUrl + '\nStatus: ' + errorResponse.status + '\nMessage: ' + errorResponse.message + '\nResult: ' + errorResponse.result) + throw new Error(errorResponse.result) + } + + if (lookupResponse.result[0].ABI === 'Contract source code not verified' || !lookupResponse.result[0].SourceCode) { + return { status: 'not verified' } + } + + const lookupUrl = this.getContractCodeUrl(contractAddress) + const { sourceFiles, targetFilePath } = this.processReceivedFiles(lookupResponse.result[0], contractAddress, chainId) + + return { status: 'verified', lookupUrl, sourceFiles, targetFilePath } + } + + getContractCodeUrl(address: string): string { + const url = new URL(this.explorerUrl + `/address/${address}#code`) + return url.href + } + + processReceivedFiles(source: EtherscanSource, contractAddress: string, chainId: string): { sourceFiles: SourceFile[]; targetFilePath?: string } { + const filePrefix = `/${this.LOOKUP_STORE_DIR}/${chainId}/${contractAddress}` + + // Covers the cases: + // SourceFile: {[FileName]: [content]} + // SourceFile: {{sources: {[FileName]: [content]}}} + let parsedFiles: any + try { + parsedFiles = JSON.parse(source.SourceCode) + } catch (e) { + try { + // Etherscan wraps the Object in one additional bracket + parsedFiles = JSON.parse(source.SourceCode.substring(1, source.SourceCode.length - 1)).sources + } catch (e) {} + } + + if (parsedFiles) { + const result: SourceFile[] = [] + let targetFilePath = '' + for (const [fileName, fileObj] of Object.entries(parsedFiles)) { + const path = `${filePrefix}/${fileName}` + + result.push({ path, content: fileObj.content }) + + if (path.endsWith(`/${source.ContractName}.sol`)) { + targetFilePath = path + } + } + return { sourceFiles: result, targetFilePath } + } + + // Parsing to JSON failed, SourceCode is the code itself + const targetFilePath = `${filePrefix}/${source.ContractName}.sol` + const sourceFiles: SourceFile[] = [{ content: source.SourceCode, path: targetFilePath }] + return { sourceFiles, targetFilePath } + } +} diff --git a/apps/contract-verification/src/app/Verifiers/SourcifyVerifier.ts b/apps/contract-verification/src/app/Verifiers/SourcifyVerifier.ts new file mode 100644 index 0000000000..ab5235e2aa --- /dev/null +++ b/apps/contract-verification/src/app/Verifiers/SourcifyVerifier.ts @@ -0,0 +1,169 @@ +import { CompilerAbstract, SourcesCode } from '@remix-project/remix-solidity' +import { AbstractVerifier } from './AbstractVerifier' +import type { LookupResponse, SourceFile, SubmittedContract, VerificationResponse, VerificationStatus } from '../types' +import { ethers } from 'ethers' + +interface SourcifyVerificationRequest { + address: string + chain: string + files: Record + creatorTxHash?: string + chosenContract?: string +} + +type SourcifyVerificationStatus = 'perfect' | 'full' | 'partial' | null + +interface SourcifyVerificationResponse { + result: [ + { + address: string + chainId: string + status: SourcifyVerificationStatus + libraryMap: { + [key: string]: string + } + message?: string + } + ] +} + +interface SourcifyErrorResponse { + error: string +} + +interface SourcifyFile { + name: string + path: string + content: string +} + +interface SourcifyLookupResponse { + status: Exclude + files: SourcifyFile[] +} + +export class SourcifyVerifier extends AbstractVerifier { + LOOKUP_STORE_DIR = 'sourcify-verified' + + async verify(submittedContract: SubmittedContract, compilerAbstract: CompilerAbstract): Promise { + const metadataStr = compilerAbstract.data.contracts[submittedContract.filePath][submittedContract.contractName].metadata + const sources = compilerAbstract.source.sources + + // from { "filename.sol": {content: "contract MyContract { ... }"} } + // to { "filename.sol": "contract MyContract { ... }" } + const formattedSources = Object.entries(sources).reduce((acc, [fileName, { content }]) => { + acc[fileName] = content + return acc + }, {}) + const body: SourcifyVerificationRequest = { + chain: submittedContract.chainId, + address: submittedContract.address, + files: { + 'metadata.json': metadataStr, + ...formattedSources, + }, + } + + const response = await fetch(new URL(this.apiUrl + '/verify').href, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }) + + if (!response.ok) { + const errorResponse: SourcifyErrorResponse = await response.json() + console.error('Error on Sourcify verification at ' + this.apiUrl + '\nStatus: ' + response.status + '\nResponse: ' + JSON.stringify(errorResponse)) + throw new Error(errorResponse.error) + } + + const verificationResponse: SourcifyVerificationResponse = await response.json() + + if (verificationResponse.result[0].status === null) { + console.error('Error on Sourcify verification at ' + this.apiUrl + '\nStatus: ' + response.status + '\nResponse: ' + verificationResponse.result[0].message) + throw new Error(verificationResponse.result[0].message) + } + + // Map to a user-facing status message + let status: VerificationStatus = 'unknown' + let lookupUrl: string | undefined = undefined + if (verificationResponse.result[0].status === 'perfect' || verificationResponse.result[0].status === 'full') { + status = 'fully verified' + lookupUrl = this.getContractCodeUrl(submittedContract.address, submittedContract.chainId, true) + } else if (verificationResponse.result[0].status === 'partial') { + status = 'partially verified' + lookupUrl = this.getContractCodeUrl(submittedContract.address, submittedContract.chainId, false) + } + + return { status, receiptId: null, lookupUrl } + } + + async lookup(contractAddress: string, chainId: string): Promise { + const url = new URL(this.apiUrl + `/files/any/${chainId}/${contractAddress}`) + + const response = await fetch(url.href, { method: 'GET' }) + + if (!response.ok) { + const errorResponse: SourcifyErrorResponse = await response.json() + + if (errorResponse.error === 'Files have not been found!') { + return { status: 'not verified' } + } + + console.error('Error on Sourcify lookup at ' + this.apiUrl + '\nStatus: ' + response.status + '\nResponse: ' + JSON.stringify(errorResponse)) + throw new Error(errorResponse.error) + } + + const lookupResponse: SourcifyLookupResponse = await response.json() + + let status: VerificationStatus = 'unknown' + let lookupUrl: string | undefined = undefined + if (lookupResponse.status === 'perfect' || lookupResponse.status === 'full') { + status = 'fully verified' + lookupUrl = this.getContractCodeUrl(contractAddress, chainId, true) + } else if (lookupResponse.status === 'partial') { + status = 'partially verified' + lookupUrl = this.getContractCodeUrl(contractAddress, chainId, false) + } + + const { sourceFiles, targetFilePath } = this.processReceivedFiles(lookupResponse.files, contractAddress, chainId) + + return { status, lookupUrl, sourceFiles, targetFilePath } + } + + getContractCodeUrl(address: string, chainId: string, fullMatch: boolean): string { + const url = new URL(this.explorerUrl + `/contracts/${fullMatch ? 'full_match' : 'partial_match'}/${chainId}/${address}/`) + return url.href + } + + processReceivedFiles(files: SourcifyFile[], contractAddress: string, chainId: string): { sourceFiles: SourceFile[]; targetFilePath?: string } { + const result: SourceFile[] = [] + let targetFilePath: string + const filePrefix = `/${this.LOOKUP_STORE_DIR}/${chainId}/${contractAddress}` + + for (const file of files) { + let filePath: string + for (const a of [contractAddress, ethers.utils.getAddress(contractAddress)]) { + const matching = file.path.match(`/${a}/(.*)$`) + if (matching) { + filePath = matching[1] + break + } + } + + if (filePath) { + result.push({ path: `${filePrefix}/${filePath}`, content: file.content }) + } + + if (file.name === 'metadata.json') { + const metadata = JSON.parse(file.content) + const compilationTarget = metadata.settings.compilationTarget + const contractPath = Object.keys(compilationTarget)[0] + targetFilePath = `${filePrefix}/sources/${contractPath}` + } + } + + return { sourceFiles: result, targetFilePath } + } +} diff --git a/apps/contract-verification/src/app/Verifiers/index.ts b/apps/contract-verification/src/app/Verifiers/index.ts new file mode 100644 index 0000000000..23de8cd89d --- /dev/null +++ b/apps/contract-verification/src/app/Verifiers/index.ts @@ -0,0 +1,30 @@ +import type { VerifierIdentifier, VerifierSettings } from '../types' +import { AbstractVerifier } from './AbstractVerifier' +import { BlockscoutVerifier } from './BlockscoutVerifier' +import { EtherscanVerifier } from './EtherscanVerifier' +import { SourcifyVerifier } from './SourcifyVerifier' + +export { AbstractVerifier } from './AbstractVerifier' +export { BlockscoutVerifier } from './BlockscoutVerifier' +export { SourcifyVerifier } from './SourcifyVerifier' +export { EtherscanVerifier } from './EtherscanVerifier' + +export function getVerifier(identifier: VerifierIdentifier, settings: VerifierSettings): AbstractVerifier { + switch (identifier) { + case 'Sourcify': + if (!settings?.explorerUrl) { + throw new Error('The Sourcify verifier requires an explorer URL.') + } + return new SourcifyVerifier(settings.apiUrl, settings.explorerUrl) + case 'Etherscan': + if (!settings?.explorerUrl) { + throw new Error('The Etherscan verifier requires an explorer URL.') + } + if (!settings?.apiKey) { + throw new Error('The Etherscan verifier requires an API key.') + } + return new EtherscanVerifier(settings.apiUrl, settings.explorerUrl, settings.apiKey) + case 'Blockscout': + return new BlockscoutVerifier(settings.apiUrl) + } +} diff --git a/apps/contract-verification/src/app/VerifyFormContext.tsx b/apps/contract-verification/src/app/VerifyFormContext.tsx new file mode 100644 index 0000000000..14d146b0c1 --- /dev/null +++ b/apps/contract-verification/src/app/VerifyFormContext.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import type { Chain } from './types' +import { ContractDropdownSelection } from './components/ContractDropdown' + +// Define the type for the context +type VerifyFormContextType = { + selectedChain: Chain | undefined + setSelectedChain: React.Dispatch> + contractAddress: string + setContractAddress: React.Dispatch> + contractAddressError: string + setContractAddressError: React.Dispatch> + selectedContract: ContractDropdownSelection | undefined + setSelectedContract: React.Dispatch> + proxyAddress: string + setProxyAddress: React.Dispatch> + proxyAddressError: string + setProxyAddressError: React.Dispatch> + abiEncodedConstructorArgs: string + setAbiEncodedConstructorArgs: React.Dispatch> + abiEncodingError: string + setAbiEncodingError: React.Dispatch> +} + +// Provide a default value with the appropriate types +const defaultContextValue: VerifyFormContextType = { + selectedChain: undefined, + setSelectedChain: (selectedChain: Chain) => {}, + contractAddress: '', + setContractAddress: (contractAddress: string) => {}, + contractAddressError: '', + setContractAddressError: (contractAddressError: string) => {}, + selectedContract: undefined, + setSelectedContract: (selectedContract: ContractDropdownSelection) => {}, + proxyAddress: '', + setProxyAddress: (proxyAddress: string) => {}, + proxyAddressError: '', + setProxyAddressError: (contractAddressError: string) => {}, + abiEncodedConstructorArgs: '', + setAbiEncodedConstructorArgs: (contractAddproxyAddressress: string) => {}, + abiEncodingError: '', + setAbiEncodingError: (contractAddressError: string) => {}, +} + +// Create the context with the type +export const VerifyFormContext = React.createContext(defaultContextValue) diff --git a/apps/contract-verification/src/app/app.tsx b/apps/contract-verification/src/app/app.tsx new file mode 100644 index 0000000000..0ea4332543 --- /dev/null +++ b/apps/contract-verification/src/app/app.tsx @@ -0,0 +1,154 @@ +import { useState, useEffect, useRef } from 'react' + +import { ContractVerificationPluginClient } from './ContractVerificationPluginClient' + +import { AppContext } from './AppContext' +import { VerifyFormContext } from './VerifyFormContext' +import DisplayRoutes from './routes' +import type { ContractVerificationSettings, ThemeType, Chain, SubmittedContracts, VerificationReceipt, VerificationResponse } from './types' +import { mergeChainSettingsWithDefaults } from './utils' + +import './App.css' +import { CompilerAbstract } from '@remix-project/remix-solidity' +import { useLocalStorage } from './hooks/useLocalStorage' +import { getVerifier } from './Verifiers' +import { ContractDropdownSelection } from './components/ContractDropdown' + +const plugin = new ContractVerificationPluginClient() + +const App = () => { + const [themeType, setThemeType] = useState('dark') + const [settings, setSettings] = useLocalStorage('contract-verification:settings', { chains: {} }) + const [submittedContracts, setSubmittedContracts] = useLocalStorage('contract-verification:submitted-contracts', {}) + const [chains, setChains] = useState([]) // State to hold the chains data + const [compilationOutput, setCompilationOutput] = useState<{ [key: string]: CompilerAbstract } | undefined>() + + // Form values: + const [selectedChain, setSelectedChain] = useState() + const [contractAddress, setContractAddress] = useState('') + const [contractAddressError, setContractAddressError] = useState('') + const [selectedContract, setSelectedContract] = useState() + const [proxyAddress, setProxyAddress] = useState('') + const [proxyAddressError, setProxyAddressError] = useState('') + const [abiEncodedConstructorArgs, setAbiEncodedConstructorArgs] = useState('') + const [abiEncodingError, setAbiEncodingError] = useState('') + + const timer = useRef(null) + + useEffect(() => { + plugin.internalEvents.on('verification_activated', () => { + // Fetch compiler artefacts initially + plugin.call('compilerArtefacts' as any, 'getAllCompilerAbstracts').then((obj: any) => { + setCompilationOutput(obj) + }) + + // Subscribe to compilations + plugin.on('compilerArtefacts' as any, 'compilationSaved', (compilerAbstracts: { [key: string]: CompilerAbstract }) => { + setCompilationOutput((prev) => ({ ...(prev || {}), ...compilerAbstracts })) + }) + + // Fetch chains.json and update state + fetch('https://chainid.network/chains.json') + .then((response) => response.json()) + .then((data) => setChains(data)) + .catch((error) => console.error('Failed to fetch chains.json:', error)) + }) + + // Clean up on unmount + return () => { + plugin.off('compilerArtefacts' as any, 'compilationSaved') + } + }, []) + + // Poll status of pending receipts frequently + useEffect(() => { + const getPendingReceipts = (submissions: SubmittedContracts) => { + const pendingReceipts: VerificationReceipt[] = [] + // Check statuses of receipts + for (const submission of Object.values(submissions)) { + for (const receipt of submission.receipts) { + if (receipt.status === 'pending') { + pendingReceipts.push(receipt) + } + } + for (const proxyReceipt of submission.proxyReceipts ?? []) { + if (proxyReceipt.status === 'pending') { + pendingReceipts.push(proxyReceipt) + } + } + } + return pendingReceipts + } + + let pendingReceipts = getPendingReceipts(submittedContracts) + + if (pendingReceipts.length > 0) { + if (timer.current) { + clearInterval(timer.current) + timer.current = null + } + + const pollStatus = async () => { + const changedSubmittedContracts = { ...submittedContracts } + + for (const receipt of pendingReceipts) { + await new Promise((resolve) => setTimeout(resolve, 500)) // avoid api rate limit exceed. + + const { verifierInfo, receiptId } = receipt + if (receiptId) { + const contract = changedSubmittedContracts[receipt.contractId] + const chainSettings = mergeChainSettingsWithDefaults(contract.chainId, settings) + const verifierSettings = chainSettings.verifiers[verifierInfo.name] + + // In case the user overwrites the API later, prefer the one stored in localStorage + const verifier = getVerifier(verifierInfo.name, { ...verifierSettings, apiUrl: verifierInfo.apiUrl }) + if (!verifier.checkVerificationStatus) { + continue + } + + try { + let response: VerificationResponse + if (receipt.isProxyReceipt) { + response = await verifier.checkProxyVerificationStatus(receiptId) + } else { + response = await verifier.checkVerificationStatus(receiptId) + } + const { status, message, lookupUrl } = response + receipt.status = status + receipt.message = message + if (lookupUrl) { + receipt.lookupUrl = lookupUrl + } + } catch (e) { + receipt.failedChecks++ + // Only retry 5 times + if (receipt.failedChecks >= 5) { + receipt.status = 'failed' + receipt.message = e.message + } + } + } + } + + pendingReceipts = getPendingReceipts(changedSubmittedContracts) + if (timer.current && pendingReceipts.length === 0) { + clearInterval(timer.current) + timer.current = null + } + setSubmittedContracts((prev) => Object.assign({}, prev, changedSubmittedContracts)) + } + + timer.current = setInterval(pollStatus, 1000) + } + }, [submittedContracts]) + + return ( + + + + + + ) +} + +export default App diff --git a/apps/contract-verification/src/app/components/AccordionReceipt.tsx b/apps/contract-verification/src/app/components/AccordionReceipt.tsx new file mode 100644 index 0000000000..33ee96a7ce --- /dev/null +++ b/apps/contract-verification/src/app/components/AccordionReceipt.tsx @@ -0,0 +1,105 @@ +import React, { useMemo } from 'react' +import { SubmittedContract, VerificationReceipt } from '../types' +import { shortenAddress, CustomTooltip } from '@remix-ui/helper' +import { AppContext } from '../AppContext' +import { CopyToClipboard } from '@remix-ui/clipboard' + +interface AccordionReceiptProps { + contract: SubmittedContract + index: number +} + +export const AccordionReceipt: React.FC = ({ contract, index }) => { + const { chains } = React.useContext(AppContext) + + const [expanded, setExpanded] = React.useState(false) + + const chain = useMemo(() => { + return chains.find((c) => c.chainId === parseInt(contract.chainId)) + }, [contract, chains]) + const chainName = chain?.name ?? 'Unknown Chain' + + const hasProxy = contract.proxyAddress && contract.proxyReceipts + + const toggleAccordion = () => { + setExpanded(!expanded) + } + + return ( +
+
+ + +
+ + + {contract.contractName} at {shortenAddress(contract.address)} {contract.proxyAddress ? 'with proxy' : ''} + + +
+ + +
+ +
+
+ Chain: + {chainName} ({contract.chainId}) +
+
+ File: + {contract.filePath} +
+
+ Submitted at: + {new Date(contract.date).toLocaleString()} +
+ +
+ Verified at: + +
+ + {hasProxy && ( + <> +
+ Proxy Address: + + {shortenAddress(contract.proxyAddress)} + + +
+
+ Proxy verified at: + +
+ + )} +
+
+ ) +} + +const ReceiptsBody = ({ receipts }: { receipts: VerificationReceipt[] }) => { + return ( +
    + {receipts.map((receipt) => ( +
  • + + {receipt.verifierInfo.name} + + + + {['verified', 'partially verified', 'already verified'].includes(receipt.status) ? : receipt.status === 'fully verified' ? : receipt.status === 'failed' ? : ['pending', 'awaiting implementation verification'].includes(receipt.status) ? : } + + + {!!receipt.lookupUrl && } +
  • + ))} +
+ ) +} diff --git a/apps/contract-verification/src/app/components/ConfigInput.tsx b/apps/contract-verification/src/app/components/ConfigInput.tsx new file mode 100644 index 0000000000..0737840115 --- /dev/null +++ b/apps/contract-verification/src/app/components/ConfigInput.tsx @@ -0,0 +1,69 @@ +import React, { useEffect, useState } from 'react' +import { CustomTooltip } from '@remix-ui/helper' + +interface ConfigInputProps { + label: string + id: string + secret: boolean + initialValue: string + saveResult: (result: string) => void +} + +// Chooses one contract from the compilation output. +export const ConfigInput: React.FC = ({ label, id, secret, initialValue, saveResult }) => { + const [value, setValue] = useState(initialValue) + const [enabled, setEnabled] = useState(false) + + // Reset state when initialValue changes + useEffect(() => { + setValue(initialValue) + setEnabled(false) + }, [initialValue]) + + const handleChange = () => { + setEnabled(true) + } + + const handleSave = () => { + setEnabled(false) + saveResult(value) + } + + const handleCancel = () => { + setEnabled(false) + setValue(initialValue) + } + + return ( +
+ +
+ setValue(e.target.value)} + disabled={!enabled} + /> + + { enabled ? ( + <> + + + + ) : ( + + + + )} +
+
+ ) +} diff --git a/apps/contract-verification/src/app/components/ConstructorArguments.tsx b/apps/contract-verification/src/app/components/ConstructorArguments.tsx new file mode 100644 index 0000000000..e2a4239eaa --- /dev/null +++ b/apps/contract-verification/src/app/components/ConstructorArguments.tsx @@ -0,0 +1,135 @@ +import { useContext, useEffect, useRef, useState } from 'react' +import { ethers } from 'ethers' + +import { AppContext } from '../AppContext' +import { ContractDropdownSelection } from './ContractDropdown' + +interface ConstructorArgumentsProps { + abiEncodedConstructorArgs: string + setAbiEncodedConstructorArgs: React.Dispatch> + abiEncodingError: string + setAbiEncodingError: React.Dispatch> + selectedContract: ContractDropdownSelection +} + +export const ConstructorArguments: React.FC = ({ abiEncodedConstructorArgs, setAbiEncodedConstructorArgs, abiEncodingError, setAbiEncodingError, selectedContract }) => { + const { compilationOutput } = useContext(AppContext) + const [toggleRawInput, setToggleRawInput] = useState(false) + + const { triggerFilePath, filePath, contractName } = selectedContract + const selectedCompilerAbstract = triggerFilePath && compilationOutput[triggerFilePath] + const compiledContract = selectedCompilerAbstract?.data?.contracts?.[filePath]?.[contractName] + const abi = compiledContract?.abi + + const constructorArgs = abi && abi.find((a) => a.type === 'constructor')?.inputs + + const decodeConstructorArgs = (value: string) => { + try { + const decodedObj = ethers.utils.defaultAbiCoder.decode( + constructorArgs.map((inp) => inp.type), + value + ) + const decoded = decodedObj.map((val) => JSON.stringify(val)) + return { decoded, errorMessage: '' } + } catch (e) { + console.error(e) + const errorMessage = 'Decoding error: ' + e.message + const decoded = Array(constructorArgs?.length ?? 0).fill('') + return { decoded, errorMessage } + } + } + + const [constructorArgsValues, setConstructorArgsValues] = useState(abiEncodedConstructorArgs ? decodeConstructorArgs(abiEncodedConstructorArgs).decoded : Array(constructorArgs?.length ?? 0).fill('')) + + const constructorArgsInInitialState = useRef(true) + useEffect(() => { + if (constructorArgsInInitialState.current) { + constructorArgsInInitialState.current = false + return + } + setAbiEncodedConstructorArgs('') + setAbiEncodingError('') + setConstructorArgsValues(Array(constructorArgs?.length ?? 0).fill('')) + }, [constructorArgs]) + + const handleConstructorArgs = (value: string, index: number) => { + const changedConstructorArgsValues = [...constructorArgsValues.slice(0, index), value, ...constructorArgsValues.slice(index + 1)] + setConstructorArgsValues(changedConstructorArgsValues) + + // if any constructorArgsValue is falsey (empty etc.), don't encode yet + if (changedConstructorArgsValues.some((value) => !value)) { + setAbiEncodedConstructorArgs('') + setAbiEncodingError('') + return + } + + const types = constructorArgs.map((inp) => inp.type) + const parsedArgsValues = [] + for (const arg of changedConstructorArgsValues) { + try { + parsedArgsValues.push(JSON.parse(arg)) + } catch (e) { + parsedArgsValues.push(arg) + } + } + + try { + const newAbiEncoding = ethers.utils.defaultAbiCoder.encode(types, parsedArgsValues) + setAbiEncodedConstructorArgs(newAbiEncoding) + setAbiEncodingError('') + } catch (e) { + console.error(e) + setAbiEncodedConstructorArgs('') + setAbiEncodingError('Encoding error: ' + e.message) + } + } + + const handleRawConstructorArgs = (value: string) => { + setAbiEncodedConstructorArgs(value) + const { decoded, errorMessage } = decodeConstructorArgs(value) + setConstructorArgsValues(decoded) + setAbiEncodingError(errorMessage) + } + + if (!selectedContract) return null + if (!compilationOutput && Object.keys(compilationOutput).length === 0) return null + // No render if no constructor args + if (!constructorArgs || constructorArgs.length === 0) return null + + return ( +
+ +
+ setToggleRawInput(!toggleRawInput)} /> + +
+ {toggleRawInput ? ( +
+ {' '} + +
+ otherwise +
) } @@ -202,7 +223,7 @@ export function AccountUI(props: AccountProps) { - + }> @@ -234,3 +255,29 @@ export function AccountUI(props: AccountProps) {
) } + +const EIP712_Example = { + domain: { + chainId: 1, + name: "Example App", + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + version: "1", + }, + message: { + prompt: "Welcome! In order to authenticate to this website, sign this request and your public address will be sent to the server in a verifiable way.", + createdAt: 1718570375196, + }, + primaryType: 'AuthRequest', + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + AuthRequest: [ + { name: 'prompt', type: 'string' }, + { name: 'createdAt', type: 'uint256' }, + ], + }, +} \ No newline at end of file diff --git a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx index 44aad72011..7ccc8ce55b 100644 --- a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx @@ -6,7 +6,6 @@ import { ContractData, FuncABI, OverSizeLimit } from '@remix-project/core-plugin import * as ethJSUtil from '@ethereumjs/util' import { ContractGUI } from './contractGUI' import { CustomTooltip, deployWithProxyMsg, upgradeWithProxyMsg } from '@remix-ui/helper' -import { title } from 'process' const _paq = (window._paq = window._paq || []) export function ContractDropdownUI(props: ContractDropdownProps) { @@ -80,6 +79,7 @@ export function ContractDropdownUI(props: ContractDropdownProps) { content: '', }) if (!currentContract) enableAtAddress(false) + if (currentContract && loadedAddress) enableAtAddress(true) } else { setAbiLabel({ display: 'none', 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 4f6c2964ae..1cb2a91918 100644 --- a/libs/remix-ui/run-tab/src/lib/components/environment.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/environment.tsx @@ -35,7 +35,7 @@ export function EnvironmentUI(props: EnvironmentProps) { }> - + diff --git a/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx b/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx index eee7ceb166..37253056f7 100644 --- a/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx @@ -8,60 +8,24 @@ import { UniversalDappUI } from './universalDappUI' export function InstanceContainerUI(props: InstanceContainerProps) { const { instanceList } = props.instances - const clearInstance = () => { + const clearInstance = async() => { + const isPinnedAvailable = await props.plugin.call('fileManager', 'exists', `.deploys/pinned-contracts/${props.plugin.REACT_API.chainId}`) + if (isPinnedAvailable) await props.plugin.call('fileManager', 'remove', `.deploys/pinned-contracts/${props.plugin.REACT_API.chainId}`) props.clearInstances() } return ( -
-
- }> - - -
- - {props.pinnedInstances.instanceList.length > 0 ? ( -
- {' '} - {props.pinnedInstances.instanceList.map((instance, index) => { - return ( - - ) - })} -
- ) : ( - - - - )} - -
+
+
}> - + +
{instanceList.length}
+
+
{instanceList.length > 0 ? ( } > - + ) : null}
+ {instanceList.length > 0 ? (
{' '} @@ -81,8 +46,9 @@ export function InstanceContainerUI(props: InstanceContainerProps) { - ) : ( - - - - )} + ) : ''}
) } diff --git a/libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx b/libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx index ff64fb820f..f565278e37 100644 --- a/libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx @@ -57,8 +57,7 @@ export function RecorderUI(props: RecorderProps) { tooltipText={} >