Merge remote-tracking branch 'upstream/master' into spacesailor24/web3js-v4-upgrade

pull/5370/head
Oleksii Kosynskyi 1 year ago
commit 5942374311
  1. 7
      .eslintrc.json
  2. 4
      .prettierrc.json
  3. 14
      apps/debugger/src/app/app.tsx
  4. 11
      apps/debugger/src/main.tsx
  5. 67
      apps/debugger/webpack.config.js
  6. 30
      apps/doc-gen/src/app/App.tsx
  7. 25
      apps/doc-gen/src/app/hooks/useLocalStorage.tsx
  8. 23
      apps/doc-gen/src/app/views/ErrorView.tsx
  9. 10
      apps/doc-gen/src/main.tsx
  10. 25
      apps/doc-gen/webpack.config.js
  11. 9
      apps/doc-viewer/src/app/App.tsx
  12. 4
      apps/doc-viewer/src/main.tsx
  13. 32
      apps/doc-viewer/webpack.config.js
  14. 20
      apps/etherscan/src/app/AppContext.tsx
  15. 87
      apps/etherscan/src/app/RemixPlugin.tsx
  16. 100
      apps/etherscan/src/app/app.tsx
  17. 63
      apps/etherscan/src/app/components/HeaderWithSettings.tsx
  18. 30
      apps/etherscan/src/app/components/SubmitButton.tsx
  19. 5
      apps/etherscan/src/app/hooks/useLocalStorage.tsx
  20. 10
      apps/etherscan/src/app/layouts/Default.tsx
  21. 18
      apps/etherscan/src/app/routes.tsx
  22. 107
      apps/etherscan/src/app/views/CaptureKeyView.tsx
  23. 16
      apps/etherscan/src/app/views/ErrorView.tsx
  24. 17
      apps/etherscan/src/app/views/HomeView.tsx
  25. 135
      apps/etherscan/src/app/views/ReceiptsView.tsx
  26. 316
      apps/etherscan/src/app/views/VerifyView.tsx
  27. 14
      apps/etherscan/src/main.tsx
  28. 64
      apps/etherscan/webpack.config.js
  29. 64
      apps/remix-ide-e2e/nightwatch.ts
  30. 104
      apps/remix-ide-e2e/src/local-plugin/src/app/app.tsx
  31. 8
      apps/remix-ide-e2e/src/local-plugin/src/app/logger.tsx
  32. 4
      apps/remix-ide/background.js
  33. 28
      apps/remix-ide/src/app/components/hidden-panel.tsx
  34. 34
      apps/remix-ide/src/app/components/main-panel.tsx
  35. 200
      apps/remix-ide/src/app/components/preload.tsx
  36. 19
      apps/remix-ide/src/app/components/side-panel.tsx
  37. 72
      apps/remix-ide/src/app/components/vertical-icons.tsx
  38. 32
      apps/remix-ide/src/app/plugins/contractFlattener.tsx
  39. 24
      apps/remix-ide/src/app/plugins/notification.tsx
  40. 384
      apps/remix-ide/src/app/plugins/parser/code-parser.tsx
  41. 32
      apps/remix-ide/src/app/plugins/permission-handler-plugin.tsx
  42. 128
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  43. 39
      apps/remix-ide/src/app/plugins/solidity-script.tsx
  44. 155
      apps/remix-ide/src/app/plugins/solidity-umlgen.tsx
  45. 56
      apps/remix-ide/src/app/providers/abstract-provider.tsx
  46. 90
      apps/remix-ide/src/app/providers/custom-vm-fork-provider.tsx
  47. 31
      apps/remix-ide/src/app/providers/external-http-provider.tsx
  48. 25
      apps/remix-ide/src/app/providers/foundry-provider.tsx
  49. 25
      apps/remix-ide/src/app/providers/ganache-provider.tsx
  50. 31
      apps/remix-ide/src/app/providers/goerli-vm-fork-provider.tsx
  51. 21
      apps/remix-ide/src/app/providers/hardhat-provider.tsx
  52. 31
      apps/remix-ide/src/app/providers/injected-L2-provider.tsx
  53. 7
      apps/remix-ide/src/app/providers/injected-arbitrum-one-provider.tsx
  54. 7
      apps/remix-ide/src/app/providers/injected-optimism-provider.tsx
  55. 16
      apps/remix-ide/src/app/providers/injected-provider-default.tsx
  56. 10
      apps/remix-ide/src/app/providers/injected-provider-trustwallet.tsx
  57. 52
      apps/remix-ide/src/app/providers/injected-provider.tsx
  58. 31
      apps/remix-ide/src/app/providers/mainnet-vm-fork-provider.tsx
  59. 31
      apps/remix-ide/src/app/providers/sepolia-vm-fork-provider.tsx
  60. 114
      apps/remix-ide/src/app/providers/vm-provider.tsx
  61. 12
      apps/remix-ide/src/app/tabs/search.tsx
  62. 46
      apps/remix-ide/src/app/tabs/settings-tab.tsx
  63. 573
      apps/remix-ide/src/blockchain/blockchain.tsx
  64. 19
      apps/remix-ide/src/index.tsx
  65. 92
      apps/remix-ide/webpack.config.js
  66. 8
      apps/solhint/src/app/App.tsx
  67. 38
      apps/solhint/webpack.config.js
  68. 4
      apps/solidity-compiler/src/app/app.tsx
  69. 68
      apps/solidity-compiler/webpack.config.js
  70. 45
      apps/vyper/src/app/app.tsx
  71. 34
      apps/vyper/src/app/components/CompilerButton.tsx
  72. 17
      apps/vyper/src/app/components/LocalUrl.tsx
  73. 68
      apps/vyper/src/app/components/VyperResult.tsx
  74. 5
      apps/vyper/src/app/components/WarnRemote.tsx
  75. 39
      apps/vyper/src/app/utils/compiler.tsx
  76. 39
      apps/vyper/src/app/utils/remix-client.tsx
  77. 14
      apps/vyper/src/main.tsx
  78. 67
      apps/vyper/webpack.config.js
  79. 18
      apps/walletconnect/src/app/app.tsx
  80. 29
      apps/walletconnect/src/app/walletConnectUI.tsx
  81. 5
      apps/walletconnect/src/main.tsx
  82. 66
      apps/walletconnect/webpack.config.js
  83. 14
      libs/remix-debug/test.ts
  84. 25
      libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx
  85. 10
      libs/remix-ui/app/src/lib/remix-app/components/modals/dialogViewPlugin.tsx
  86. 11
      libs/remix-ui/app/src/lib/remix-app/components/modals/dialogs.tsx
  87. 58
      libs/remix-ui/app/src/lib/remix-app/components/modals/matomo.tsx
  88. 58
      libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx
  89. 28
      libs/remix-ui/app/src/lib/remix-app/components/modals/origin-warning.tsx
  90. 23
      libs/remix-ui/app/src/lib/remix-app/components/splashscreen.tsx
  91. 8
      libs/remix-ui/app/src/lib/remix-app/context/context.tsx
  92. 69
      libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
  93. 54
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  94. 69
      libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx
  95. 41
      libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx
  96. 12
      libs/remix-ui/debugger-ui/src/hooks/extract-data.tsx
  97. 267
      libs/remix-ui/debugger-ui/src/lib/button-navigator/button-navigator.tsx
  98. 213
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  99. 15
      libs/remix-ui/debugger-ui/src/lib/slider/slider.tsx
  100. 28
      libs/remix-ui/debugger-ui/src/lib/step-manager/step-manager.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -48,6 +48,11 @@
"no-empty": "off", "no-empty": "off",
"jsx-a11y/anchor-is-valid": "off", "jsx-a11y/anchor-is-valid": "off",
"@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "off",
"react-hooks/exhaustive-deps": "off",
"array-callback-return": "off",
"prefer-spread": "off",
"indent": ["error", 2] "indent": ["error", 2]
} }
}, },
@ -67,4 +72,4 @@
"globals": { "globals": {
"JSX": true "JSX": true
} }
} }

@ -1,6 +1,7 @@
{ {
"tabWidth": 2, "tabWidth": 2,
"useTabs": false, "useTabs": false,
"printWidth": 180,
"semi": false, "semi": false,
"singleQuote": true, "singleQuote": true,
"quoteProps": "consistent", "quoteProps": "consistent",
@ -9,6 +10,5 @@
"trailingComma": "none", "trailingComma": "none",
"jsxBracketSameLine": false, "jsxBracketSameLine": false,
"arrowParens": "always", "arrowParens": "always",
"singleAttributePerLine": false, "singleAttributePerLine": false
"ignorePath": ".prettierignore"
} }

@ -1,17 +1,17 @@
import React, { useState, useEffect } from 'react'; import React, {useState, useEffect} from 'react'
import { DebuggerUI } from '@remix-ui/debugger-ui' // eslint-disable-line import {DebuggerUI} from '@remix-ui/debugger-ui' // eslint-disable-line
import { DebuggerClientApi } from './debugger' import {DebuggerClientApi} from './debugger'
const remix = new DebuggerClientApi() const remix = new DebuggerClientApi()
export const App = () => { export const App = () => {
return ( return (
<div className="debugger"> <div className="debugger">
<DebuggerUI debuggerAPI={remix} /> <DebuggerUI debuggerAPI={remix} />
</div> </div>
); )
}; }
export default App; export default App

@ -1,9 +1,6 @@
import React from 'react'; import React from 'react'
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom'
import App from './app/app'; import App from './app/app'
ReactDOM.render( ReactDOM.render(<App />, document.getElementById('root'))
<App />,
document.getElementById('root')
);

@ -1,8 +1,7 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => { module.exports = composePlugins(withNx(), (config) => {
@ -12,56 +11,52 @@ module.exports = composePlugins(withNx(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify')
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
config.output.publicPath = '/' config.output.publicPath = '/'
// add copy & provide plugin // add copy & provide plugin
config.plugins.push( config.plugins.push(
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}) })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer // set minimizer
config.optimization.minimizer = [ config.optimization.minimizer = [
new TerserPlugin({ new TerserPlugin({
@ -71,17 +66,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/
} }
return config; return config
}); })

@ -4,13 +4,13 @@ import './App.css'
import { DocGenClient } from './docgen-client' import { DocGenClient } from './docgen-client'
import { Build } from './docgen/site' import { Build } from './docgen/site'
export const client = new DocGenClient() export const client = new DocGenClient()
const App = () => { const App = () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [themeType, setThemeType] = useState<string>('dark'); const [themeType, setThemeType] = useState<string>('dark')
const [hasBuild, setHasBuild] = useState<boolean>(false); const [hasBuild, setHasBuild] = useState<boolean>(false)
const [fileName, setFileName] = useState<string>(''); const [fileName, setFileName] = useState<string>('')
useEffect(() => { useEffect(() => {
const watchThemeSwitch = async () => { const watchThemeSwitch = async () => {
@ -21,19 +21,27 @@ const App = () => {
setHasBuild(true) setHasBuild(true)
setFileName(fileName) setFileName(fileName)
}) })
client.eventEmitter.on('docsGenerated', (docs: string[]) => { client.eventEmitter.on('docsGenerated', (docs: string[]) => {})
})
} }
watchThemeSwitch() watchThemeSwitch()
}, []) }, [])
return ( return (
<div className="p-3"> <div className="p-3">
<h5 className="h-5 mb-3">Compile a Solidity contract and generate its documentation as Markdown. (Right-click on a contract in the File Explorer and select "Generate Docs" from the context menu.).</h5> <h5 className="h-5 mb-3">
{fileName && <div className="border-bottom border-top px-2 py-3 justify-center align-items-center d-flex"> Compile a Solidity contract and generate its documentation as Markdown. (Right-click on a contract in the File
<h6>File: {fileName}</h6> Explorer and select "Generate Docs" from the context menu.).
</div>} </h5>
{hasBuild && <button className="btn btn-primary btn-block mt-4" onClick={() => client.generateDocs()}>Generate Docs</button>} {fileName && (
<div className="border-bottom border-top px-2 py-3 justify-center align-items-center d-flex">
<h6>File: {fileName}</h6>
</div>
)}
{hasBuild && (
<button className="btn btn-primary btn-block mt-4" onClick={() => client.generateDocs()}>
Generate Docs
</button>
)}
</div> </div>
) )
} }

@ -1,4 +1,4 @@
import { useState } from "react"; import { useState } from 'react'
export function useLocalStorage(key: string, initialValue: any) { export function useLocalStorage(key: string, initialValue: any) {
// State to store our value // State to store our value
@ -6,32 +6,31 @@ export function useLocalStorage(key: string, initialValue: any) {
const [storedValue, setStoredValue] = useState(() => { const [storedValue, setStoredValue] = useState(() => {
try { try {
// Get from local storage by key // Get from local storage by key
const item = window.localStorage.getItem(key); const item = window.localStorage.getItem(key)
// Parse stored json or if none return initialValue // Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue; return item ? JSON.parse(item) : initialValue
} catch (error) { } catch (error) {
// If error also return initialValue // If error also return initialValue
console.log(error); console.log(error)
return initialValue; return initialValue
} }
}); })
// Return a wrapped version of useState's setter function that ... // Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage. // ... persists the new value to localStorage.
const setValue = (value: any) => { const setValue = (value: any) => {
try { try {
// Allow value to be a function so we have same API as useState // Allow value to be a function so we have same API as useState
const valueToStore = const valueToStore = value instanceof Function ? value(storedValue) : value
value instanceof Function ? value(storedValue) : value;
// Save state // Save state
setStoredValue(valueToStore); setStoredValue(valueToStore)
// Save to local storage // Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore)); window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) { } catch (error) {
// A more advanced implementation would handle the error case // A more advanced implementation would handle the error case
console.log(error); console.log(error)
} }
}; }
return [storedValue, setValue]; return [storedValue, setValue]
} }

@ -1,31 +1,28 @@
import React from "react"; import React from 'react'
export const ErrorView: React.FC = () => { export const ErrorView: React.FC = () => {
return ( return (
<div <div
style={{ style={{
width: "100%", width: '100%',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
alignItems: "center", alignItems: 'center',
}} }}
> >
<img <img
style={{ paddingBottom: "2em" }} style={{ paddingBottom: '2em' }}
width="250" width="250"
src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png" src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png"
alt="Error page" alt="Error page"
/> />
<h5>Sorry, something unexpected happened. </h5> <h5>Sorry, something unexpected happened. </h5>
<h5> <h5>
Please raise an issue:{" "} Please raise an issue:{' '}
<a <a style={{ color: 'red' }} href="https://github.com/Machinalabs/remix-ethdoc-plugin/issues">
style={{ color: "red" }}
href="https://github.com/Machinalabs/remix-ethdoc-plugin/issues"
>
Here Here
</a> </a>
</h5> </h5>
</div> </div>
); )
}; }

@ -1,11 +1,11 @@
import React from "react"; import React from 'react'
import ReactDOM from "react-dom"; import ReactDOM from 'react-dom'
import App from "./app/App"; import App from './app/App'
// import { Routes } from "./routes"; // import { Routes } from "./routes";
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById('root'),
); )

@ -1,19 +1,19 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const { composePlugins, withNx } = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react') const { withReact } = require('@nrwl/react')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => { module.exports = composePlugins(withNx(), withReact(), config => {
// Update the webpack config as needed here. // Update the webpack config as needed here.
// e.g. `config.plugins.push(new MyPlugin())` // e.g. `config.plugins.push(new MyPlugin())`
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"fs": false, fs: false,
} }
// add externals // add externals
@ -24,10 +24,9 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
config.module.rules.push({ config.module.rules.push({
test: /\.hbs$/, test: /\.hbs$/,
type: 'asset/source' type: 'asset/source',
}) })
// add public path // add public path
config.output.publicPath = '/' config.output.publicPath = '/'
@ -38,16 +37,14 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser',
}), }),
new webpack.DefinePlugin({ new webpack.DefinePlugin({}),
}),
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre',
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
@ -67,7 +64,7 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
extractComments: false, extractComments: false,
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin(),
]; ]
return config; return config
}) })

@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react" import React, {useEffect, useState} from 'react'
import { DocViewer } from "./docviewer" import {DocViewer} from './docviewer'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm' import remarkGfm from 'remark-gfm'
@ -11,13 +11,12 @@ export default function App() {
client.eventEmitter.on('contentsReady', (fileContents: string) => { client.eventEmitter.on('contentsReady', (fileContents: string) => {
setContents(fileContents) setContents(fileContents)
}) })
}, []) }, [])
return ( return (
<> <>
<div className="m-5 p-2"> <div className="m-5 p-2">
<ReactMarkdown children={contents} remarkPlugins={[remarkGfm]}/> <ReactMarkdown children={contents} remarkPlugins={[remarkGfm]} />
</div> </div>
</> </>
) )
} }

@ -6,5 +6,5 @@ ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById('root')
); )

@ -1,8 +1,8 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react') const {withReact} = require('@nrwl/react')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => { module.exports = composePlugins(withNx(), withReact(), (config) => {
@ -12,7 +12,7 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
@ -23,18 +23,16 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}),
new webpack.DefinePlugin({
}), }),
new webpack.DefinePlugin({})
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
@ -48,13 +46,13 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
return config; return config
}) })

@ -1,24 +1,24 @@
import React from "react" import React from 'react'
import { PluginClient } from "@remixproject/plugin" import {PluginClient} from '@remixproject/plugin'
import { Receipt, ThemeType } from "./types" import {Receipt, ThemeType} from './types'
export const AppContext = React.createContext({ export const AppContext = React.createContext({
apiKey: "", apiKey: '',
setAPIKey: (value: string) => { setAPIKey: (value: string) => {
console.log("Set API Key from Context") console.log('Set API Key from Context')
}, },
clientInstance: {} as PluginClient, clientInstance: {} as PluginClient,
receipts: [] as Receipt[], receipts: [] as Receipt[],
setReceipts: (receipts: Receipt[]) => { setReceipts: (receipts: Receipt[]) => {
console.log("Calling Set Receipts") console.log('Calling Set Receipts')
}, },
contracts: [] as string[], contracts: [] as string[],
setContracts: (contracts: string[]) => { setContracts: (contracts: string[]) => {
console.log("Calling Set Contract Names") console.log('Calling Set Contract Names')
}, },
themeType: "dark" as ThemeType, themeType: 'dark' as ThemeType,
setThemeType: (themeType: ThemeType) => { setThemeType: (themeType: ThemeType) => {
console.log("Calling Set Theme Type") console.log('Calling Set Theme Type')
}, }
}) })

@ -1,40 +1,59 @@
import { PluginClient } from '@remixproject/plugin'; import {PluginClient} from '@remixproject/plugin'
import { verify, EtherScanReturn } from './utils/verify'; import {verify, EtherScanReturn} from './utils/verify'
import { getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus } from './utils'; import {getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus} from './utils'
export class RemixClient extends PluginClient { export class RemixClient extends PluginClient {
loaded() {
return this.onload()
}
loaded() { async verify(
return this.onload() apiKey: string,
} contractAddress: string,
contractArguments: string,
async verify (apiKey: string, contractAddress: string, contractArguments: string, contractName: string, compilationResultParam: any, chainRef?: number | string, isProxyContract?: boolean, expectedImplAddress?: string) { contractName: string,
const result = await verify(apiKey, contractAddress, contractArguments, contractName, compilationResultParam, chainRef, isProxyContract, expectedImplAddress, this, compilationResultParam: any,
(value: EtherScanReturn) => {}, (value: string) => {}) chainRef?: number | string,
return result isProxyContract?: boolean,
} expectedImplAddress?: string
) {
const result = await verify(
apiKey,
contractAddress,
contractArguments,
contractName,
compilationResultParam,
chainRef,
isProxyContract,
expectedImplAddress,
this,
(value: EtherScanReturn) => {},
(value: string) => {}
)
return result
}
async receiptStatus(receiptGuid: string, apiKey: string, isProxyContract: boolean) {
try {
const {network, networkId} = await getNetworkName(this)
if (network === 'vm') {
throw new Error('Cannot check the receipt status in the selected network')
}
const etherscanApi = getEtherScanApi(networkId)
let receiptStatus
async receiptStatus (receiptGuid: string, apiKey: string, isProxyContract: boolean) { if (isProxyContract) receiptStatus = await getProxyContractReceiptStatus(receiptGuid, apiKey, etherscanApi)
try { else receiptStatus = await getReceiptStatus(receiptGuid, apiKey, etherscanApi)
const { network, networkId } = await getNetworkName(this) return {
if (network === "vm") { message: receiptStatus.result,
throw new Error("Cannot check the receipt status in the selected network") succeed: receiptStatus.status === '0' ? false : true
} }
const etherscanApi = getEtherScanApi(networkId) } catch (e: any) {
let receiptStatus return {
status: 'error',
if (isProxyContract) receiptStatus = await getProxyContractReceiptStatus(receiptGuid, apiKey, etherscanApi) message: e.message,
else receiptStatus = await getReceiptStatus(receiptGuid, apiKey, etherscanApi) succeed: false
return { }
message: receiptStatus.result,
succeed: receiptStatus.status === '0' ? false : true
}
} catch (e: any){
return {
status: 'error',
message: e.message,
succeed: false
}
}
} }
}
} }

@ -1,22 +1,19 @@
import React, { useState, useEffect, useRef } from "react" import React, {useState, useEffect, useRef} from 'react'
import { import {CompilationFileSources, CompilationResult} from '@remixproject/plugin-api'
CompilationFileSources,
CompilationResult,
} from "@remixproject/plugin-api"
import { RemixClient } from "./RemixPlugin"; import {RemixClient} from './RemixPlugin'
import { createClient } from "@remixproject/plugin-webview"; import {createClient} from '@remixproject/plugin-webview'
import { AppContext } from "./AppContext" import {AppContext} from './AppContext'
import { DisplayRoutes } from "./routes" import {DisplayRoutes} from './routes'
import { useLocalStorage } from "./hooks/useLocalStorage" import {useLocalStorage} from './hooks/useLocalStorage'
import { getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus } from "./utils" import {getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus} from './utils'
import { Receipt, ThemeType } from "./types" import {Receipt, ThemeType} from './types'
import "./App.css" import './App.css'
export const getNewContractNames = (compilationResult: CompilationResult) => { export const getNewContractNames = (compilationResult: CompilationResult) => {
const compiledContracts = compilationResult.contracts const compiledContracts = compilationResult.contracts
@ -31,11 +28,11 @@ export const getNewContractNames = (compilationResult: CompilationResult) => {
} }
const App = () => { const App = () => {
const [apiKey, setAPIKey] = useLocalStorage("apiKey", "") const [apiKey, setAPIKey] = useLocalStorage('apiKey', '')
const [clientInstance, setClientInstance] = useState(undefined as any) const [clientInstance, setClientInstance] = useState(undefined as any)
const [receipts, setReceipts] = useLocalStorage("receipts", []) const [receipts, setReceipts] = useLocalStorage('receipts', [])
const [contracts, setContracts] = useState([] as string[]) const [contracts, setContracts] = useState([] as string[])
const [themeType, setThemeType] = useState("dark" as ThemeType) const [themeType, setThemeType] = useState('dark' as ThemeType)
const timer = useRef(null) const timer = useRef(null)
const clientInstanceRef = useRef(clientInstance) const clientInstanceRef = useRef(clientInstance)
@ -49,26 +46,15 @@ const App = () => {
const loadClient = async () => { const loadClient = async () => {
await client.onload() await client.onload()
setClientInstance(client) setClientInstance(client)
client.on("solidity", client.on('solidity', 'compilationFinished', (fileName: string, source: CompilationFileSources, languageVersion: string, data: CompilationResult) => {
"compilationFinished", const newContractsNames = getNewContractNames(data)
(
fileName: string, const newContractsToSave: string[] = [...contractsRef.current, ...newContractsNames]
source: CompilationFileSources,
languageVersion: string, const uniqueContracts: string[] = [...new Set(newContractsToSave)]
data: CompilationResult
) => { setContracts(uniqueContracts)
const newContractsNames = getNewContractNames(data) })
const newContractsToSave: string[] = [
...contractsRef.current,
...newContractsNames,
]
const uniqueContracts: string[] = [...new Set(newContractsToSave)]
setContracts(uniqueContracts)
}
)
//const currentTheme = await client.call("theme", "currentTheme") //const currentTheme = await client.call("theme", "currentTheme")
//setThemeType(currentTheme.quality) //setThemeType(currentTheme.quality)
@ -82,7 +68,7 @@ const App = () => {
useEffect(() => { useEffect(() => {
let receiptsNotVerified: Receipt[] = receipts.filter((item: Receipt) => { let receiptsNotVerified: Receipt[] = receipts.filter((item: Receipt) => {
return item.status === "Pending in queue" || item.status === "Max rate limit reached" return item.status === 'Pending in queue' || item.status === 'Max rate limit reached'
}) })
if (receiptsNotVerified.length > 0) { if (receiptsNotVerified.length > 0) {
@ -91,51 +77,41 @@ const App = () => {
timer.current = null timer.current = null
} }
timer.current = setInterval(async () => { timer.current = setInterval(async () => {
const { network, networkId } = await getNetworkName(clientInstanceRef.current) const {network, networkId} = await getNetworkName(clientInstanceRef.current)
if (!clientInstanceRef.current) { if (!clientInstanceRef.current) {
return return
} }
if (network === "vm") { if (network === 'vm') {
return return
} }
let newReceipts = receipts let newReceipts = receipts
for (const item of receiptsNotVerified) { for (const item of receiptsNotVerified) {
await new Promise(r => setTimeout(r, 500)) // avoid api rate limit exceed. await new Promise((r) => setTimeout(r, 500)) // avoid api rate limit exceed.
let status let status
if (item.isProxyContract) { if (item.isProxyContract) {
status = await getProxyContractReceiptStatus( status = await getProxyContractReceiptStatus(item.guid, apiKey, getEtherScanApi(networkId))
item.guid,
apiKey,
getEtherScanApi(networkId)
)
if (status.status === '1') { if (status.status === '1') {
status.message = status.result status.message = status.result
status.result = 'Successfully Updated' status.result = 'Successfully Updated'
} }
} else } else status = await getReceiptStatus(item.guid, apiKey, getEtherScanApi(networkId))
status = await getReceiptStatus( if (status.result === 'Pass - Verified' || status.result === 'Already Verified' || status.result === 'Successfully Updated') {
item.guid,
apiKey,
getEtherScanApi(networkId)
)
if (status.result === "Pass - Verified" || status.result === "Already Verified" ||
status.result === "Successfully Updated") {
newReceipts = newReceipts.map((currentReceipt: Receipt) => { newReceipts = newReceipts.map((currentReceipt: Receipt) => {
if (currentReceipt.guid === item.guid) { if (currentReceipt.guid === item.guid) {
let res = { const res = {
...currentReceipt, ...currentReceipt,
status: status.result, status: status.result
} }
if (currentReceipt.isProxyContract) res.message = status.message if (currentReceipt.isProxyContract) res.message = status.message
return res return res
} }
return currentReceipt return currentReceipt
}) })
} }
} }
receiptsNotVerified = newReceipts.filter((item: Receipt) => { receiptsNotVerified = newReceipts.filter((item: Receipt) => {
return item.status === "Pending in queue" || item.status === "Max rate limit reached" return item.status === 'Pending in queue' || item.status === 'Max rate limit reached'
}) })
if (timer.current && receiptsNotVerified.length === 0) { if (timer.current && receiptsNotVerified.length === 0) {
clearInterval(timer.current) clearInterval(timer.current)
@ -157,7 +133,7 @@ const App = () => {
contracts, contracts,
setContracts, setContracts,
themeType, themeType,
setThemeType, setThemeType
}} }}
> >
<DisplayRoutes /> <DisplayRoutes />
@ -165,4 +141,4 @@ const App = () => {
) )
} }
export default App export default App

@ -1,8 +1,8 @@
import React from "react" import React from 'react'
import { NavLink } from "react-router-dom" import {NavLink} from 'react-router-dom'
import { CustomTooltip } from '@remix-ui/helper' import {CustomTooltip} from '@remix-ui/helper'
import { AppContext } from "../AppContext" import {AppContext} from '../AppContext'
interface Props { interface Props {
title?: string title?: string
@ -13,76 +13,61 @@ interface IconProps {
from: string from: string
} }
const HomeIcon: React.FC<IconProps> = ({ from }: IconProps) => { const HomeIcon: React.FC<IconProps> = ({from}: IconProps) => {
return ( return (
<NavLink <NavLink
data-id="home" data-id="home"
to={{ to={{
pathname: "/" pathname: '/'
}} }}
className={({ isActive }) => isActive ? "border border-secondary shadow-none btn p-1 m-0" : "border-0 shadow-none btn p-1 m-0"} className={({isActive}) => (isActive ? 'border border-secondary shadow-none btn p-1 m-0' : 'border-0 shadow-none btn p-1 m-0')}
style={ ({ isActive }) => !isActive ? { width: "1.8rem", filter: "contrast(0.5)"} : {width: "1.8rem"}} style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})}
state={ from } state={from}
> >
<CustomTooltip <CustomTooltip tooltipText="Home" tooltipId="etherscan-nav-home" placement="bottom">
tooltipText='Home'
tooltipId='etherscan-nav-home'
placement='bottom'
>
<i className="fas fa-home"></i> <i className="fas fa-home"></i>
</CustomTooltip> </CustomTooltip>
</NavLink> </NavLink>
) )
} }
const ReceiptsIcon: React.FC<IconProps> = ({ from }: IconProps) => { const ReceiptsIcon: React.FC<IconProps> = ({from}: IconProps) => {
return ( return (
<NavLink <NavLink
data-id="receipts" data-id="receipts"
to={{ to={{
pathname: "/receipts" pathname: '/receipts'
}} }}
className={({ isActive }) => isActive ? "border border-secondary shadow-none btn p-1 m-0" : "border-0 shadow-none btn p-1 m-0"} className={({isActive}) => (isActive ? 'border border-secondary shadow-none btn p-1 m-0' : 'border-0 shadow-none btn p-1 m-0')}
style={ ({ isActive }) => !isActive ? { width: "1.8rem", filter: "contrast(0.5)"} : {width: "1.8rem"}} style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})}
state={ from } state={from}
> >
<CustomTooltip <CustomTooltip tooltipText="Receipts" tooltipId="etherscan-nav-receipts" placement="bottom">
tooltipText='Receipts'
tooltipId='etherscan-nav-receipts'
placement='bottom'
>
<i className="fas fa-receipt"></i> <i className="fas fa-receipt"></i>
</CustomTooltip> </CustomTooltip>
</NavLink> </NavLink>
) )
} }
const SettingsIcon: React.FC<IconProps> = ({ from }: IconProps) => { const SettingsIcon: React.FC<IconProps> = ({from}: IconProps) => {
return ( return (
<NavLink <NavLink
data-id="settings" data-id="settings"
to={{ to={{
pathname: "/settings" pathname: '/settings'
}} }}
className={({ isActive }) => isActive ? "border border-secondary shadow-none btn p-1 m-0" : "border-0 shadow-none btn p-1 m-0"} className={({isActive}) => (isActive ? 'border border-secondary shadow-none btn p-1 m-0' : 'border-0 shadow-none btn p-1 m-0')}
style={ ({ isActive }) => !isActive ? { width: "1.8rem", filter: "contrast(0.5)"} : {width: "1.8rem"}} style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})}
state= {from} state={from}
> >
<CustomTooltip <CustomTooltip tooltipText="Settings" tooltipId="etherscan-nav-settings" placement="bottom">
tooltipText='Settings'
tooltipId='etherscan-nav-settings'
placement='bottom'
>
<i className="fas fa-cog"></i> <i className="fas fa-cog"></i>
</CustomTooltip> </CustomTooltip>
</NavLink> </NavLink>
) )
} }
export const HeaderWithSettings: React.FC<Props> = ({ export const HeaderWithSettings: React.FC<Props> = ({title = '', from}) => {
title = "",
from,
}) => {
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{() => ( {() => (

@ -1,5 +1,5 @@
import React from "react" import React from 'react'
import { CustomTooltip } from '@remix-ui/helper' import {CustomTooltip} from '@remix-ui/helper'
interface Props { interface Props {
text: string text: string
@ -8,35 +8,21 @@ interface Props {
disable?: boolean disable?: boolean
} }
export const SubmitButton: React.FC<Props> = ({ export const SubmitButton: React.FC<Props> = ({text, dataId, isSubmitting = false, disable = true}) => {
text,
dataId,
isSubmitting = false,
disable = true
}) => {
return ( return (
<div> <div>
<button <button data-id={dataId} type="submit" className="btn btn-primary btn-block p-1 text-decoration-none" disabled={disable}>
data-id={dataId}
type="submit"
className="btn btn-primary btn-block p-1 text-decoration-none"
disabled={disable}
>
<CustomTooltip <CustomTooltip
tooltipText={disable ? "Fill in the valid value(s) and select a supported network" : "Click to proceed"} tooltipText={disable ? 'Fill in the valid value(s) and select a supported network' : 'Click to proceed'}
tooltipId={'etherscan-submit-button-'+ dataId} tooltipId={'etherscan-submit-button-' + dataId}
tooltipTextClasses="border bg-light text-dark p-1 pr-3" tooltipTextClasses="border bg-light text-dark p-1 pr-3"
placement='bottom' placement="bottom"
> >
<div> <div>
{!isSubmitting && text} {!isSubmitting && text}
{isSubmitting && ( {isSubmitting && (
<div> <div>
<span <span className="spinner-border spinner-border-sm mr-1" role="status" aria-hidden="true" />
className="spinner-border spinner-border-sm mr-1"
role="status"
aria-hidden="true"
/>
Verifying... Please wait Verifying... Please wait
</div> </div>
)} )}

@ -1,4 +1,4 @@
import { useState } from "react" import {useState} from 'react'
export function useLocalStorage(key: string, initialValue: any) { export function useLocalStorage(key: string, initialValue: any) {
// State to store our value // State to store our value
@ -21,8 +21,7 @@ export function useLocalStorage(key: string, initialValue: any) {
const setValue = (value: any) => { const setValue = (value: any) => {
try { try {
// Allow value to be a function so we have same API as useState // Allow value to be a function so we have same API as useState
const valueToStore = const valueToStore = value instanceof Function ? value(storedValue) : value
value instanceof Function ? value(storedValue) : value
// Save state // Save state
setStoredValue(valueToStore) setStoredValue(valueToStore)
// Save to local storage // Save to local storage

@ -1,17 +1,13 @@
import React, { PropsWithChildren } from "react" import React, {PropsWithChildren} from 'react'
import { HeaderWithSettings } from "../components" import {HeaderWithSettings} from '../components'
interface Props { interface Props {
from: string from: string
title?: string title?: string
} }
export const DefaultLayout: React.FC<PropsWithChildren<Props>> = ({ export const DefaultLayout: React.FC<PropsWithChildren<Props>> = ({children, from, title}) => {
children,
from,
title
}) => {
return ( return (
<div> <div>
<HeaderWithSettings from={from} title={title} /> <HeaderWithSettings from={from} title={title} />

@ -1,13 +1,8 @@
import React from "react" import React from 'react'
import { import {HashRouter as Router, Route, Routes, RouteProps} from 'react-router-dom'
HashRouter as Router,
Route,
Routes,
RouteProps,
} from "react-router-dom"
import { ErrorView, HomeView, ReceiptsView, CaptureKeyView } from "./views" import {ErrorView, HomeView, ReceiptsView, CaptureKeyView} from './views'
import { DefaultLayout } from "./layouts" import {DefaultLayout} from './layouts'
interface Props extends RouteProps { interface Props extends RouteProps {
component: any // TODO: new (props: any) => React.Component component: any // TODO: new (props: any) => React.Component
@ -16,7 +11,7 @@ interface Props extends RouteProps {
export const DisplayRoutes = () => ( export const DisplayRoutes = () => (
<Router> <Router>
<Routes> <Routes>
<Route <Route
path="/" path="/"
element={ element={
@ -25,8 +20,7 @@ export const DisplayRoutes = () => (
</DefaultLayout> </DefaultLayout>
} }
/> />
<Route path="/error" <Route path="/error" element={<ErrorView />} />
element={<ErrorView />} />
<Route <Route
path="/receipts" path="/receipts"
element={ element={

@ -1,75 +1,62 @@
import React, { useState } from "react" import React, {useState} from 'react'
import { Formik, ErrorMessage, Field } from "formik" import {Formik, ErrorMessage, Field} from 'formik'
import { useNavigate, useLocation } from "react-router-dom" import {useNavigate, useLocation} from 'react-router-dom'
import { AppContext } from "../AppContext" import {AppContext} from '../AppContext'
import { SubmitButton } from "../components" import {SubmitButton} from '../components'
export const CaptureKeyView: React.FC = () => { export const CaptureKeyView: React.FC = () => {
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
const [msg, setMsg] = useState("") const [msg, setMsg] = useState('')
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{({ apiKey, clientInstance, setAPIKey }) => { {({apiKey, clientInstance, setAPIKey}) => {
if (!apiKey) setMsg('Please provide a 34-character API key to continue') if (!apiKey) setMsg('Please provide a 34-character API key to continue')
return ( return (
<div> <div>
<Formik <Formik
initialValues={{ apiKey }} initialValues={{apiKey}}
validate={(values) => { validate={(values) => {
const errors = {} as any const errors = {} as any
if (!values.apiKey) { if (!values.apiKey) {
errors.apiKey = "Required" errors.apiKey = 'Required'
} else if (values.apiKey.length !== 34) { } else if (values.apiKey.length !== 34) {
errors.apiKey = "API key should be 34 characters long" errors.apiKey = 'API key should be 34 characters long'
} }
return errors return errors
}} }}
onSubmit={(values) => { onSubmit={(values) => {
const apiKey = values.apiKey const apiKey = values.apiKey
if (apiKey.length === 34) { if (apiKey.length === 34) {
setAPIKey(values.apiKey) setAPIKey(values.apiKey)
navigate((location && location.state ? location.state : '/')) navigate(location && location.state ? location.state : '/')
} }
}} }}
> >
{({ errors, touched, handleSubmit }) => ( {({errors, touched, handleSubmit}) => (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="form-group mb-2"> <div className="form-group mb-2">
<label htmlFor="apikey">API Key</label> <label htmlFor="apikey">API Key</label>
<Field <Field
className={ className={errors.apiKey && touched.apiKey ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm'}
errors.apiKey && touched.apiKey type="password"
? "form-control form-control-sm is-invalid" name="apiKey"
: "form-control form-control-sm" placeholder="e.g. GM1T20XY6JGSAPWKDCYZ7B2FJXKTJRFVGZ"
} />
type="password" <ErrorMessage className="invalid-feedback" name="apiKey" component="div" />
name="apiKey" </div>
placeholder="e.g. GM1T20XY6JGSAPWKDCYZ7B2FJXKTJRFVGZ"
/>
<ErrorMessage
className="invalid-feedback"
name="apiKey"
component="div"
/>
</div>
<div> <div>
<SubmitButton text="Save" dataId="save-api-key" disable={errors && errors.apiKey ? true : false } /> <SubmitButton text="Save" dataId="save-api-key" disable={errors && errors.apiKey ? true : false} />
</div> </div>
</form> </form>
)} )}
</Formik> </Formik>
<div <div data-id="api-key-result" className="text-primary mt-4 text-center" style={{fontSize: '0.8em'}} dangerouslySetInnerHTML={{__html: msg}} />
data-id="api-key-result" </div>
className="text-primary mt-4 text-center"
style={{fontSize: "0.8em"}}
dangerouslySetInnerHTML={{ __html: msg }}
/>
</div>
) )
}} }}
</AppContext.Consumer> </AppContext.Consumer>

@ -1,21 +1,13 @@
import React from "react" import React from 'react'
export const ErrorView: React.FC = () => { export const ErrorView: React.FC = () => {
return ( return (
<div className="d-flex w-100 flex-column align-items-center"> <div className="d-flex w-100 flex-column align-items-center">
<img <img className="pb-4" width="250" src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png" alt="Error page" />
className="pb-4"
width="250"
src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png"
alt="Error page"
/>
<h5>Sorry, something unexpected happened.</h5> <h5>Sorry, something unexpected happened.</h5>
<h5> <h5>
Please raise an issue:{" "} Please raise an issue:{' '}
<a <a className="text-danger" href="https://github.com/ethereum/remix-project/issues">
className="text-danger"
href="https://github.com/ethereum/remix-project/issues"
>
Here Here
</a> </a>
</h5> </h5>

@ -1,20 +1,20 @@
import React from "react" import React from 'react'
import { Navigate } from "react-router-dom" import {Navigate} from 'react-router-dom'
import { AppContext } from "../AppContext" import {AppContext} from '../AppContext'
import { Receipt } from "../types" import {Receipt} from '../types'
import { VerifyView } from "./VerifyView" import {VerifyView} from './VerifyView'
export const HomeView: React.FC = () => { export const HomeView: React.FC = () => {
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{({ apiKey, clientInstance, setReceipts, receipts, contracts }) => { {({apiKey, clientInstance, setReceipts, receipts, contracts}) => {
return !apiKey ? ( return !apiKey ? (
<Navigate <Navigate
to={{ to={{
pathname: "/settings" pathname: '/settings'
}} }}
/> />
) : ( ) : (
@ -28,8 +28,7 @@ export const HomeView: React.FC = () => {
}} }}
/> />
) )
} }}
}
</AppContext.Consumer> </AppContext.Consumer>
) )
} }

@ -1,13 +1,13 @@
import React, { useState } from "react" import React, {useState} from 'react'
import { Formik, ErrorMessage, Field } from "formik" import {Formik, ErrorMessage, Field} from 'formik'
import { getEtherScanApi, getNetworkName, getReceiptStatus, getProxyContractReceiptStatus } from "../utils" import {getEtherScanApi, getNetworkName, getReceiptStatus, getProxyContractReceiptStatus} from '../utils'
import { Receipt } from "../types" import {Receipt} from '../types'
import { AppContext } from "../AppContext" import {AppContext} from '../AppContext'
import { SubmitButton } from "../components" import {SubmitButton} from '../components'
import { Navigate } from "react-router-dom" import {Navigate} from 'react-router-dom'
import { Button } from "react-bootstrap" import {Button} from 'react-bootstrap'
import { CustomTooltip } from '@remix-ui/helper' import {CustomTooltip} from '@remix-ui/helper'
interface FormValues { interface FormValues {
receiptGuid: string receiptGuid: string
@ -17,38 +17,25 @@ export const ReceiptsView: React.FC = () => {
const [results, setResults] = useState({succeed: false, message: ''}) const [results, setResults] = useState({succeed: false, message: ''})
const [isProxyContractReceipt, setIsProxyContractReceipt] = useState(false) const [isProxyContractReceipt, setIsProxyContractReceipt] = useState(false)
const onGetReceiptStatus = async ( const onGetReceiptStatus = async (values: FormValues, clientInstance: any, apiKey: string) => {
values: FormValues,
clientInstance: any,
apiKey: string
) => {
try { try {
const { network, networkId } = await getNetworkName(clientInstance) const {network, networkId} = await getNetworkName(clientInstance)
if (network === "vm") { if (network === 'vm') {
setResults({ setResults({
succeed: false, succeed: false,
message: "Cannot verify in the selected network" message: 'Cannot verify in the selected network'
}) })
return return
} }
const etherscanApi = getEtherScanApi(networkId) const etherscanApi = getEtherScanApi(networkId)
let result let result
if (isProxyContractReceipt) { if (isProxyContractReceipt) {
result = await getProxyContractReceiptStatus( result = await getProxyContractReceiptStatus(values.receiptGuid, apiKey, etherscanApi)
values.receiptGuid,
apiKey,
etherscanApi
)
if (result.status === '1') { if (result.status === '1') {
result.message = result.result result.message = result.result
result.result = 'Successfully Updated' result.result = 'Successfully Updated'
} }
} else } else result = await getReceiptStatus(values.receiptGuid, apiKey, etherscanApi)
result = await getReceiptStatus(
values.receiptGuid,
apiKey,
etherscanApi
)
setResults({ setResults({
succeed: result.status === '1' ? true : false, succeed: result.status === '1' ? true : false,
message: result.result || (result.status === '0' ? 'Verification failed' : result.message) message: result.result || (result.status === '0' ? 'Verification failed' : result.message)
@ -63,46 +50,36 @@ export const ReceiptsView: React.FC = () => {
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{({ apiKey, clientInstance, receipts, setReceipts }) => { {({apiKey, clientInstance, receipts, setReceipts}) => {
return !apiKey ? ( return !apiKey ? (
<Navigate <Navigate
to={{ to={{
pathname: "/settings" pathname: '/settings'
}} }}
/> />
) : ( ) : (
<div> <div>
<Formik <Formik
initialValues={{ receiptGuid: "" }} initialValues={{receiptGuid: ''}}
validate={(values) => { validate={(values) => {
const errors = {} as any const errors = {} as any
if (!values.receiptGuid) { if (!values.receiptGuid) {
errors.receiptGuid = "Required" errors.receiptGuid = 'Required'
} }
return errors return errors
}} }}
onSubmit={(values) => onSubmit={(values) => onGetReceiptStatus(values, clientInstance, apiKey)}
onGetReceiptStatus(values, clientInstance, apiKey)
}
> >
{({ errors, touched, handleSubmit, handleChange }) => ( {({errors, touched, handleSubmit, handleChange}) => (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="form-group mb-2"> <div className="form-group mb-2">
<label htmlFor="receiptGuid">Receipt GUID</label> <label htmlFor="receiptGuid">Receipt GUID</label>
<Field <Field
className={ className={errors.receiptGuid && touched.receiptGuid ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm'}
errors.receiptGuid && touched.receiptGuid
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="text" type="text"
name="receiptGuid" name="receiptGuid"
/> />
<ErrorMessage <ErrorMessage className="invalid-feedback" name="receiptGuid" component="div" />
className="invalid-feedback"
name="receiptGuid"
component="div"
/>
</div> </div>
<div className="d-flex mb-2 custom-control custom-checkbox"> <div className="d-flex mb-2 custom-control custom-checkbox">
@ -115,37 +92,44 @@ export const ReceiptsView: React.FC = () => {
handleChange(e) handleChange(e)
if (e.target.checked) setIsProxyContractReceipt(true) if (e.target.checked) setIsProxyContractReceipt(true)
else setIsProxyContractReceipt(false) else setIsProxyContractReceipt(false)
}} }}
/> />
<label className="form-check-label custom-control-label" htmlFor="isProxyReceipt">It's a proxy contract GUID</label> <label className="form-check-label custom-control-label" htmlFor="isProxyReceipt">
It's a proxy contract GUID
</label>
</div> </div>
<SubmitButton text="Check" disable = {!touched.receiptGuid || (touched.receiptGuid && errors.receiptGuid) ? true : false} /> <SubmitButton text="Check" disable={!touched.receiptGuid || (touched.receiptGuid && errors.receiptGuid) ? true : false} />
</form> </form>
)} )}
</Formik> </Formik>
<div <div
className={results['succeed'] ? "text-success mt-3 text-center" : "text-danger mt-3 text-center"} className={results['succeed'] ? 'text-success mt-3 text-center' : 'text-danger mt-3 text-center'}
dangerouslySetInnerHTML={{ __html: results.message ? results.message : '' }} dangerouslySetInnerHTML={{
__html: results.message ? results.message : ''
}}
/> />
<ReceiptsTable receipts={receipts} /><br/> <ReceiptsTable receipts={receipts} />
<CustomTooltip <br />
tooltipText="Clear the list of receipts" <CustomTooltip tooltipText="Clear the list of receipts" tooltipId="etherscan-clear-receipts" placement="bottom">
tooltipId='etherscan-clear-receipts' <Button
placement='bottom' className="btn-sm"
> onClick={() => {
<Button className="btn-sm" onClick={() => { setReceipts([]) }} >Clear</Button> setReceipts([])
}}
>
Clear
</Button>
</CustomTooltip> </CustomTooltip>
</div> </div>
) )
} }}
}
</AppContext.Consumer> </AppContext.Consumer>
) )
} }
const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => { const ReceiptsTable: React.FC<{receipts: Receipt[]}> = ({receipts}) => {
return ( return (
<div className="table-responsive"> <div className="table-responsive">
<h6>Receipts</h6> <h6>Receipts</h6>
@ -162,20 +146,23 @@ const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => {
receipts.map((item: Receipt, index) => { receipts.map((item: Receipt, index) => {
return ( return (
<tr key={item.guid}> <tr key={item.guid}>
<td className={(item.status === 'Pass - Verified' || item.status === 'Successfully Updated') <td
? 'text-success' : (item.status === 'Pending in queue' className={
? 'text-warning' : (item.status === 'Already Verified' item.status === 'Pass - Verified' || item.status === 'Successfully Updated'
? 'text-info': 'text-secondary'))}> ? 'text-success'
{item.status} : item.status === 'Pending in queue'
{item.status === 'Successfully Updated' && <CustomTooltip ? 'text-warning'
placement={'bottom'} : item.status === 'Already Verified'
tooltipClasses="text-wrap" ? 'text-info'
tooltipId="etherscan-receipt-proxy-status" : 'text-secondary'
tooltipText={item.message}
>
<i style={{ fontSize: 'small' }} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
</CustomTooltip>
} }
>
{item.status}
{item.status === 'Successfully Updated' && (
<CustomTooltip placement={'bottom'} tooltipClasses="text-wrap" tooltipId="etherscan-receipt-proxy-status" tooltipText={item.message}>
<i style={{fontSize: 'small'}} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
</CustomTooltip>
)}
</td> </td>
<td>{item.guid}</td> <td>{item.guid}</td>
</tr> </tr>

@ -1,16 +1,14 @@
import React, { useEffect, useRef, useState } from "react" import React, {useEffect, useRef, useState} from 'react'
import Web3 from 'web3' import Web3 from 'web3'
import { import {PluginClient} from '@remixproject/plugin'
PluginClient, import {CustomTooltip} from '@remix-ui/helper'
} from "@remixproject/plugin" import {Formik, ErrorMessage, Field} from 'formik'
import { CustomTooltip } from '@remix-ui/helper'
import { Formik, ErrorMessage, Field } from "formik"
import { SubmitButton } from "../components" import {SubmitButton} from '../components'
import { Receipt } from "../types" import {Receipt} from '../types'
import { verify } from "../utils/verify" import {verify} from '../utils/verify'
import { etherscanScripts } from "@remix-project/remix-ws-templates" import {etherscanScripts} from '@remix-project/remix-ws-templates'
interface Props { interface Props {
client: PluginClient client: PluginClient
@ -25,15 +23,10 @@ interface FormValues {
expectedImplAddress?: string expectedImplAddress?: string
} }
export const VerifyView: React.FC<Props> = ({ export const VerifyView: React.FC<Props> = ({apiKey, client, contracts, onVerifiedContract}) => {
apiKey, const [results, setResults] = useState('')
client, const [networkName, setNetworkName] = useState('Loading...')
contracts, const [selectedContract, setSelectedContract] = useState('')
onVerifiedContract,
}) => {
const [results, setResults] = useState("")
const [networkName, setNetworkName] = useState("Loading...")
const [selectedContract, setSelectedContract] = useState("")
const [showConstructorArgs, setShowConstructorArgs] = useState(false) const [showConstructorArgs, setShowConstructorArgs] = useState(false)
const [isProxyContract, setIsProxyContract] = useState(false) const [isProxyContract, setIsProxyContract] = useState(false)
const [constructorInputs, setConstructorInputs] = useState([]) const [constructorInputs, setConstructorInputs] = useState([])
@ -41,13 +34,13 @@ export const VerifyView: React.FC<Props> = ({
useEffect(() => { useEffect(() => {
if (client && client.on) { if (client && client.on) {
client.on("blockchain" as any, 'networkStatus', (result) => { client.on('blockchain' as any, 'networkStatus', (result) => {
setNetworkName(`${result.network.name} ${result.network.id !== '-' ? `(Chain id: ${result.network.id})` : '(Not supported)'}`) setNetworkName(`${result.network.name} ${result.network.id !== '-' ? `(Chain id: ${result.network.id})` : '(Not supported)'}`)
}) })
} }
return () => { return () => {
// To fix memory leak // To fix memory leak
if (client && client.off) client.off("blockchain" as any, 'networkStatus') if (client && client.off) client.off('blockchain' as any, 'networkStatus')
} }
}, [client]) }, [client])
@ -56,8 +49,8 @@ export const VerifyView: React.FC<Props> = ({
}, [contracts]) }, [contracts])
const updateConsFields = (contractName) => { const updateConsFields = (contractName) => {
client.call("compilerArtefacts" as any, "getArtefactsByContractName", contractName).then((result) => { client.call('compilerArtefacts' as any, 'getArtefactsByContractName', contractName).then((result) => {
const { artefact } = result const {artefact} = result
if (artefact && artefact.abi && artefact.abi[0] && artefact.abi[0].type && artefact.abi[0].type === 'constructor' && artefact.abi[0].inputs.length > 0) { if (artefact && artefact.abi && artefact.abi[0] && artefact.abi[0].type && artefact.abi[0].type === 'constructor' && artefact.abi[0].inputs.length > 0) {
setConstructorInputs(artefact.abi[0].inputs) setConstructorInputs(artefact.abi[0].inputs)
setShowConstructorArgs(true) setShowConstructorArgs(true)
@ -69,13 +62,10 @@ export const VerifyView: React.FC<Props> = ({
} }
const onVerifyContract = async (values: FormValues) => { const onVerifyContract = async (values: FormValues) => {
const compilationResult = (await client.call( const compilationResult = (await client.call('solidity', 'getCompilationResult')) as any
"solidity",
"getCompilationResult"
)) as any
if (!compilationResult) { if (!compilationResult) {
throw new Error("no compilation result available") throw new Error('no compilation result available')
} }
const constructorValues = [] const constructorValues = []
@ -83,9 +73,9 @@ export const VerifyView: React.FC<Props> = ({
if (key.startsWith('contractArgValue')) constructorValues.push(values[key]) if (key.startsWith('contractArgValue')) constructorValues.push(values[key])
} }
const web3 = new Web3() const web3 = new Web3()
const constructorTypes = constructorInputs.map(e => e.type) const constructorTypes = constructorInputs.map((e) => e.type)
let contractArguments = web3.eth.abi.encodeParameters(constructorTypes, constructorValues) let contractArguments = web3.eth.abi.encodeParameters(constructorTypes, constructorValues)
contractArguments = contractArguments.replace("0x", "") contractArguments = contractArguments.replace('0x', '')
verificationResult.current = await verify( verificationResult.current = await verify(
apiKey, apiKey,
@ -98,7 +88,7 @@ export const VerifyView: React.FC<Props> = ({
values.expectedImplAddress, values.expectedImplAddress,
client, client,
onVerifiedContract, onVerifiedContract,
setResults, setResults
) )
setResults(verificationResult.current['message']) setResults(verificationResult.current['message'])
} }
@ -107,189 +97,147 @@ export const VerifyView: React.FC<Props> = ({
<div> <div>
<Formik <Formik
initialValues={{ initialValues={{
contractName: "", contractName: '',
contractAddress: "" contractAddress: ''
}} }}
validate={(values) => { validate={(values) => {
const errors = {} as any const errors = {} as any
if (!values.contractName) { if (!values.contractName) {
errors.contractName = "Required" errors.contractName = 'Required'
} }
if (!values.contractAddress) { if (!values.contractAddress) {
errors.contractAddress = "Required" errors.contractAddress = 'Required'
} }
if (values.contractAddress.trim() === "" || !values.contractAddress.startsWith('0x') if (values.contractAddress.trim() === '' || !values.contractAddress.startsWith('0x') || values.contractAddress.length !== 42) {
|| values.contractAddress.length !== 42) { errors.contractAddress = 'Please enter a valid contract address'
errors.contractAddress = "Please enter a valid contract address"
} }
return errors return errors
}} }}
onSubmit={(values) => onVerifyContract(values)} onSubmit={(values) => onVerifyContract(values)}
> >
{({ errors, touched, handleSubmit, handleChange, isSubmitting }) => { {({errors, touched, handleSubmit, handleChange, isSubmitting}) => {
return (<form onSubmit={handleSubmit}> return (
<div className="form-group"> <form onSubmit={handleSubmit}>
<label htmlFor="network">Selected Network</label> <div className="form-group">
<CustomTooltip <label htmlFor="network">Selected Network</label>
tooltipText="Network is fetched from 'Deploy and Run Transactions' plugin's ENVIRONMENT field" <CustomTooltip
tooltipId='etherscan-impl-address2' tooltipText="Network is fetched from 'Deploy and Run Transactions' plugin's ENVIRONMENT field"
placement='bottom' tooltipId="etherscan-impl-address2"
> placement="bottom"
>
<Field className="form-control" type="text" name="network" value={networkName} disabled={true} />
</CustomTooltip>
</div>
<div className="form-group">
<label htmlFor="contractName">Contract Name</label>
<Field <Field
className="form-control" as="select"
type="text" className={errors.contractName && touched.contractName && contracts.length ? 'form-control is-invalid' : 'form-control'}
name="network" name="contractName"
value={networkName} onChange={async (e) => {
disabled={true} handleChange(e)
/> setSelectedContract(e.target.value)
</CustomTooltip> updateConsFields(e.target.value)
</div> }}
<div className="form-group"> >
<label htmlFor="contractName">Contract Name</label> <option disabled={true} value="">
<Field {contracts.length ? 'Select a contract' : `--- No compiled contracts ---`}
as="select"
className={
errors.contractName && touched.contractName && contracts.length
? "form-control is-invalid"
: "form-control"
}
name="contractName"
onChange={async (e) => {
handleChange(e)
setSelectedContract(e.target.value)
updateConsFields(e.target.value)
}}
>
<option disabled={true} value="">
{ contracts.length ? 'Select a contract' : `--- No compiled contracts ---` }
</option>
{contracts.map((item) => (
<option key={item} value={item}>
{item}
</option> </option>
))} {contracts.map((item) => (
</Field> <option key={item} value={item}>
<ErrorMessage {item}
className="invalid-feedback" </option>
name="contractName" ))}
component="div" </Field>
/> <ErrorMessage className="invalid-feedback" name="contractName" component="div" />
</div> </div>
<div className={ showConstructorArgs ? 'form-group d-block': 'form-group d-none' } > <div className={showConstructorArgs ? 'form-group d-block' : 'form-group d-none'}>
<label>Constructor Arguments</label> <label>Constructor Arguments</label>
{constructorInputs.map((item, index) => { {constructorInputs.map((item, index) => {
return ( return (
<div className="d-flex"> <div className="d-flex">
<Field <Field className="form-control m-1" type="text" key={`contractArgName${index}`} name={`contractArgName${index}`} value={item.name} disabled={true} />
className="form-control m-1" <CustomTooltip tooltipText={`value of ${item.name}`} tooltipId={`etherscan-constructor-value${index}`} placement="top">
type="text" <Field className="form-control m-1" type="text" key={`contractArgValue${index}`} name={`contractArgValue${index}`} placeholder={item.type} />
key={`contractArgName${index}`}
name={`contractArgName${index}`}
value={item.name}
disabled={true}
/>
<CustomTooltip
tooltipText={`value of ${item.name}`}
tooltipId={`etherscan-constructor-value${index}`}
placement='top'
>
<Field
className="form-control m-1"
type="text"
key={`contractArgValue${index}`}
name={`contractArgValue${index}`}
placeholder={item.type}
/>
</CustomTooltip> </CustomTooltip>
</div> </div>
)} )
)} })}
</div>
<div className="form-group">
<label htmlFor="contractAddress">Contract Address</label>
<Field
className={
errors.contractAddress && touched.contractAddress
? "form-control is-invalid"
: "form-control"
}
type="text"
name="contractAddress"
placeholder="e.g. 0x11b79afc03baf25c631dd70169bb6a3160b2706e"
/>
<ErrorMessage
className="invalid-feedback"
name="contractAddress"
component="div"
/>
<div className="d-flex mb-2 custom-control custom-checkbox">
<Field
className="custom-control-input"
type="checkbox"
name="isProxy"
id="isProxy"
onChange={async (e) => {
handleChange(e)
if (e.target.checked) setIsProxyContract(true)
else setIsProxyContract(false)
}}
/>
<label className="form-check-label custom-control-label" htmlFor="isProxy">It's a proxy contract address</label>
</div> </div>
</div> <div className="form-group">
<div className={ isProxyContract ? 'form-group d-block': 'form-group d-none' }> <label htmlFor="contractAddress">Contract Address</label>
<label htmlFor="expectedImplAddress">Expected Implementation Address</label>
<CustomTooltip
tooltipText='Providing expected implementation address enforces a check to ensure the returned implementation contract address is same as address picked up by the verifier'
tooltipId='etherscan-impl-address'
placement='bottom'
>
<Field <Field
className="form-control" className={errors.contractAddress && touched.contractAddress ? 'form-control is-invalid' : 'form-control'}
type="text" type="text"
name="expectedImplAddress" name="contractAddress"
placeholder="verified implementation contract address" placeholder="e.g. 0x11b79afc03baf25c631dd70169bb6a3160b2706e"
/> />
</CustomTooltip> <ErrorMessage className="invalid-feedback" name="contractAddress" component="div" />
<i style={{ fontSize: 'x-small' }} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i> <div className="d-flex mb-2 custom-control custom-checkbox">
<label> &nbsp;Make sure contract is already verified on Etherscan</label> <Field
</div> className="custom-control-input"
<SubmitButton dataId="verify-contract" text="Verify" type="checkbox"
isSubmitting={isSubmitting} name="isProxy"
disable={ !contracts.length || id="isProxy"
!touched.contractName || onChange={async (e) => {
!touched.contractAddress || handleChange(e)
(touched.contractName && errors.contractName) || if (e.target.checked) setIsProxyContract(true)
(touched.contractAddress && errors.contractAddress) || else setIsProxyContract(false)
(networkName === 'VM (Not supported)') }}
? true />
: false} <label className="form-check-label custom-control-label" htmlFor="isProxy">
/> It's a proxy contract address
<br/> </label>
<CustomTooltip </div>
tooltipText='Generate the required TS scripts to verify a contract on Etherscan' </div>
tooltipId='etherscan-generate-scripts' <div className={isProxyContract ? 'form-group d-block' : 'form-group d-none'}>
placement='bottom' <label htmlFor="expectedImplAddress">Expected Implementation Address</label>
> <CustomTooltip
<button tooltipText="Providing expected implementation address enforces a check to ensure the returned implementation contract address is same as address picked up by the verifier"
type="button" tooltipId="etherscan-impl-address"
className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block" placement="bottom"
onClick={async () => { >
etherscanScripts(client) <Field className="form-control" type="text" name="expectedImplAddress" placeholder="verified implementation contract address" />
}} </CustomTooltip>
<i style={{fontSize: 'x-small'}} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
<label> &nbsp;Make sure contract is already verified on Etherscan</label>
</div>
<SubmitButton
dataId="verify-contract"
text="Verify"
isSubmitting={isSubmitting}
disable={
!contracts.length ||
!touched.contractName ||
!touched.contractAddress ||
(touched.contractName && errors.contractName) ||
(touched.contractAddress && errors.contractAddress) ||
networkName === 'VM (Not supported)'
? true
: false
}
/>
<br />
<CustomTooltip tooltipText="Generate the required TS scripts to verify a contract on Etherscan" tooltipId="etherscan-generate-scripts" placement="bottom">
<button
type="button"
className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block"
onClick={async () => {
etherscanScripts(client)
}}
> >
Generate Verification Scripts Generate Verification Scripts
</button> </button>
</CustomTooltip> </CustomTooltip>
</form>) </form>
)
}} }}
</Formik> </Formik>
<div <div
data-id="verify-result" data-id="verify-result"
className={verificationResult.current['succeed'] ? "text-success mt-4 text-center" : "text-danger mt-4 text-center"} className={verificationResult.current['succeed'] ? 'text-success mt-4 text-center' : 'text-danger mt-4 text-center'}
style={{fontSize: "0.8em"}} style={{fontSize: '0.8em'}}
dangerouslySetInnerHTML={{ __html: results }} dangerouslySetInnerHTML={{__html: results}}
/> />
{/* <div style={{ display: "block", textAlign: "center", marginTop: "1em" }}> {/* <div style={{ display: "block", textAlign: "center", marginTop: "1em" }}>
<Link to="/receipts">View Receipts</Link> <Link to="/receipts">View Receipts</Link>

@ -1,7 +1,11 @@
import { StrictMode } from 'react'; import {StrictMode} from 'react'
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom'
import App from './app/app'
import App from './app/app'; ReactDOM.render(
<StrictMode>
ReactDOM.render(<StrictMode><App /></StrictMode>, document.getElementById('root')); <App />
</StrictMode>,
document.getElementById('root')
)

@ -1,7 +1,7 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const versionData = { const versionData = {
timestamp: Date.now(), timestamp: Date.now(),
@ -15,29 +15,29 @@ module.exports = composePlugins(withNx(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify')
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
@ -47,26 +47,24 @@ module.exports = composePlugins(withNx(), (config) => {
config.output.filename = `[name].plugin-etherscan.${versionData.timestamp}.js` config.output.filename = `[name].plugin-etherscan.${versionData.timestamp}.js`
config.output.chunkFilename = `[name].plugin-etherscan.${versionData.timestamp}.js` config.output.chunkFilename = `[name].plugin-etherscan.${versionData.timestamp}.js`
// add copy & provide plugin // add copy & provide plugin
config.plugins.push( config.plugins.push(
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}) })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer // set minimizer
config.optimization.minimizer = [ config.optimization.minimizer = [
new TerserPlugin({ new TerserPlugin({
@ -76,17 +74,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/
} }
return config; return config
}); })

@ -7,7 +7,7 @@ module.exports = {
globals_path: '', globals_path: '',
test_settings: { test_settings: {
default: { 'default': {
selenium_port: 4444, selenium_port: 4444,
selenium_host: 'localhost', selenium_host: 'localhost',
globals: { globals: {
@ -23,28 +23,29 @@ module.exports = {
exclude: ['dist/apps/remix-ide-e2e/src/tests/runAndDeploy.test.js', 'dist/apps/remix-ide-e2e/src/tests/pluginManager.test.ts'] exclude: ['dist/apps/remix-ide-e2e/src/tests/runAndDeploy.test.js', 'dist/apps/remix-ide-e2e/src/tests/pluginManager.test.ts']
}, },
chrome: { 'chrome': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'chrome', 'browserName': 'chrome',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'goog:chromeOptions': { 'goog:chromeOptions': {
args: ['window-size=2560,1440', args: [
'start-fullscreen', 'window-size=2560,1440',
'--no-sandbox', 'start-fullscreen',
'--headless', '--no-sandbox',
'--verbose', '--headless',
"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36", '--verbose',
'--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
] ]
} }
} }
}, },
chromeDesktop: { 'chromeDesktop': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'chrome', 'browserName': 'chrome',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'goog:chromeOptions': { 'goog:chromeOptions': {
args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox'] args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox']
} }
@ -53,40 +54,33 @@ module.exports = {
'chrome-runAndDeploy': { 'chrome-runAndDeploy': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'chrome', 'browserName': 'chrome',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'goog:chromeOptions': { 'goog:chromeOptions': {
args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox', '--headless', '--verbose'] args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox', '--headless', '--verbose']
} }
} }
}, },
firefoxDesktop: { 'firefoxDesktop': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'firefox', 'browserName': 'firefox',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'moz:firefoxOptions': { 'moz:firefoxOptions': {
args: [ args: ['-width=2560', '-height=1440']
'-width=2560',
'-height=1440'
]
} }
} }
}, },
firefox: { 'firefox': {
desiredCapabilities: { desiredCapabilities: {
browserName: 'firefox', 'browserName': 'firefox',
javascriptEnabled: true, 'javascriptEnabled': true,
acceptSslCerts: true, 'acceptSslCerts': true,
'moz:firefoxOptions': { 'moz:firefoxOptions': {
args: [ args: ['-headless', '-width=2560', '-height=1440']
'-headless',
'-width=2560',
'-height=1440'
]
} }
} }
} }

@ -1,32 +1,43 @@
import React, {useEffect, useState} from 'react'
import React, { useEffect, useState } from 'react' import {RemixPlugin} from './Client'
import { RemixPlugin } from './Client' import {Logger} from './logger'
import { Logger } from './logger' import {filePanelProfile} from '@remixproject/plugin-api'
import { filePanelProfile } from '@remixproject/plugin-api' import {filSystemProfile} from '@remixproject/plugin-api'
import { filSystemProfile } from '@remixproject/plugin-api' import {dGitProfile} from '@remixproject/plugin-api'
import { dGitProfile } from '@remixproject/plugin-api' import {editorProfile} from '@remixproject/plugin-api'
import { editorProfile } from '@remixproject/plugin-api' import {settingsProfile} from '@remixproject/plugin-api'
import { settingsProfile } from '@remixproject/plugin-api' import {networkProfile} from '@remixproject/plugin-api'
import { networkProfile } from '@remixproject/plugin-api' import {udappProfile} from '@remixproject/plugin-api'
import { udappProfile } from '@remixproject/plugin-api' import {compilerProfile} from '@remixproject/plugin-api'
import { compilerProfile } from '@remixproject/plugin-api' import {contentImportProfile} from '@remixproject/plugin-api'
import { contentImportProfile } from '@remixproject/plugin-api' import {windowProfile} from '@remixproject/plugin-api'
import { windowProfile } from '@remixproject/plugin-api' import {pluginManagerProfile} from '@remixproject/plugin-api'
import { pluginManagerProfile } from '@remixproject/plugin-api' import {Profile} from '@remixproject/plugin-utils'
import { Profile } from '@remixproject/plugin-utils'
import './app.css' import './app.css'
const client = new RemixPlugin() const client = new RemixPlugin()
function App () { function App() {
const [payload, setPayload] = useState<string>('') const [payload, setPayload] = useState<string>('')
const [log, setLog] = useState<any>() const [log, setLog] = useState<any>()
const [started, setStarted] = useState<boolean>(false) const [started, setStarted] = useState<boolean>(false)
const [events, setEvents] = useState<any>() const [events, setEvents] = useState<any>()
const [profiles, setProfiles] = useState<Profile[]>([pluginManagerProfile, filePanelProfile, filSystemProfile, dGitProfile, networkProfile, settingsProfile, editorProfile, compilerProfile, udappProfile, contentImportProfile, windowProfile]) const [profiles, setProfiles] = useState<Profile[]>([
pluginManagerProfile,
filePanelProfile,
filSystemProfile,
dGitProfile,
networkProfile,
settingsProfile,
editorProfile,
compilerProfile,
udappProfile,
contentImportProfile,
windowProfile
])
const handleChange = ({ target }: any) => { const handleChange = ({target}: any) => {
setPayload(target.value) setPayload(target.value)
} }
@ -44,7 +55,7 @@ function App () {
const p = await client.call('manager', 'getProfile', name) const p = await client.call('manager', 'getProfile', name)
addProfiles = [...addProfiles, p] addProfiles = [...addProfiles, p]
} }
setProfiles(profiles => [...profiles, ...addProfiles]) setProfiles((profiles) => [...profiles, ...addProfiles])
profiles.map((profile: Profile) => { profiles.map((profile: Profile) => {
if (profile.events) { if (profile.events) {
@ -77,8 +88,10 @@ function App () {
let ob: any = null let ob: any = null
try { try {
ob = JSON.parse(payload) ob = JSON.parse(payload)
if (ob && !Array.isArray(ob)) { ob = [ob] } if (ob && !Array.isArray(ob)) {
} catch (e) { } ob = [ob]
}
} catch (e) {}
const args = ob || [payload] const args = ob || [payload]
setStarted(true) setStarted(true)
setLog('') setLog('')
@ -97,30 +110,41 @@ function App () {
return ( return (
<div className="App container-fluid"> <div className="App container-fluid">
<h5>PLUGIN API TESTER</h5> <h5>PLUGIN API TESTER</h5>
<label id='callStatus'>{started ? <>start</> : <>stop</> }</label><br></br> <label id="callStatus">{started ? <>start</> : <>stop</>}</label>
<br></br>
<label>method results</label> <label>method results</label>
<Logger id='methods' log={log}></Logger> <Logger id="methods" log={log}></Logger>
<label>events</label> <label>events</label>
<Logger id='events' log={events}></Logger> <Logger id="events" log={events}></Logger>
<input <input className="form-control w-100" type="text" id="payload" placeholder="Enter payload here..." value={payload} onChange={handleChange} data-id="payload-input" />
className='form-control w-100'
type="text"
id="payload"
placeholder="Enter payload here..."
value={payload}
onChange={handleChange}
data-id="payload-input"
/>
{profiles.map((profile: Profile) => { {profiles.map((profile: Profile) => {
const methods = profile.methods.map((method: string) => { const methods = profile.methods.map((method: string) => {
return <button data-id={`${profile.name}:${method}`} key={method} className='btn btn-primary btn-sm ml-1 mb-1' onClick={async () => await clientMethod(profile, method)}>{method}</button> return (
<button data-id={`${profile.name}:${method}`} key={method} className="btn btn-primary btn-sm ml-1 mb-1" onClick={async () => await clientMethod(profile, method)}>
{method}
</button>
)
}) })
const events = profile.events ? profile.events.map((event: string) => { const events = profile.events
return <label key={event} className='m-1'>{event}</label> ? profile.events.map((event: string) => {
}) : null return (
return <div key={profile.name} className='small border-bottom'><label className='text-uppercase'>{profile.name}</label><br></br>{methods}<br></br>{events ? <label>EVENTS:</label> : null}{events}</div> <label key={event} className="m-1">
{event}
</label>
)
})
: null
return (
<div key={profile.name} className="small border-bottom">
<label className="text-uppercase">{profile.name}</label>
<br></br>
{methods}
<br></br>
{events ? <label>EVENTS:</label> : null}
{events}
</div>
)
})} })}
</div> </div>
) )
} }

@ -1,9 +1,13 @@
import React from 'react' import React from 'react'
interface loggerProps { interface loggerProps {
log: any, log: any
id: string id: string
} }
export const Logger: React.FC<loggerProps> = (props) => { export const Logger: React.FC<loggerProps> = (props) => {
return (<div id={props.id} className="jumbotron overflow-auto text-break mb-1 p-2">{props.log}</div>) return (
<div id={props.id} className="jumbotron overflow-auto text-break mb-1 p-2">
{props.log}
</div>
)
} }

@ -2,9 +2,9 @@
'use strict' 'use strict'
chrome.browserAction.onClicked.addListener(function (tab) { chrome.browserAction.onClicked.addListener(function (tab) {
chrome.storage.sync.set({ 'chrome-app-sync': true }) chrome.storage.sync.set({'chrome-app-sync': true})
chrome.tabs.create({ 'url': chrome.extension.getURL('index.html') }, function (tab) { chrome.tabs.create({url: chrome.extension.getURL('index.html')}, function (tab) {
// tab opened // tab opened
}) })
}) })

@ -1,9 +1,9 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React from 'react'
import { AbstractPanel } from './panel' import {AbstractPanel} from './panel'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { RemixPluginPanel } from '@remix-ui/panel' import {RemixPluginPanel} from '@remix-ui/panel'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
const profile = { const profile = {
name: 'hiddenPanel', name: 'hiddenPanel',
@ -16,35 +16,37 @@ const profile = {
export class HiddenPanel extends AbstractPanel { export class HiddenPanel extends AbstractPanel {
el: HTMLElement el: HTMLElement
dispatch: React.Dispatch<any> = () => {} dispatch: React.Dispatch<any> = () => {}
constructor () { constructor() {
super(profile) super(profile)
this.el = document.createElement('div') this.el = document.createElement('div')
this.el.setAttribute('class', 'pluginsContainer') this.el.setAttribute('class', 'pluginsContainer')
} }
addView (profile: any, view: any): void { addView(profile: any, view: any): void {
super.removeView(profile) super.removeView(profile)
super.addView(profile, view) super.addView(profile, view)
this.renderComponent() this.renderComponent()
} }
updateComponent (state: any) { updateComponent(state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins}/> return <RemixPluginPanel header={<></>} plugins={state.plugins} />
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
} }
render() { render() {
return ( return (
<div className='pluginsContainer'><PluginViewWrapper plugin={this} /></div> <div className="pluginsContainer">
); <PluginViewWrapper plugin={this} />
</div>
)
} }
renderComponent () { renderComponent() {
this.dispatch({ this.dispatch({
plugins: this.plugins, plugins: this.plugins
}) })
} }
} }

@ -1,8 +1,8 @@
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { AbstractPanel } from './panel' import {AbstractPanel} from './panel'
import { RemixPluginPanel } from '@remix-ui/panel' import {RemixPluginPanel} from '@remix-ui/panel'
import packageJson from '../../../../../package.json' import packageJson from '../../../../../package.json'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
const profile = { const profile = {
name: 'mainPanel', name: 'mainPanel',
@ -15,7 +15,7 @@ const profile = {
export class MainPanel extends AbstractPanel { export class MainPanel extends AbstractPanel {
element: HTMLDivElement element: HTMLDivElement
dispatch: React.Dispatch<any> = () => {} dispatch: React.Dispatch<any> = () => {}
constructor (config) { constructor(config) {
super(profile) super(profile)
this.element = document.createElement('div') this.element = document.createElement('div')
this.element.setAttribute('data-id', 'mainPanelPluginsContainer') this.element.setAttribute('data-id', 'mainPanelPluginsContainer')
@ -23,46 +23,50 @@ export class MainPanel extends AbstractPanel {
// this.config = config // this.config = config
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
} }
onActivation () { onActivation() {
this.renderComponent() this.renderComponent()
} }
focus (name) { focus(name) {
this.emit('focusChanged', name) this.emit('focusChanged', name)
super.focus(name) super.focus(name)
this.renderComponent() this.renderComponent()
} }
addView (profile, view) { addView(profile, view) {
super.addView(profile, view) super.addView(profile, view)
this.renderComponent() this.renderComponent()
} }
removeView (profile) { removeView(profile) {
super.removeView(profile) super.removeView(profile)
this.renderComponent() this.renderComponent()
} }
async showContent (name) { async showContent(name) {
super.showContent(name) super.showContent(name)
this.renderComponent() this.renderComponent()
} }
renderComponent () { renderComponent() {
this.dispatch({ this.dispatch({
plugins: this.plugins plugins: this.plugins
}) })
} }
render() { render() {
return <div style={{height: '100%', width: '100%'}} data-id='mainPanelPluginsContainer'><PluginViewWrapper plugin={this} /></div> return (
<div style={{height: '100%', width: '100%'}} data-id="mainPanelPluginsContainer">
<PluginViewWrapper plugin={this} />
</div>
)
} }
updateComponent (state: any) { updateComponent(state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins}/> return <RemixPluginPanel header={<></>} plugins={state.plugins} />
} }
} }

@ -1,16 +1,15 @@
import { RemixApp } from '@remix-ui/app' import {RemixApp} from '@remix-ui/app'
import React, { useEffect, useRef, useState } from 'react' import React, {useEffect, useRef, useState} from 'react'
import { render } from 'react-dom' import {render} from 'react-dom'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { fileSystem, fileSystems } from '../files/fileSystem' import {fileSystem, fileSystems} from '../files/fileSystem'
import { indexedDBFileSystem } from '../files/filesystems/indexedDB' import {indexedDBFileSystem} from '../files/filesystems/indexedDB'
import { localStorageFS } from '../files/filesystems/localStorage' import {localStorageFS} from '../files/filesystems/localStorage'
import { fileSystemUtility, migrationTestData } from '../files/filesystems/fileSystemUtility' import {fileSystemUtility, migrationTestData} from '../files/filesystems/fileSystemUtility'
import './styles/preload.css' import './styles/preload.css'
const _paq = window._paq = window._paq || [] const _paq = (window._paq = window._paq || [])
export const Preload = () => { export const Preload = () => {
const [supported, setSupported] = useState<boolean>(true) const [supported, setSupported] = useState<boolean>(true)
const [error, setError] = useState<boolean>(false) const [error, setError] = useState<boolean>(false)
const [showDownloader, setShowDownloader] = useState<boolean>(false) const [showDownloader, setShowDownloader] = useState<boolean>(false)
@ -18,26 +17,34 @@ export const Preload = () => {
const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem()) const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem())
const localStorageFileSystem = useRef<fileSystem>(new localStorageFS()) const localStorageFileSystem = useRef<fileSystem>(new localStorageFS())
// url parameters to e2e test the fallbacks and error warnings // url parameters to e2e test the fallbacks and error warnings
const testmigrationFallback = useRef<boolean>(window.location.hash.includes('e2e_testmigration_fallback=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:') const testmigrationFallback = useRef<boolean>(
const testmigrationResult = useRef<boolean>(window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:') window.location.hash.includes('e2e_testmigration_fallback=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:'
const testBlockStorage = useRef<boolean>(window.location.hash.includes('e2e_testblock_storage=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:') )
const testmigrationResult = useRef<boolean>(
window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:'
)
const testBlockStorage = useRef<boolean>(
window.location.hash.includes('e2e_testblock_storage=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:'
)
function loadAppComponent() { function loadAppComponent() {
import('../../app').then((AppComponent) => { import('../../app')
const appComponent = new AppComponent.default() .then((AppComponent) => {
appComponent.run().then(() => { const appComponent = new AppComponent.default()
render( appComponent.run().then(() => {
<> render(
<RemixApp app={appComponent} /> <>
</>, <RemixApp app={appComponent} />
document.getElementById('root') </>,
) document.getElementById('root')
)
})
})
.catch((err) => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.error('Error loading Remix:', err)
setError(true)
}) })
}).catch(err => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.error('Error loading Remix:', err)
setError(true)
})
} }
const downloadBackup = async () => { const downloadBackup = async () => {
@ -51,12 +58,15 @@ export const Preload = () => {
setShowDownloader(false) setShowDownloader(false)
const fsUtility = new fileSystemUtility() const fsUtility = new fileSystemUtility()
const migrationResult = await fsUtility.migrate(localStorageFileSystem.current, remixIndexedDB.current) const migrationResult = await fsUtility.migrate(localStorageFileSystem.current, remixIndexedDB.current)
_paq.push(['trackEvent', 'Migrate', 'result', migrationResult?'success' : 'fail']) _paq.push(['trackEvent', 'Migrate', 'result', migrationResult ? 'success' : 'fail'])
await setFileSystems() await setFileSystems()
} }
const setFileSystems = async() => { const setFileSystems = async () => {
const fsLoaded = await remixFileSystems.current.setFileSystem([(testmigrationFallback.current || testBlockStorage.current)? null: remixIndexedDB.current, testBlockStorage.current? null:localStorageFileSystem.current]) const fsLoaded = await remixFileSystems.current.setFileSystem([
testmigrationFallback.current || testBlockStorage.current ? null : remixIndexedDB.current,
testBlockStorage.current ? null : localStorageFileSystem.current
])
if (fsLoaded) { if (fsLoaded) {
console.log(fsLoaded.name + ' activated') console.log(fsLoaded.name + ' activated')
_paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name]) _paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name])
@ -67,7 +77,7 @@ export const Preload = () => {
} }
} }
const testmigration = async() => { const testmigration = async () => {
if (testmigrationResult.current) { if (testmigrationResult.current) {
const fsUtility = new fileSystemUtility() const fsUtility = new fileSystemUtility()
fsUtility.populateWorkspace(migrationTestData, remixFileSystems.current.fileSystems['localstorage'].fs) fsUtility.populateWorkspace(migrationTestData, remixFileSystems.current.fileSystems['localstorage'].fs)
@ -76,69 +86,89 @@ export const Preload = () => {
useEffect(() => { useEffect(() => {
async function loadStorage() { async function loadStorage() {
await remixFileSystems.current.addFileSystem(remixIndexedDB.current) || _paq.push(['trackEvent', 'Storage', 'error', 'indexedDB not supported']) ;(await remixFileSystems.current.addFileSystem(remixIndexedDB.current)) || _paq.push(['trackEvent', 'Storage', 'error', 'indexedDB not supported'])
await remixFileSystems.current.addFileSystem(localStorageFileSystem.current) || _paq.push(['trackEvent', 'Storage', 'error', 'localstorage not supported']) ;(await remixFileSystems.current.addFileSystem(localStorageFileSystem.current)) || _paq.push(['trackEvent', 'Storage', 'error', 'localstorage not supported'])
await testmigration() await testmigration()
remixIndexedDB.current.loaded && await remixIndexedDB.current.checkWorkspaces() remixIndexedDB.current.loaded && (await remixIndexedDB.current.checkWorkspaces())
localStorageFileSystem.current.loaded && await localStorageFileSystem.current.checkWorkspaces() localStorageFileSystem.current.loaded && (await localStorageFileSystem.current.checkWorkspaces())
remixIndexedDB.current.loaded && ( (remixIndexedDB.current.hasWorkSpaces || !localStorageFileSystem.current.hasWorkSpaces)? await setFileSystems():setShowDownloader(true)) remixIndexedDB.current.loaded && (remixIndexedDB.current.hasWorkSpaces || !localStorageFileSystem.current.hasWorkSpaces ? await setFileSystems() : setShowDownloader(true))
!remixIndexedDB.current.loaded && await setFileSystems() !remixIndexedDB.current.loaded && (await setFileSystems())
} }
loadStorage() loadStorage()
}, []) }, [])
return <> return (
<div className='preload-container'> <>
<div className='preload-logo pb-4'> <div className="preload-container">
{logo} <div className="preload-logo pb-4">
<div className="info-secondary splash"> {logo}
REMIX IDE <div className="info-secondary splash">
<br /> REMIX IDE
<span className='version'> v{packageJson.version}</span> <br />
<span className="version"> v{packageJson.version}</span>
</div>
</div> </div>
</div> {!supported ? (
{!supported ? <div className="preload-info-container alert alert-warning">
<div className='preload-info-container alert alert-warning'> Your browser does not support any of the filesystems required by Remix. Either change the settings in your browser or use a supported browser.
Your browser does not support any of the filesystems required by Remix.
Either change the settings in your browser or use a supported browser.
</div> : null}
{error ?
<div className='preload-info-container alert alert-danger text-left'>
An unknown error has occurred while loading the application.<br></br>
Doing a hard refresh might fix this issue:<br></br>
<div className='pt-2'>
Windows:<br></br>
- Chrome: CTRL + F5 or CTRL + Reload Button<br></br>
- Firefox: CTRL + SHIFT + R or CTRL + F5<br></br>
</div> </div>
<div className='pt-2'> ) : null}
MacOS:<br></br> {error ? (
- Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button<br></br> <div className="preload-info-container alert alert-danger text-left">
An unknown error has occurred while loading the application.
<br></br>
Doing a hard refresh might fix this issue:<br></br>
<div className="pt-2">
Windows:<br></br>- Chrome: CTRL + F5 or CTRL + Reload Button
<br></br>- Firefox: CTRL + SHIFT + R or CTRL + F5<br></br>
</div>
<div className="pt-2">
MacOS:<br></br>- Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button<br></br>
</div>
<div className="pt-2">
Linux:<br></br>- Chrome & FireFox: CTRL + SHIFT + R<br></br>
</div>
</div> </div>
<div className='pt-2'> ) : null}
Linux:<br></br> {showDownloader ? (
- Chrome & FireFox: CTRL + SHIFT + R<br></br> <div className="preload-info-container alert alert-info">
This app will be updated now. Please download a backup of your files now to make sure you don't lose your work.
<br></br>
You don't need to do anything else, your files will be available when the app loads.
<div
onClick={async () => {
await downloadBackup()
}}
data-id="downloadbackup-btn"
className="btn btn-primary mt-1"
>
download backup
</div>
<div
onClick={async () => {
await migrateAndLoad()
}}
data-id="skipbackup-btn"
className="btn btn-primary mt-1"
>
skip backup
</div>
</div> </div>
</div> : null} ) : null}
{showDownloader ? {supported && !error && !showDownloader ? (
<div className='preload-info-container alert alert-info'> <div>
This app will be updated now. Please download a backup of your files now to make sure you don't lose your work. <i className="fas fa-spinner fa-spin fa-2x"></i>
<br></br> </div>
You don't need to do anything else, your files will be available when the app loads. ) : null}
<div onClick={async () => { await downloadBackup() }} data-id='downloadbackup-btn' className='btn btn-primary mt-1'>download backup</div> </div>
<div onClick={async () => { await migrateAndLoad() }} data-id='skipbackup-btn' className='btn btn-primary mt-1'>skip backup</div> </>
</div> : null} )
{(supported && !error && !showDownloader) ?
<div>
<i className="fas fa-spinner fa-spin fa-2x"></i>
</div> : null}
</div>
</>
} }
const logo = (
const logo = <svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100"> <svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100">
<path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z" /> <path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z" />
<path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z" /> <path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z" />
<path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z" /> <path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z" />
</svg> </svg>
)

@ -1,10 +1,10 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React from 'react'
import { AbstractPanel } from './panel' import {AbstractPanel} from './panel'
import { RemixPluginPanel } from '@remix-ui/panel' import {RemixPluginPanel} from '@remix-ui/panel'
import packageJson from '../../../../../package.json' import packageJson from '../../../../../package.json'
import { RemixUIPanelHeader } from '@remix-ui/panel' import {RemixUIPanelHeader} from '@remix-ui/panel'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
// const csjs = require('csjs-inject') // const csjs = require('csjs-inject')
const sidePanel = { const sidePanel = {
@ -79,14 +79,17 @@ export class SidePanel extends AbstractPanel {
this.renderComponent() this.renderComponent()
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
} }
render() { render() {
return ( return (
<section className='panel plugin-manager'> <PluginViewWrapper plugin={this} /></section> <section className="panel plugin-manager">
); {' '}
<PluginViewWrapper plugin={this} />
</section>
)
} }
updateComponent(state: any) { updateComponent(state: any) {

@ -1,11 +1,11 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React from 'react'
import packageJson from '../../../../../package.json' import packageJson from '../../../../../package.json'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { EventEmitter } from 'events' import {EventEmitter} from 'events'
import { IconRecord, RemixUiVerticalIconsPanel } from '@remix-ui/vertical-icons-panel' import {IconRecord, RemixUiVerticalIconsPanel} from '@remix-ui/vertical-icons-panel'
import { Profile } from '@remixproject/plugin-utils' import {Profile} from '@remixproject/plugin-utils'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
const profile = { const profile = {
name: 'menuicons', name: 'menuicons',
@ -21,44 +21,51 @@ export class VerticalIcons extends Plugin {
htmlElement: HTMLDivElement htmlElement: HTMLDivElement
icons: Record<string, IconRecord> = {} icons: Record<string, IconRecord> = {}
dispatch: React.Dispatch<any> = () => {} dispatch: React.Dispatch<any> = () => {}
constructor () { constructor() {
super(profile) super(profile)
this.events = new EventEmitter() this.events = new EventEmitter()
this.htmlElement = document.createElement('div') this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'icon-panel') this.htmlElement.setAttribute('id', 'icon-panel')
} }
renderComponent () { renderComponent() {
const fixedOrder = ['filePanel', 'search', 'solidity','udapp', 'debugger', 'solidityStaticAnalysis', 'solidityUnitTesting', 'pluginManager'] const fixedOrder = ['filePanel', 'search', 'solidity', 'udapp', 'debugger', 'solidityStaticAnalysis', 'solidityUnitTesting', 'pluginManager']
const divived = Object.values(this.icons).map((value) => { return { const divived = Object.values(this.icons)
...value, .map((value) => {
isRequired: fixedOrder.indexOf(value.profile.name) > -1 return {
}}).sort((a,b) => { ...value,
return a.timestamp - b.timestamp isRequired: fixedOrder.indexOf(value.profile.name) > -1
}) }
})
.sort((a, b) => {
return a.timestamp - b.timestamp
})
const required = divived.filter((value) => value.isRequired).sort((a,b) => { const required = divived
return fixedOrder.indexOf(a.profile.name) - fixedOrder.indexOf(b.profile.name) .filter((value) => value.isRequired)
}) .sort((a, b) => {
return fixedOrder.indexOf(a.profile.name) - fixedOrder.indexOf(b.profile.name)
})
const sorted: IconRecord[] = [ const sorted: IconRecord[] = [
...required, ...required,
...divived.filter((value) => { return !value.isRequired }) ...divived.filter((value) => {
return !value.isRequired
})
] ]
this.dispatch({ this.dispatch({
verticalIconsPlugin: this, verticalIconsPlugin: this,
icons: sorted icons: sorted
}) })
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
} }
onActivation () { onActivation() {
this.renderComponent() this.renderComponent()
this.on('sidePanel', 'focusChanged', (name: string) => { this.on('sidePanel', 'focusChanged', (name: string) => {
Object.keys(this.icons).map((o) => { Object.keys(this.icons).map((o) => {
@ -69,7 +76,7 @@ export class VerticalIcons extends Plugin {
}) })
} }
async linkContent (profile: Profile) { async linkContent(profile: Profile) {
if (!profile.icon) return if (!profile.icon) return
if (!profile.kind) profile.kind = 'none' if (!profile.kind) profile.kind = 'none'
this.icons[profile.name] = { this.icons[profile.name] = {
@ -81,7 +88,7 @@ export class VerticalIcons extends Plugin {
this.renderComponent() this.renderComponent()
} }
unlinkContent (profile: Profile) { unlinkContent(profile: Profile) {
delete this.icons[profile.name] delete this.icons[profile.name]
this.renderComponent() this.renderComponent()
} }
@ -95,7 +102,7 @@ export class VerticalIcons extends Plugin {
* Set an icon as active * Set an icon as active
* @param {string} name Name of profile of the module to activate * @param {string} name Name of profile of the module to activate
*/ */
select (name: string) { select(name: string) {
// TODO: Only keep `this.emit` (issue#2210) // TODO: Only keep `this.emit` (issue#2210)
this.emit('showContent', name) this.emit('showContent', name)
this.events.emit('showContent', name) this.events.emit('showContent', name)
@ -105,22 +112,21 @@ export class VerticalIcons extends Plugin {
* Toggles the side panel for plugin * Toggles the side panel for plugin
* @param {string} name Name of profile of the module to activate * @param {string} name Name of profile of the module to activate
*/ */
toggle (name: string) { toggle(name: string) {
// TODO: Only keep `this.emit` (issue#2210) // TODO: Only keep `this.emit` (issue#2210)
this.emit('toggleContent', name) this.emit('toggleContent', name)
this.events.emit('toggleContent', name) this.events.emit('toggleContent', name)
} }
updateComponent(state: any){ updateComponent(state: any) {
return <RemixUiVerticalIconsPanel return <RemixUiVerticalIconsPanel verticalIconsPlugin={state.verticalIconsPlugin} icons={state.icons} />
verticalIconsPlugin={state.verticalIconsPlugin}
icons={state.icons}
/>
} }
render() { render() {
return ( return (
<div id='icon-panel'><PluginViewWrapper plugin={this} /></div> <div id="icon-panel">
); <PluginViewWrapper plugin={this} />
</div>
)
} }
} }

@ -1,9 +1,9 @@
import React from 'react' import React from 'react'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { customAction } from '@remixproject/plugin-api' import {customAction} from '@remixproject/plugin-api'
import { concatSourceFiles, getDependencyGraph, normalizeContractPath } from '@remix-ui/solidity-compiler' import {concatSourceFiles, getDependencyGraph, normalizeContractPath} from '@remix-ui/solidity-compiler'
const _paq = window._paq = window._paq || [] const _paq = (window._paq = window._paq || [])
const profile = { const profile = {
name: 'contractflattener', name: 'contractflattener',
@ -11,11 +11,10 @@ const profile = {
description: 'Flatten solidity contracts', description: 'Flatten solidity contracts',
methods: ['flattenAContract', 'flattenContract'], methods: ['flattenAContract', 'flattenContract'],
events: [], events: [],
maintainedBy: 'Remix', maintainedBy: 'Remix'
} }
export class ContractFlattener extends Plugin { export class ContractFlattener extends Plugin {
triggerFlattenContract: boolean = false triggerFlattenContract: boolean = false
constructor() { constructor() {
super(profile) super(profile)
@ -23,13 +22,13 @@ export class ContractFlattener extends Plugin {
onActivation(): void { onActivation(): void {
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => { this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => {
if(data.sources && Object.keys(data.sources).length > 1) { if (data.sources && Object.keys(data.sources).length > 1) {
if(this.triggerFlattenContract) { if (this.triggerFlattenContract) {
this.triggerFlattenContract = false this.triggerFlattenContract = false
await this.flattenContract(source, file, data) await this.flattenContract(source, file, data)
} }
} }
}) })
_paq.push(['trackEvent', 'plugin', 'activated', 'contractFlattener']) _paq.push(['trackEvent', 'plugin', 'activated', 'contractFlattener'])
} }
@ -48,8 +47,7 @@ export class ContractFlattener extends Plugin {
* Takes the flattened result, writes it to a file and returns the result. * Takes the flattened result, writes it to a file and returns the result.
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
async flattenContract (source: { sources: any, target: string }, async flattenContract(source: {sources: any; target: string}, filePath: string, data: {contracts: any; sources: any}): Promise<string> {
filePath: string, data: { contracts: any, sources: any }): Promise<string> {
const appendage = '_flattened.sol' const appendage = '_flattened.sol'
const normalized = normalizeContractPath(filePath) const normalized = normalizeContractPath(filePath)
const path = `${normalized[normalized.length - 2]}${appendage}` const path = `${normalized[normalized.length - 2]}${appendage}`
@ -58,17 +56,15 @@ export class ContractFlattener extends Plugin {
let sorted let sorted
let result let result
let sources let sources
try{ try {
dependencyGraph = getDependencyGraph(ast, filePath) dependencyGraph = getDependencyGraph(ast, filePath)
sorted = dependencyGraph.isEmpty() sorted = dependencyGraph.isEmpty() ? [filePath] : dependencyGraph.sort().reverse()
? [filePath]
: dependencyGraph.sort().reverse()
sources = source.sources sources = source.sources
result = concatSourceFiles(sorted, sources) result = concatSourceFiles(sorted, sources)
}catch(err){ } catch (err) {
console.warn(err) console.warn(err)
} }
await this.call('fileManager', 'writeFile', path , result) await this.call('fileManager', 'writeFile', path, result)
_paq.push(['trackEvent', 'plugin', 'contractFlattener', 'flattenAContract']) _paq.push(['trackEvent', 'plugin', 'contractFlattener', 'flattenAContract'])
// clean up memory references & return result // clean up memory references & return result
sorted = null sorted = null
@ -76,4 +72,4 @@ export class ContractFlattener extends Plugin {
dependencyGraph = null dependencyGraph = null
return result return result
} }
} }

@ -1,11 +1,11 @@
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { LibraryProfile, MethodApi, StatusEvents } from '@remixproject/plugin-utils' import {LibraryProfile, MethodApi, StatusEvents} from '@remixproject/plugin-utils'
import { AppModal } from '@remix-ui/app' import {AppModal} from '@remix-ui/app'
import { AlertModal } from '@remix-ui/app' import {AlertModal} from '@remix-ui/app'
import { dispatchModalInterface } from '@remix-ui/app' import {dispatchModalInterface} from '@remix-ui/app'
interface INotificationApi { interface INotificationApi {
events: StatusEvents, events: StatusEvents
methods: { methods: {
modal: (args: AppModal) => void modal: (args: AppModal) => void
alert: (args: AlertModal) => void alert: (args: AlertModal) => void
@ -13,7 +13,7 @@ interface INotificationApi {
} }
} }
const profile:LibraryProfile<INotificationApi> = { const profile: LibraryProfile<INotificationApi> = {
name: 'notification', name: 'notification',
displayName: 'Notification', displayName: 'Notification',
description: 'Displays notifications', description: 'Displays notifications',
@ -22,23 +22,23 @@ const profile:LibraryProfile<INotificationApi> = {
export class NotificationPlugin extends Plugin implements MethodApi<INotificationApi> { export class NotificationPlugin extends Plugin implements MethodApi<INotificationApi> {
dispatcher: dispatchModalInterface dispatcher: dispatchModalInterface
constructor () { constructor() {
super(profile) super(profile)
} }
setDispatcher (dispatcher: dispatchModalInterface) { setDispatcher(dispatcher: dispatchModalInterface) {
this.dispatcher = dispatcher this.dispatcher = dispatcher
} }
async modal (args: AppModal) { async modal(args: AppModal) {
return this.dispatcher.modal(args) return this.dispatcher.modal(args)
} }
async alert (args: AlertModal) { async alert(args: AlertModal) {
return this.dispatcher.alert(args) return this.dispatcher.alert(args)
} }
async toast (message: string | JSX.Element) { async toast(message: string | JSX.Element) {
this.dispatcher.toast(message) this.dispatcher.toast(message)
} }
} }

@ -1,63 +1,108 @@
'use strict' 'use strict'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { sourceMappingDecoder } from '@remix-project/remix-debug' import {sourceMappingDecoder} from '@remix-project/remix-debug'
import { CompilerAbstract } from '@remix-project/remix-solidity' import {CompilerAbstract} from '@remix-project/remix-solidity'
import { CompilationResult } from '@remix-project/remix-solidity' import {CompilationResult} from '@remix-project/remix-solidity'
import CodeParserGasService from './services/code-parser-gas-service' import CodeParserGasService from './services/code-parser-gas-service'
import CodeParserCompiler from './services/code-parser-compiler' import CodeParserCompiler from './services/code-parser-compiler'
import CodeParserAntlrService from './services/code-parser-antlr-service' import CodeParserAntlrService from './services/code-parser-antlr-service'
import CodeParserImports, { CodeParserImportsData } from './services/code-parser-imports' import CodeParserImports, {CodeParserImportsData} from './services/code-parser-imports'
import React from 'react' import React from 'react'
import { Profile } from '@remixproject/plugin-utils' import {Profile} from '@remixproject/plugin-utils'
import { ContractDefinitionAstNode, EventDefinitionAstNode, FunctionCallAstNode, FunctionDefinitionAstNode, IdentifierAstNode, ImportDirectiveAstNode, ModifierDefinitionAstNode, SourceUnitAstNode, StructDefinitionAstNode, VariableDeclarationAstNode } from '@remix-project/remix-analyzer' import {
import { lastCompilationResult, RemixApi } from '@remixproject/plugin-api' ContractDefinitionAstNode,
import { antlr } from './types' EventDefinitionAstNode,
import { ParseResult } from './types/antlr-types' FunctionCallAstNode,
FunctionDefinitionAstNode,
IdentifierAstNode,
ImportDirectiveAstNode,
ModifierDefinitionAstNode,
SourceUnitAstNode,
StructDefinitionAstNode,
VariableDeclarationAstNode
} from '@remix-project/remix-analyzer'
import {lastCompilationResult, RemixApi} from '@remixproject/plugin-api'
import {antlr} from './types'
import {ParseResult} from './types/antlr-types'
const profile: Profile = { const profile: Profile = {
name: 'codeParser', name: 'codeParser',
methods: ['nodesAtPosition', 'getContractNodes', 'getCurrentFileNodes', 'getLineColumnOfNode', 'getLineColumnOfPosition', 'getFunctionParamaters', 'getDeclaration', 'getFunctionReturnParameters', 'getVariableDeclaration', 'getNodeDocumentation', 'getNodeLink', 'listAstNodes', 'getANTLRBlockAtPosition', 'getLastNodeInLine', 'resolveImports', 'parseSolidity', 'getNodesWithScope', 'getNodesWithName', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'getGasEstimates', 'getImports'], methods: [
'nodesAtPosition',
'getContractNodes',
'getCurrentFileNodes',
'getLineColumnOfNode',
'getLineColumnOfPosition',
'getFunctionParamaters',
'getDeclaration',
'getFunctionReturnParameters',
'getVariableDeclaration',
'getNodeDocumentation',
'getNodeLink',
'listAstNodes',
'getANTLRBlockAtPosition',
'getLastNodeInLine',
'resolveImports',
'parseSolidity',
'getNodesWithScope',
'getNodesWithName',
'getNodes',
'compile',
'getNodeById',
'getLastCompilationResult',
'positionOfDefinition',
'definitionAtPosition',
'jumpToDefinition',
'referrencesAtPosition',
'referencesOf',
'getActiveHighlights',
'gasEstimation',
'declarationOf',
'getGasEstimates',
'getImports'
],
events: [], events: [],
version: '0.0.1' version: '0.0.1'
} }
export function isNodeDefinition(node: genericASTNode) { export function isNodeDefinition(node: genericASTNode) {
return node.nodeType === 'ContractDefinition' || return (
node.nodeType === 'FunctionDefinition' || node.nodeType === 'ContractDefinition' ||
node.nodeType === 'ModifierDefinition' || node.nodeType === 'FunctionDefinition' ||
node.nodeType === 'VariableDeclaration' || node.nodeType === 'ModifierDefinition' ||
node.nodeType === 'StructDefinition' || node.nodeType === 'VariableDeclaration' ||
node.nodeType === 'EventDefinition' node.nodeType === 'StructDefinition' ||
node.nodeType === 'EventDefinition'
)
} }
export type genericASTNode = export type genericASTNode =
ContractDefinitionAstNode | ContractDefinitionAstNode
| FunctionDefinitionAstNode | FunctionDefinitionAstNode
| ModifierDefinitionAstNode | ModifierDefinitionAstNode
| VariableDeclarationAstNode | VariableDeclarationAstNode
| StructDefinitionAstNode | StructDefinitionAstNode
| EventDefinitionAstNode | EventDefinitionAstNode
| IdentifierAstNode | IdentifierAstNode
| FunctionCallAstNode | FunctionCallAstNode
| ImportDirectiveAstNode | ImportDirectiveAstNode
| SourceUnitAstNode | SourceUnitAstNode
interface flatReferenceIndexNode { interface flatReferenceIndexNode {
[id: number]: genericASTNode [id: number]: genericASTNode
} }
interface declarationIndexNode { interface declarationIndexNode {
[id: number]: genericASTNode[] [id: number]: genericASTNode[]
} }
interface codeParserIndex { interface codeParserIndex {
declarations: declarationIndexNode, declarations: declarationIndexNode
flatReferences: flatReferenceIndexNode, flatReferences: flatReferenceIndexNode
nodesPerFile: any nodesPerFile: any
} }
export class CodeParser extends Plugin { export class CodeParser extends Plugin {
compilerAbstract: CompilerAbstract compilerAbstract: CompilerAbstract
currentFile: string currentFile: string
nodeIndex: codeParserIndex nodeIndex: codeParserIndex
@ -80,7 +125,6 @@ export class CodeParser extends Plugin {
debuggerIsOn: boolean = false debuggerIsOn: boolean = false
constructor(astWalker: any) { constructor(astWalker: any) {
super(profile) super(profile)
this.astWalker = astWalker this.astWalker = astWalker
@ -106,7 +150,6 @@ export class CodeParser extends Plugin {
} }
async onActivation() { async onActivation() {
this.gasService = new CodeParserGasService(this) this.gasService = new CodeParserGasService(this)
this.compilerService = new CodeParserCompiler(this) this.compilerService = new CodeParserCompiler(this)
this.antlrService = new CodeParserAntlrService(this) this.antlrService = new CodeParserAntlrService(this)
@ -158,49 +201,49 @@ export class CodeParser extends Plugin {
}) })
await this.compilerService.init() await this.compilerService.init()
this.on('solidity', 'compilerLoaded', async () => { this.on('solidity', 'compilerLoaded', async () => {
await this.reload() await this.reload()
}) })
this.on('debugger', 'startDebugging', async () => { this.on('debugger', 'startDebugging', async () => {
this.debuggerIsOn = true this.debuggerIsOn = true
await this.reload() await this.reload()
}) })
this.on('debugger', 'stopDebugging', async () => { this.on('debugger', 'stopDebugging', async () => {
this.debuggerIsOn = false this.debuggerIsOn = false
await this.reload() await this.reload()
}) })
} }
async reload(){ async reload() {
await this.call('editor', 'discardLineTexts') await this.call('editor', 'discardLineTexts')
await this.call('fileDecorator', 'clearFileDecorators') await this.call('fileDecorator', 'clearFileDecorators')
await this.call('editor', 'clearErrorMarkers', [this.currentFile]) await this.call('editor', 'clearErrorMarkers', [this.currentFile])
await this.handleChangeEvents() await this.handleChangeEvents()
} }
/** /**
* *
* @returns * @returns
*/ */
async getLastCompilationResult() { async getLastCompilationResult() {
return this.compilerAbstract return this.compilerAbstract
} }
getSubNodes<T extends genericASTNode>(node: T): number[] { getSubNodes<T extends genericASTNode>(node: T): number[] {
return node.nodeType == "ContractDefinition" && node.contractDependencies; return node.nodeType == 'ContractDefinition' && node.contractDependencies
} }
/** /**
* Builds a flat index and declarations of all the nodes in the compilation result * Builds a flat index and declarations of all the nodes in the compilation result
* @param compilationResult * @param compilationResult
* @param source * @param source
*/ */
_buildIndex(compilationResult: CompilationResult, source) { _buildIndex(compilationResult: CompilationResult, source) {
if (compilationResult && compilationResult.sources) { if (compilationResult && compilationResult.sources) {
const callback = (node: genericASTNode) => { const callback = (node: genericASTNode) => {
if (node && ("referencedDeclaration" in node) && node.referencedDeclaration) { if (node && 'referencedDeclaration' in node && node.referencedDeclaration) {
if (!this.nodeIndex.declarations[node.referencedDeclaration]) { if (!this.nodeIndex.declarations[node.referencedDeclaration]) {
this.nodeIndex.declarations[node.referencedDeclaration] = [] this.nodeIndex.declarations[node.referencedDeclaration] = []
} }
@ -211,9 +254,7 @@ export class CodeParser extends Plugin {
for (const s in compilationResult.sources) { for (const s in compilationResult.sources) {
this.astWalker.walkFull(compilationResult.sources[s].ast, callback) this.astWalker.walkFull(compilationResult.sources[s].ast, callback)
} }
} }
} }
// NODE HELPERS // NODE HELPERS
@ -232,15 +273,13 @@ export class CodeParser extends Plugin {
return '(' + params.toString() + ')' return '(' + params.toString() + ')'
} }
_flatNodeList(contractNode: ContractDefinitionAstNode, fileName: string, inScope: boolean, compilatioResult: any) { _flatNodeList(contractNode: ContractDefinitionAstNode, fileName: string, inScope: boolean, compilatioResult: any) {
const index = {} const index = {}
const contractName: string = contractNode.name const contractName: string = contractNode.name
const callback = (node) => { const callback = (node) => {
if (inScope && node.scope !== contractNode.id if (inScope && node.scope !== contractNode.id && !(node.nodeType === 'EnumDefinition' || node.nodeType === 'EventDefinition' || node.nodeType === 'ModifierDefinition'))
&& !(node.nodeType === 'EnumDefinition' || node.nodeType === 'EventDefinition' || node.nodeType === 'ModifierDefinition'))
return return
if (inScope) node.isClassNode = true; if (inScope) node.isClassNode = true
node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult) node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult)
node.functionName = node.name + this._getInputParams(node) node.functionName = node.name + this._getInputParams(node)
node.contractName = contractName node.contractName = contractName
@ -261,7 +300,10 @@ export class CodeParser extends Plugin {
if (node.nodeType === 'ContractDefinition') { if (node.nodeType === 'ContractDefinition') {
const flatNodes = this._flatNodeList(node, fileName, false, compilationResult) const flatNodes = this._flatNodeList(node, fileName, false, compilationResult)
node.gasEstimate = this._getContractGasEstimate(node, node.name, fileName, compilationResult) node.gasEstimate = this._getContractGasEstimate(node, node.name, fileName, compilationResult)
nodesByContract.contracts[node.name] = { contractDefinition: node, contractNodes: flatNodes } nodesByContract.contracts[node.name] = {
contractDefinition: node,
contractNodes: flatNodes
}
const baseNodes = {} const baseNodes = {}
const baseNodesWithBaseContractScope = {} const baseNodesWithBaseContractScope = {}
if (node.linearizedBaseContracts) { if (node.linearizedBaseContracts) {
@ -271,19 +313,16 @@ export class CodeParser extends Plugin {
const callback = (node) => { const callback = (node) => {
node.contractName = (baseContract as any).name node.contractName = (baseContract as any).name
node.contractId = (baseContract as any).id node.contractId = (baseContract as any).id
node.isBaseNode = true; node.isBaseNode = true
baseNodes[node.id] = node baseNodes[node.id] = node
if ((node.scope && node.scope === baseContract.id) if ((node.scope && node.scope === baseContract.id) || node.nodeType === 'EnumDefinition' || node.nodeType === 'EventDefinition') {
|| node.nodeType === 'EnumDefinition'
|| node.nodeType === 'EventDefinition'
) {
baseNodesWithBaseContractScope[node.id] = node baseNodesWithBaseContractScope[node.id] = node
} }
if (node.members) { if (node.members) {
for (const member of node.members) { for (const member of node.members) {
member.contractName = (baseContract as any).name member.contractName = (baseContract as any).name
member.contractId = (baseContract as any).id member.contractId = (baseContract as any).id
member.isBaseNode = true; member.isBaseNode = true
} }
} }
} }
@ -296,10 +335,9 @@ export class CodeParser extends Plugin {
nodesByContract.contracts[node.name].contractScopeNodes = this._flatNodeList(node, fileName, true, compilationResult) nodesByContract.contracts[node.name].contractScopeNodes = this._flatNodeList(node, fileName, true, compilationResult)
} }
if (node.nodeType === 'ImportDirective') { if (node.nodeType === 'ImportDirective') {
const imported = await this.resolveImports(node, {}) const imported = await this.resolveImports(node, {})
for (const importedNode of (Object.values(imported) as any)) { for (const importedNode of Object.values(imported) as any) {
if (importedNode.nodes) if (importedNode.nodes)
for (const subNode of importedNode.nodes) { for (const subNode of importedNode.nodes) {
nodesByContract.imports[subNode.id] = subNode nodesByContract.imports[subNode.id] = subNode
@ -312,7 +350,6 @@ export class CodeParser extends Plugin {
} }
_getContractGasEstimate(node: ContractDefinitionAstNode | FunctionDefinitionAstNode, contractName: string, fileName: string, compilationResult: lastCompilationResult) { _getContractGasEstimate(node: ContractDefinitionAstNode | FunctionDefinitionAstNode, contractName: string, fileName: string, compilationResult: lastCompilationResult) {
const contracts = compilationResult.data.contracts && compilationResult.data.contracts[this.currentFile] const contracts = compilationResult.data.contracts && compilationResult.data.contracts[this.currentFile]
for (const name in contracts) { for (const name in contracts) {
if (name === contractName) { if (name === contractName) {
@ -331,11 +368,11 @@ export class CodeParser extends Plugin {
} else if (visibility === 'private' || visibility === 'internal') { } else if (visibility === 'private' || visibility === 'internal') {
executionCost = estimationObj === null ? '-' : estimationObj.internal[fn] executionCost = estimationObj === null ? '-' : estimationObj.internal[fn]
} }
return { executionCost } return {executionCost}
} else { } else {
return { return {
creationCost: estimationObj === null ? '-' : estimationObj.creation.totalCost, creationCost: estimationObj === null ? '-' : estimationObj.creation.totalCost,
codeDepositCost: estimationObj === null ? '-' : estimationObj.creation.codeDepositCost, codeDepositCost: estimationObj === null ? '-' : estimationObj.creation.codeDepositCost
} }
} }
} }
@ -344,31 +381,41 @@ export class CodeParser extends Plugin {
} }
/** /**
* Nodes at position where position is a number, offset * Nodes at position where position is a number, offset
* @param position * @param position
* @param type * @param type
* @returns * @returns
*/ */
async nodesAtPosition(position: number, type = ''): Promise<genericASTNode[]> { async nodesAtPosition(position: number, type = ''): Promise<genericASTNode[]> {
let lastCompilationResult = this.compilerAbstract let lastCompilationResult = this.compilerAbstract
if(this.debuggerIsOn) { if (this.debuggerIsOn) {
lastCompilationResult = await this.call('compilerArtefacts', 'get', '__last') lastCompilationResult = await this.call('compilerArtefacts', 'get', '__last')
this.currentFile = await this.call('fileManager', 'file') this.currentFile = await this.call('fileManager', 'file')
} }
if (!lastCompilationResult) return [] if (!lastCompilationResult) return []
const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile) const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile)
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data && lastCompilationResult.data.sources && lastCompilationResult.data.sources[this.currentFile]) { if (
const nodes: genericASTNode[] = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file]) lastCompilationResult &&
lastCompilationResult.languageversion.indexOf('soljson') === 0 &&
lastCompilationResult.data &&
lastCompilationResult.data.sources &&
lastCompilationResult.data.sources[this.currentFile]
) {
const nodes: genericASTNode[] = sourceMappingDecoder.nodesAtPosition(
type,
position,
lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file]
)
return nodes return nodes
} }
return [] return []
} }
/** /**
* *
* @param id * @param id
* @returns * @returns
*/ */
async getNodeById(id: number) { async getNodeById(id: number) {
for (const key in this.nodeIndex.flatReferences) { for (const key in this.nodeIndex.flatReferences) {
if (this.nodeIndex.flatReferences[key].id === id) { if (this.nodeIndex.flatReferences[key].id === id) {
@ -378,19 +425,19 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param id * @param id
* @returns * @returns
*/ */
async getDeclaration(id: number) { async getDeclaration(id: number) {
if (this.nodeIndex.declarations && this.nodeIndex.declarations[id]) return this.nodeIndex.declarations[id] if (this.nodeIndex.declarations && this.nodeIndex.declarations[id]) return this.nodeIndex.declarations[id]
} }
/** /**
* *
* @param scope * @param scope
* @returns * @returns
*/ */
async getNodesWithScope(scope: number) { async getNodesWithScope(scope: number) {
const nodes = [] const nodes = []
for (const node of Object.values(this.nodeIndex.flatReferences)) { for (const node of Object.values(this.nodeIndex.flatReferences)) {
@ -400,10 +447,10 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param name * @param name
* @returns * @returns
*/ */
async getNodesWithName(name: string) { async getNodesWithName(name: string) {
const nodes: genericASTNode[] = [] const nodes: genericASTNode[] = []
for (const node of Object.values(this.nodeIndex.flatReferences)) { for (const node of Object.values(this.nodeIndex.flatReferences)) {
@ -412,22 +459,22 @@ export class CodeParser extends Plugin {
return nodes return nodes
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
declarationOf<T extends genericASTNode>(node: T) { declarationOf<T extends genericASTNode>(node: T) {
if (node && ('referencedDeclaration' in node) && node.referencedDeclaration) { if (node && 'referencedDeclaration' in node && node.referencedDeclaration) {
return this.nodeIndex.flatReferences[node.referencedDeclaration] return this.nodeIndex.flatReferences[node.referencedDeclaration]
} }
return null return null
} }
/** /**
* *
* @param position * @param position
* @returns * @returns
*/ */
async definitionAtPosition(position: number) { async definitionAtPosition(position: number) {
const nodes = await this.nodesAtPosition(position) const nodes = await this.nodesAtPosition(position)
let nodeDefinition: any let nodeDefinition: any
@ -436,7 +483,7 @@ export class CodeParser extends Plugin {
node = nodes[nodes.length - 1] node = nodes[nodes.length - 1]
nodeDefinition = node nodeDefinition = node
if (!isNodeDefinition(node)) { if (!isNodeDefinition(node)) {
nodeDefinition = await this.declarationOf(node) || node nodeDefinition = (await this.declarationOf(node)) || node
} }
if (node.nodeType === 'ImportDirective') { if (node.nodeType === 'ImportDirective') {
for (const key in this.nodeIndex.flatReferences) { for (const key in this.nodeIndex.flatReferences) {
@ -464,32 +511,32 @@ export class CodeParser extends Plugin {
return nodeDefinition return nodeDefinition
} }
} }
} }
async getContractNodes(contractName: string) { async getContractNodes(contractName: string) {
if (this.nodeIndex.nodesPerFile if (
&& this.nodeIndex.nodesPerFile[this.currentFile] this.nodeIndex.nodesPerFile &&
&& this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName] this.nodeIndex.nodesPerFile[this.currentFile] &&
&& this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName].contractNodes) { this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName] &&
this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName].contractNodes
) {
return this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName] return this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName]
} }
return false return false
} }
async getCurrentFileNodes() { async getCurrentFileNodes() {
if (this.nodeIndex.nodesPerFile if (this.nodeIndex.nodesPerFile && this.nodeIndex.nodesPerFile[this.currentFile]) {
&& this.nodeIndex.nodesPerFile[this.currentFile]) {
return this.nodeIndex.nodesPerFile[this.currentFile] return this.nodeIndex.nodesPerFile[this.currentFile]
} }
return false return false
} }
/** /**
* *
* @param identifierNode * @param identifierNode
* @returns * @returns
*/ */
async findIdentifier(identifierNode: any) { async findIdentifier(identifierNode: any) {
const astNodes = await this.antlrService.listAstNodes() const astNodes = await this.antlrService.listAstNodes()
for (const node of astNodes) { for (const node of astNodes) {
@ -500,10 +547,10 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
async positionOfDefinition(node: genericASTNode): Promise<any | null> { async positionOfDefinition(node: genericASTNode): Promise<any | null> {
if (node) { if (node) {
if (node.src) { if (node.src) {
@ -517,11 +564,11 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param node * @param node
* @param imported * @param imported
* @returns * @returns
*/ */
async resolveImports(node: any, imported = {}) { async resolveImports(node: any, imported = {}) {
if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) { if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) {
const importNode: any = await this.getNodeById(node.sourceUnit) const importNode: any = await this.getNodeById(node.sourceUnit)
@ -535,13 +582,11 @@ export class CodeParser extends Plugin {
return imported return imported
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
referencesOf(node: genericASTNode) { referencesOf(node: genericASTNode) {
const results: genericASTNode[] = [] const results: genericASTNode[] = []
const highlights = (id: number) => { const highlights = (id: number) => {
@ -553,7 +598,7 @@ export class CodeParser extends Plugin {
} }
} }
} }
if (node && ("referencedDeclaration" in node) && node.referencedDeclaration) { if (node && 'referencedDeclaration' in node && node.referencedDeclaration) {
highlights(node.referencedDeclaration) highlights(node.referencedDeclaration)
const current = this.nodeIndex.flatReferences[node.referencedDeclaration] const current = this.nodeIndex.flatReferences[node.referencedDeclaration]
results.push(current) results.push(current)
@ -564,10 +609,10 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param position * @param position
* @returns * @returns
*/ */
async referrencesAtPosition(position: any): Promise<genericASTNode[]> { async referrencesAtPosition(position: any): Promise<genericASTNode[]> {
const nodes = await this.nodesAtPosition(position) const nodes = await this.nodesAtPosition(position)
if (nodes && nodes.length) { if (nodes && nodes.length) {
@ -579,20 +624,18 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @returns * @returns
*/ */
async getNodes(): Promise<flatReferenceIndexNode> { async getNodes(): Promise<flatReferenceIndexNode> {
return this.nodeIndex.flatReferences return this.nodeIndex.flatReferences
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
async getNodeLink(node: genericASTNode) { async getNodeLink(node: genericASTNode) {
const lineColumn = await this.getLineColumnOfNode(node) const lineColumn = await this.getLineColumnOfNode(node)
const position = await this.positionOfDefinition(node) const position = await this.positionOfDefinition(node)
@ -604,16 +647,16 @@ export class CodeParser extends Plugin {
} }
/* /*
* @param node * @param node
*/ */
async getLineColumnOfNode(node: any) { async getLineColumnOfNode(node: any) {
const position = await this.positionOfDefinition(node) const position = await this.positionOfDefinition(node)
return this.getLineColumnOfPosition(position) return this.getLineColumnOfPosition(position)
} }
/* /*
* @param position * @param position
*/ */
async getLineColumnOfPosition(position: any) { async getLineColumnOfPosition(position: any) {
if (position) { if (position) {
const fileName = this.compilerAbstract.getSourceName(position.file) const fileName = this.compilerAbstract.getSourceName(position.file)
@ -624,14 +667,14 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
async getNodeDocumentation(node: genericASTNode) { async getNodeDocumentation(node: genericASTNode) {
if (("documentation" in node) && node.documentation && (node.documentation as any).text) { if ('documentation' in node && node.documentation && (node.documentation as any).text) {
let text = ''; let text = ''
(node.documentation as any).text.split('\n').forEach(line => { ;(node.documentation as any).text.split('\n').forEach((line) => {
text += `${line.trim()}\n` text += `${line.trim()}\n`
}) })
return text return text
@ -639,10 +682,10 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
async getVariableDeclaration(node: any) { async getVariableDeclaration(node: any) {
const nodeVisibility = node.visibility && node.visibility.length ? node.visibility + ' ' : '' const nodeVisibility = node.visibility && node.visibility.length ? node.visibility + ' ' : ''
const nodeName = node.name && node.name.length ? node.name : '' const nodeName = node.name && node.name.length ? node.name : ''
@ -651,23 +694,21 @@ export class CodeParser extends Plugin {
} else { } else {
if (node.typeName && node.typeName.name) { if (node.typeName && node.typeName.name) {
return `${node.typeName.name} ${nodeVisibility}${nodeName}` return `${node.typeName.name} ${nodeVisibility}${nodeName}`
} } else if (node.typeName && node.typeName.namePath) {
else if (node.typeName && node.typeName.namePath) {
return `${node.typeName.namePath} ${nodeVisibility}${nodeName}` return `${node.typeName.namePath} ${nodeVisibility}${nodeName}`
} } else {
else {
return `${nodeName}${nodeName}` return `${nodeName}${nodeName}`
} }
} }
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
async getFunctionParamaters(node: any) { async getFunctionParamaters(node: any) {
const localParam = (node.parameters && node.parameters.parameters) || (node.parameters) const localParam = (node.parameters && node.parameters.parameters) || node.parameters
if (localParam) { if (localParam) {
const params = [] const params = []
for (const param of localParam) { for (const param of localParam) {
@ -678,12 +719,12 @@ export class CodeParser extends Plugin {
} }
/** /**
* *
* @param node * @param node
* @returns * @returns
*/ */
async getFunctionReturnParameters(node: any) { async getFunctionReturnParameters(node: any) {
const localParam = (node.returnParameters && node.returnParameters.parameters) const localParam = node.returnParameters && node.returnParameters.parameters
if (localParam) { if (localParam) {
const params = [] const params = []
for (const param of localParam) { for (const param of localParam) {
@ -692,7 +733,4 @@ export class CodeParser extends Plugin {
return `(${params.join(', ')})` return `(${params.join(', ')})`
} }
} }
} }

@ -1,9 +1,9 @@
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { FormattedMessage } from 'react-intl' import {FormattedMessage} from 'react-intl'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { AppModal } from '@remix-ui/app' import {AppModal} from '@remix-ui/app'
import { PermissionHandlerDialog, PermissionHandlerValue } from '@remix-ui/permission-handler' import {PermissionHandlerDialog, PermissionHandlerValue} from '@remix-ui/permission-handler'
import { Profile } from '@remixproject/plugin-utils' import {Profile} from '@remixproject/plugin-utils'
const profile = { const profile = {
name: 'permissionhandler', name: 'permissionhandler',
@ -46,16 +46,12 @@ export class PermissionHandlerPlugin extends Plugin {
} }
} }
switchMode (from: Profile, to: Profile, method: string, set: boolean, sensitiveCall: boolean) { switchMode(from: Profile, to: Profile, method: string, set: boolean, sensitiveCall: boolean) {
if (sensitiveCall) { if (sensitiveCall) {
set set ? (this.sessionPermissions[to.name][method][from.name] = {}) : delete this.sessionPermissions[to.name][method][from.name]
? this.sessionPermissions[to.name][method][from.name] = {}
: delete this.sessionPermissions[to.name][method][from.name]
} else { } else {
set set ? (this.permissions[to.name][method][from.name] = {}) : delete this.permissions[to.name][method][from.name]
? this.permissions[to.name][method][from.name] = {} }
: delete this.permissions[to.name][method][from.name]
}
} }
clear() { clear() {
@ -93,7 +89,7 @@ export class PermissionHandlerPlugin extends Plugin {
if (!this.permissions[to.name][method][from.name]) return this.openPermission(from, to, method, message, sensitiveCall) if (!this.permissions[to.name][method][from.name]) return this.openPermission(from, to, method, message, sensitiveCall)
} }
const { allow, hash } = sensitiveCall ? this.sessionPermissions[to.name][method][from.name] : this.permissions[to.name][method][from.name] const {allow, hash} = sensitiveCall ? this.sessionPermissions[to.name][method][from.name] : this.permissions[to.name][method][from.name]
if (!allow) { if (!allow) {
const warning = this.notAllowWarning(from, to, method) const warning = this.notAllowWarning(from, to, method)
this.call('notification', 'toast', warning) this.call('notification', 'toast', warning)
@ -124,10 +120,10 @@ export class PermissionHandlerPlugin extends Plugin {
} }
const modal: AppModal = { const modal: AppModal = {
id: 'PermissionHandler', id: 'PermissionHandler',
title: <FormattedMessage id='permissionHandler.permissionNeededFor' values={{ to: to.displayName || to.name }} />, title: <FormattedMessage id="permissionHandler.permissionNeededFor" values={{to: to.displayName || to.name}} />,
message: <PermissionHandlerDialog plugin={this} theme={await this.getTheme()} value={value}></PermissionHandlerDialog>, message: <PermissionHandlerDialog plugin={this} theme={await this.getTheme()} value={value}></PermissionHandlerDialog>,
okLabel: <FormattedMessage id='permissionHandler.accept' />, okLabel: <FormattedMessage id="permissionHandler.accept" />,
cancelLabel: <FormattedMessage id='permissionHandler.decline' /> cancelLabel: <FormattedMessage id="permissionHandler.decline" />
} }
const result = await this.call('notification', 'modal', modal) const result = await this.call('notification', 'modal', modal)
@ -166,7 +162,7 @@ export class PermissionHandlerPlugin extends Plugin {
} }
this.persistPermissions() this.persistPermissions()
} }
} }
reject(this.notAllowWarning(from, to, method)) reject(this.notAllowWarning(from, to, method))
} }
}) })

@ -1,12 +1,12 @@
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
import React, { useRef, useState, useEffect } from 'react' // eslint-disable-line import React, {useRef, useState, useEffect} from 'react' // eslint-disable-line
import isElectron from 'is-electron' import isElectron from 'is-electron'
import { WebsocketPlugin } from '@remixproject/engine-web' import {WebsocketPlugin} from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { version as remixdVersion } from '../../../../../libs/remixd/package.json' import {version as remixdVersion} from '../../../../../libs/remixd/package.json'
import { PluginManager } from '@remixproject/engine' import {PluginManager} from '@remixproject/engine'
import { AppModal, AlertModal } from '@remix-ui/app' import {AppModal, AlertModal} from '@remix-ui/app'
const LOCALHOST = ' - connect to localhost - ' const LOCALHOST = ' - connect to localhost - '
@ -19,10 +19,10 @@ const profile = {
description: 'Using Remixd daemon, allow to access file system', description: 'Using Remixd daemon, allow to access file system',
kind: 'other', kind: 'other',
version: packageJson.version, version: packageJson.version,
repo: "https://github.com/ethereum/remix-project/tree/master/libs/remixd", repo: 'https://github.com/ethereum/remix-project/tree/master/libs/remixd',
maintainedBy: "Remix", maintainedBy: 'Remix',
documentation: "https://remix-ide.readthedocs.io/en/latest/remixd.html", documentation: 'https://remix-ide.readthedocs.io/en/latest/remixd.html',
authorContact: "" authorContact: ''
} }
export class RemixdHandle extends WebsocketPlugin { export class RemixdHandle extends WebsocketPlugin {
@ -62,7 +62,6 @@ export class RemixdHandle extends WebsocketPlugin {
} }
await this.appManager.deactivatePlugin('remixd') await this.appManager.deactivatePlugin('remixd')
} }
async callPluginMethod(key: string, payload?: any[]) { async callPluginMethod(key: string, payload?: any[]) {
@ -74,11 +73,11 @@ export class RemixdHandle extends WebsocketPlugin {
} }
/** /**
* connect to localhost if no connection and render the explorer * connect to localhost if no connection and render the explorer
* disconnect from localhost if connected and remove the explorer * disconnect from localhost if connected and remove the explorer
* *
* @param {String} txHash - hash of the transaction * @param {String} txHash - hash of the transaction
*/ */
async connectToLocalhost() { async connectToLocalhost() {
const connection = async (error?: any) => { const connection = async (error?: any) => {
if (error) { if (error) {
@ -91,7 +90,8 @@ export class RemixdHandle extends WebsocketPlugin {
this.canceled() this.canceled()
} else { } else {
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed if (!this.socket || (this.socket && this.socket.readyState === 3)) {
// 3 means connection closed
clearInterval(intervalId) clearInterval(intervalId)
const alert: AlertModal = { const alert: AlertModal = {
id: 'connectionAlert', id: 'connectionAlert',
@ -102,8 +102,8 @@ export class RemixdHandle extends WebsocketPlugin {
} }
}, 3000) }, 3000)
this.localhostProvider.init(() => { this.localhostProvider.init(() => {
this.call('filePanel', 'setWorkspace', { name: LOCALHOST, isLocalhost: true }, true) this.call('filePanel', 'setWorkspace', {name: LOCALHOST, isLocalhost: true}, true)
}); })
for (const plugin of this.dependentPlugins) { for (const plugin of this.dependentPlugins) {
await this.appManager.activatePlugin(plugin) await this.appManager.activatePlugin(plugin)
} }
@ -118,7 +118,7 @@ export class RemixdHandle extends WebsocketPlugin {
title: 'Access file system using remixd', title: 'Access file system using remixd',
message: remixdDialog(), message: remixdDialog(),
okLabel: 'Connect', okLabel: 'Connect',
cancelLabel: 'Cancel', cancelLabel: 'Cancel'
} }
const result = await this.call('notification', 'modal', mod) const result = await this.call('notification', 'modal', mod)
if (result) { if (result) {
@ -126,7 +126,8 @@ export class RemixdHandle extends WebsocketPlugin {
this.localhostProvider.preInit() this.localhostProvider.preInit()
super.activate() super.activate()
setTimeout(() => { setTimeout(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed if (!this.socket || (this.socket && this.socket.readyState === 3)) {
// 3 means connection closed
connection(new Error('Connection with daemon failed.')) connection(new Error('Connection with daemon failed.'))
} else { } else {
connection() connection()
@ -135,14 +136,15 @@ export class RemixdHandle extends WebsocketPlugin {
} catch (error) { } catch (error) {
connection(error) connection(error)
} }
} } else {
else {
await this.canceled() await this.canceled()
} }
} else { } else {
try { try {
super.activate() super.activate()
setTimeout(() => { connection() }, 2000) setTimeout(() => {
connection()
}, 2000)
} catch (error) { } catch (error) {
connection(error) connection(error)
} }
@ -153,37 +155,53 @@ export class RemixdHandle extends WebsocketPlugin {
function remixdDialog() { function remixdDialog() {
const commandText = 'remixd' const commandText = 'remixd'
const fullCommandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>' const fullCommandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>'
return (<> return (
<div className=''> <>
<div className='mb-2 text-break'> <div className="">
Access your local file system from Remix IDE using <a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">Remixd NPM package</a>. <div className="mb-2 text-break">
</div> Access your local file system from Remix IDE using{' '}
<div className='mb-2 text-break'> <a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">
Remixd <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">documentation</a>. Remixd NPM package
</div> </a>
<div className='mb-2 text-break'> .
The remixd command is: </div>
<br /><b>{commandText}</b> <div className="mb-2 text-break">
</div> Remixd{' '}
<div className='mb-2 text-break'> <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">
The remixd command without options uses the terminal's current directory as the shared directory and the shared Remix domain can only be https://remix.ethereum.org, https://remix-alpha.ethereum.org, or https://remix-beta.ethereum.org documentation
</div> </a>
<div className='mb-2 text-break'> .
Example command with flags: <br /> </div>
<b>{fullCommandText}</b> <div className="mb-2 text-break">
</div> The remixd command is:
<div className='mb-2 text-break'> <br />
For info about ports, see <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">Remixd ports usage</a> <b>{commandText}</b>
</div> </div>
<div className='mb-2 text-break'> <div className="mb-2 text-break">
This feature is still in Alpha. We recommend to keep a backup of the shared folder. The remixd command without options uses the terminal's current directory as the shared directory and the shared Remix domain can only be https://remix.ethereum.org,
</div> https://remix-alpha.ethereum.org, or https://remix-beta.ethereum.org
<div className='mb-2 text-break'> </div>
<h6 className="text-danger"> <div className="mb-2 text-break">
Before using, make sure remixd version is latest i.e. <b>v{remixdVersion}</b> Example command with flags: <br />
<br></br><a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#update-to-the-latest-remixd">Read here how to update it</a> <b>{fullCommandText}</b>
</h6> </div>
<div className="mb-2 text-break">
For info about ports, see{' '}
<a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">
Remixd ports usage
</a>
</div>
<div className="mb-2 text-break">This feature is still in Alpha. We recommend to keep a backup of the shared folder.</div>
<div className="mb-2 text-break">
<h6 className="text-danger">
Before using, make sure remixd version is latest i.e. <b>v{remixdVersion}</b>
<br></br>
<a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#update-to-the-latest-remixd">
Read here how to update it
</a>
</h6>
</div>
</div> </div>
</div> </>
</>) )
} }

@ -1,9 +1,9 @@
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { format } from 'util' import {format} from 'util'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { compile } from '@remix-project/remix-solidity' import {compile} from '@remix-project/remix-solidity'
import { Transaction } from 'web3-types' import {Transaction} from 'web3-types'
const _paq = window._paq = window._paq || [] //eslint-disable-line const _paq = (window._paq = window._paq || []) //eslint-disable-line
const profile = { const profile = {
name: 'solidity-script', name: 'solidity-script',
@ -13,17 +13,16 @@ const profile = {
} }
export class SolidityScript extends Plugin { export class SolidityScript extends Plugin {
constructor () { constructor() {
super(profile) super(profile)
} }
async execute (path: string, functionName: string = 'run') { async execute(path: string, functionName: string = 'run') {
_paq.push(['trackEvent', 'SolidityScript', 'execute', 'script']) _paq.push(['trackEvent', 'SolidityScript', 'execute', 'script'])
this.call('terminal', 'log', `Running free function '${functionName}' from ${path}...`) this.call('terminal', 'log', `Running free function '${functionName}' from ${path}...`)
let content = await this.call('fileManager', 'readFile', path) let content = await this.call('fileManager', 'readFile', path)
const params = await this.call('solidity', 'getCompilerParameters') const params = await this.call('solidity', 'getCompilerParameters')
content = ` content = `
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
@ -38,13 +37,15 @@ export class SolidityScript extends Plugin {
${functionName}(); ${functionName}();
} }
}` }`
const targets = { 'script.sol': { content } } const targets = {'script.sol': {content}}
// compile // compile
const compilation = await compile(targets, params, async (url, cb) => { const compilation = await compile(targets, params, async (url, cb) => {
await this.call('contentImport', 'resolveAndSave', url).then((result) => cb(null, result)).catch((error) => cb(error.message)) await this.call('contentImport', 'resolveAndSave', url)
.then((result) => cb(null, result))
.catch((error) => cb(error.message))
}) })
if (compilation.data.error) { if (compilation.data.error) {
this.call('terminal', 'log', compilation.data.error.formattedMessage) this.call('terminal', 'log', compilation.data.error.formattedMessage)
} }
@ -53,7 +54,7 @@ export class SolidityScript extends Plugin {
this.call('terminal', 'log', error.formattedMessage) this.call('terminal', 'log', error.formattedMessage)
}) })
} }
// get the contract // get the contract
const contract = compilation.getContract('SolidityScript') const contract = compilation.getContract('SolidityScript')
if (!contract) { if (!contract) {
@ -83,9 +84,12 @@ export class SolidityScript extends Plugin {
const hhlogs = await web3.testPlugin.getHHLogsForTx(receiptCall.transactionHash) const hhlogs = await web3.testPlugin.getHHLogsForTx(receiptCall.transactionHash)
if (hhlogs && hhlogs.length) { if (hhlogs && hhlogs.length) {
const finalLogs = <div><div><b>console.log:</b></div> const finalLogs = (
{ <div>
hhlogs.map((log) => { <div>
<b>console.log:</b>
</div>
{hhlogs.map((log) => {
let formattedLog let formattedLog
// Hardhat implements the same formatting options that can be found in Node.js' console.log, // Hardhat implements the same formatting options that can be found in Node.js' console.log,
// which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args // which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args
@ -98,9 +102,10 @@ export class SolidityScript extends Plugin {
} }
return <div>{formattedLog}</div> return <div>{formattedLog}</div>
})} })}
</div> </div>
)
_paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log']) _paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log'])
this.call('terminal', 'logHtml', finalLogs) this.call('terminal', 'logHtml', finalLogs)
} }
} }
} }

@ -1,19 +1,19 @@
/* eslint-disable @nrwl/nx/enforce-module-boundaries */ /* eslint-disable @nrwl/nx/enforce-module-boundaries */
import { ViewPlugin } from '@remixproject/engine-web' import {ViewPlugin} from '@remixproject/engine-web'
import React from 'react' import React from 'react'
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { RemixUiSolidityUmlGen } from '@remix-ui/solidity-uml-gen' import {RemixUiSolidityUmlGen} from '@remix-ui/solidity-uml-gen'
import { ISolidityUmlGen, ThemeQualityType, ThemeSummary } from 'libs/remix-ui/solidity-uml-gen/src/types' import {ISolidityUmlGen, ThemeQualityType, ThemeSummary} from 'libs/remix-ui/solidity-uml-gen/src/types'
import { RemixAppManager } from 'libs/remix-ui/plugin-manager/src/types' import {RemixAppManager} from 'libs/remix-ui/plugin-manager/src/types'
import { normalizeContractPath } from 'libs/remix-ui/solidity-compiler/src/lib/logic/flattenerUtilities' import {normalizeContractPath} from 'libs/remix-ui/solidity-compiler/src/lib/logic/flattenerUtilities'
import { convertAST2UmlClasses } from 'sol2uml/lib/converterAST2Classes' import {convertAST2UmlClasses} from 'sol2uml/lib/converterAST2Classes'
import vizRenderStringSync from '@aduh95/viz.js/sync' import vizRenderStringSync from '@aduh95/viz.js/sync'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
import { customAction } from '@remixproject/plugin-api' import {customAction} from '@remixproject/plugin-api'
import { ClassOptions } from 'sol2uml/lib/converterClass2Dot' import {ClassOptions} from 'sol2uml/lib/converterClass2Dot'
const parser = (window as any).SolidityParser const parser = (window as any).SolidityParser
const _paq = window._paq = window._paq || [] const _paq = (window._paq = window._paq || [])
const profile = { const profile = {
name: 'solidityumlgen', name: 'solidityumlgen',
@ -21,7 +21,7 @@ const profile = {
description: 'Generates UML diagram in svg format from last compiled contract', description: 'Generates UML diagram in svg format from last compiled contract',
location: 'mainPanel', location: 'mainPanel',
methods: ['showUmlDiagram', 'generateUml', 'generateCustomAction'], methods: ['showUmlDiagram', 'generateUml', 'generateCustomAction'],
events: [], events: []
} }
/** /**
@ -54,7 +54,6 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
this.currentlySelectedTheme = '' this.currentlySelectedTheme = ''
this.themeName = '' this.themeName = ''
this.activeTheme = {} as ThemeSummary this.activeTheme = {} as ThemeSummary
this.appManager = appManager this.appManager = appManager
this.element = document.createElement('div') this.element = document.createElement('div')
@ -64,7 +63,7 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
onActivation(): void { onActivation(): void {
this.handleThemeChange() this.handleThemeChange()
this.on('solidity', 'compilationFinished', async (file: string, source, languageVersion, data, input, version) => { this.on('solidity', 'compilationFinished', async (file: string, source, languageVersion, data, input, version) => {
if(!this.triggerGenerateUml) return if (!this.triggerGenerateUml) return
this.triggerGenerateUml = false this.triggerGenerateUml = false
const currentTheme: ThemeQualityType = await this.call('theme', 'currentTheme') const currentTheme: ThemeQualityType = await this.call('theme', 'currentTheme')
this.currentlySelectedTheme = currentTheme.quality this.currentlySelectedTheme = currentTheme.quality
@ -73,14 +72,20 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
const normalized = normalizeContractPath(file) const normalized = normalizeContractPath(file)
this.currentFile = normalized[normalized.length - 1] this.currentFile = normalized[normalized.length - 1]
try { try {
if (data.sources && Object.keys(data.sources).length > 1) { // we should flatten first as there are multiple asts if (data.sources && Object.keys(data.sources).length > 1) {
// we should flatten first as there are multiple asts
result = await this.flattenContract(source, file, data) result = await this.flattenContract(source, file, data)
} }
const ast = result.length > 1 ? parser.parse(result) : parser.parse(source.sources[file].content) const ast = result.length > 1 ? parser.parse(result) : parser.parse(source.sources[file].content)
this.umlClasses = convertAST2UmlClasses(ast, this.currentFile) this.umlClasses = convertAST2UmlClasses(ast, this.currentFile)
let umlDot = '' let umlDot = ''
this.activeTheme = await this.call('theme', 'currentTheme') this.activeTheme = await this.call('theme', 'currentTheme')
umlDot = convertUmlClasses2Dot(this.umlClasses, false, { backColor: this.activeTheme.backgroundColor, textColor: this.activeTheme.textColor, shapeColor: this.activeTheme.shapeColor, fillColor: this.activeTheme.fillColor }) umlDot = convertUmlClasses2Dot(this.umlClasses, false, {
backColor: this.activeTheme.backgroundColor,
textColor: this.activeTheme.textColor,
shapeColor: this.activeTheme.shapeColor,
fillColor: this.activeTheme.fillColor
})
const payload = vizRenderStringSync(umlDot) const payload = vizRenderStringSync(umlDot)
this.updatedSvg = payload this.updatedSvg = payload
_paq.push(['trackEvent', 'solidityumlgen', 'umlgenerated']) _paq.push(['trackEvent', 'solidityumlgen', 'umlgenerated'])
@ -93,8 +98,7 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
} }
getThemeCssVariables(cssVars: string) { getThemeCssVariables(cssVars: string) {
return window.getComputedStyle(document.documentElement) return window.getComputedStyle(document.documentElement).getPropertyValue(cssVars)
.getPropertyValue(cssVars)
} }
private handleThemeChange() { private handleThemeChange() {
@ -102,7 +106,12 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
this.currentlySelectedTheme = theme.quality this.currentlySelectedTheme = theme.quality
this.activeTheme = theme this.activeTheme = theme
this.themeDark = theme.backgroundColor this.themeDark = theme.backgroundColor
const umlDot = convertUmlClasses2Dot(this.umlClasses, false, { backColor: this.activeTheme.backgroundColor, textColor: this.activeTheme.textColor, shapeColor: this.activeTheme.shapeColor, fillColor: this.activeTheme.fillColor }) const umlDot = convertUmlClasses2Dot(this.umlClasses, false, {
backColor: this.activeTheme.backgroundColor,
textColor: this.activeTheme.textColor,
shapeColor: this.activeTheme.shapeColor,
fillColor: this.activeTheme.fillColor
})
this.updatedSvg = vizRenderStringSync(umlDot) this.updatedSvg = vizRenderStringSync(umlDot)
this.renderComponent() this.renderComponent()
await this.call('tabs', 'focus', 'solidityumlgen') await this.call('tabs', 'focus', 'solidityumlgen')
@ -133,13 +142,11 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
* and assigns to a local property * and assigns to a local property
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
async flattenContract (source: any, filePath: string, data: any) { async flattenContract(source: any, filePath: string, data: any) {
const result = await this.call('contractflattener', 'flattenContract', source, filePath, data) const result = await this.call('contractflattener', 'flattenContract', source, filePath, data)
return result return result
} }
async showUmlDiagram(svgPayload: string) { async showUmlDiagram(svgPayload: string) {
this.updatedSvg = svgPayload this.updatedSvg = svgPayload
this.renderComponent() this.renderComponent()
@ -150,18 +157,20 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
this.renderComponent() this.renderComponent()
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
this.renderComponent() this.renderComponent()
} }
render() { render() {
return <div id='sol-uml-gen'> return (
<PluginViewWrapper plugin={this} /> <div id="sol-uml-gen">
</div> <PluginViewWrapper plugin={this} />
</div>
)
} }
renderComponent () { renderComponent() {
this.dispatch({ this.dispatch({
...this, ...this,
updatedSvg: this.updatedSvg, updatedSvg: this.updatedSvg,
@ -171,24 +180,25 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
themeDark: this.themeDark, themeDark: this.themeDark,
fileName: this.currentFile, fileName: this.currentFile,
themeCollection: this.themeCollection, themeCollection: this.themeCollection,
activeTheme: this.activeTheme, activeTheme: this.activeTheme
}) })
} }
updateComponent(state: any) { updateComponent(state: any) {
return <RemixUiSolidityUmlGen return (
updatedSvg={state.updatedSvg} <RemixUiSolidityUmlGen
loading={state.loading} updatedSvg={state.updatedSvg}
themeSelected={state.currentlySelectedTheme} loading={state.loading}
themeName={state.themeName} themeSelected={state.currentlySelectedTheme}
fileName={state.fileName} themeName={state.themeName}
themeCollection={state.themeCollection} fileName={state.fileName}
themeDark={state.themeDark} themeCollection={state.themeCollection}
/> themeDark={state.themeDark}
/>
)
} }
} }
interface Sol2umlClassOptions extends ClassOptions { interface Sol2umlClassOptions extends ClassOptions {
backColor?: string backColor?: string
shapeColor?: string shapeColor?: string
@ -196,15 +206,10 @@ interface Sol2umlClassOptions extends ClassOptions {
textColor?: string textColor?: string
} }
import { dirname } from 'path' import {dirname} from 'path'
import { convertClass2Dot } from 'sol2uml/lib/converterClass2Dot' import {convertClass2Dot} from 'sol2uml/lib/converterClass2Dot'
import { import {Association, ClassStereotype, ReferenceType, UmlClass} from 'sol2uml/lib/umlClass'
Association, import {findAssociatedClass} from 'sol2uml/lib/associations'
ClassStereotype,
ReferenceType,
UmlClass,
} from 'sol2uml/lib/umlClass'
import { findAssociatedClass } from 'sol2uml/lib/associations'
// const debug = require('debug')('sol2uml') // const debug = require('debug')('sol2uml')
@ -216,11 +221,7 @@ import { findAssociatedClass } from 'sol2uml/lib/associations'
* @param classOptions command line options for the `class` command * @param classOptions command line options for the `class` command
* @return dotString Graphviz's DOT format for defining nodes, edges and clusters. * @return dotString Graphviz's DOT format for defining nodes, edges and clusters.
*/ */
export function convertUmlClasses2Dot( export function convertUmlClasses2Dot(umlClasses: UmlClass[], clusterFolders: boolean = false, classOptions: Sol2umlClassOptions = {}): string {
umlClasses: UmlClass[],
clusterFolders: boolean = false,
classOptions: Sol2umlClassOptions = {}
): string {
let dotString: string = ` let dotString: string = `
digraph UmlClassDiagram { digraph UmlClassDiagram {
rankdir=BT rankdir=BT
@ -285,10 +286,7 @@ function sortUmlClassesByCodePath(umlClasses: UmlClass[]): UmlClass[] {
}) })
} }
export function addAssociationsToDot( export function addAssociationsToDot(umlClasses: UmlClass[], classOptions: ClassOptions = {}): string {
umlClasses: UmlClass[],
classOptions: ClassOptions = {}
): string {
let dotString: string = '' let dotString: string = ''
// for each class // for each class
@ -318,18 +316,9 @@ export function addAssociationsToDot(
// for each association in that class // for each association in that class
for (const association of Object.values(sourceUmlClass.associations)) { for (const association of Object.values(sourceUmlClass.associations)) {
const targetUmlClass = findAssociatedClass( const targetUmlClass = findAssociatedClass(association, sourceUmlClass, umlClasses)
association,
sourceUmlClass,
umlClasses
)
if (targetUmlClass) { if (targetUmlClass) {
dotString += addAssociationToDot( dotString += addAssociationToDot(sourceUmlClass, targetUmlClass, association, classOptions)
sourceUmlClass,
targetUmlClass,
association,
classOptions
)
} }
} }
} }
@ -337,41 +326,23 @@ export function addAssociationsToDot(
return dotString return dotString
} }
function addAssociationToDot( function addAssociationToDot(sourceUmlClass: UmlClass, targetUmlClass: UmlClass, association: Association, classOptions: ClassOptions = {}): string {
sourceUmlClass: UmlClass,
targetUmlClass: UmlClass,
association: Association,
classOptions: ClassOptions = {}
): string {
// do not include library or interface associations if hidden // do not include library or interface associations if hidden
// Or associations to Structs, Enums or Constants if they are hidden // Or associations to Structs, Enums or Constants if they are hidden
if ( if (
(classOptions.hideLibraries && (classOptions.hideLibraries && (sourceUmlClass.stereotype === ClassStereotype.Library || targetUmlClass.stereotype === ClassStereotype.Library)) ||
(sourceUmlClass.stereotype === ClassStereotype.Library || (classOptions.hideInterfaces && (targetUmlClass.stereotype === ClassStereotype.Interface || sourceUmlClass.stereotype === ClassStereotype.Interface)) ||
targetUmlClass.stereotype === ClassStereotype.Library)) || (classOptions.hideAbstracts && (targetUmlClass.stereotype === ClassStereotype.Abstract || sourceUmlClass.stereotype === ClassStereotype.Abstract)) ||
(classOptions.hideInterfaces && (classOptions.hideStructs && targetUmlClass.stereotype === ClassStereotype.Struct) ||
(targetUmlClass.stereotype === ClassStereotype.Interface || (classOptions.hideEnums && targetUmlClass.stereotype === ClassStereotype.Enum) ||
sourceUmlClass.stereotype === ClassStereotype.Interface)) || (classOptions.hideConstants && targetUmlClass.stereotype === ClassStereotype.Constant)
(classOptions.hideAbstracts &&
(targetUmlClass.stereotype === ClassStereotype.Abstract ||
sourceUmlClass.stereotype === ClassStereotype.Abstract)) ||
(classOptions.hideStructs &&
targetUmlClass.stereotype === ClassStereotype.Struct) ||
(classOptions.hideEnums &&
targetUmlClass.stereotype === ClassStereotype.Enum) ||
(classOptions.hideConstants &&
targetUmlClass.stereotype === ClassStereotype.Constant)
) { ) {
return '' return ''
} }
let dotString = `\n${sourceUmlClass.id} -> ${targetUmlClass.id} [` let dotString = `\n${sourceUmlClass.id} -> ${targetUmlClass.id} [`
if ( if (association.referenceType == ReferenceType.Memory || (association.realization && targetUmlClass.stereotype === ClassStereotype.Interface)) {
association.referenceType == ReferenceType.Memory ||
(association.realization &&
targetUmlClass.stereotype === ClassStereotype.Interface)
) {
dotString += 'style=dashed, ' dotString += 'style=dashed, '
} }

@ -1,30 +1,30 @@
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { AppModal, AlertModal, ModalTypes } from '@remix-ui/app' import {AppModal, AlertModal, ModalTypes} from '@remix-ui/app'
import { Blockchain } from '../../blockchain/blockchain' import {Blockchain} from '../../blockchain/blockchain'
import { ethers } from 'ethers' import {ethers} from 'ethers'
export type JsonDataRequest = { export type JsonDataRequest = {
id: number, id: number
jsonrpc: string // version jsonrpc: string // version
method: string, method: string
params: Array<any>, params: Array<any>
} }
export type JsonDataResult = { export type JsonDataResult = {
id: number, id: number
jsonrpc: string // version jsonrpc: string // version
result?: any, result?: any
error?: any, error?: any
} }
export type RejectRequest = (error: Error) => void export type RejectRequest = (error: Error) => void
export type SuccessRequest = (data: JsonDataResult) => void export type SuccessRequest = (data: JsonDataResult) => void
export interface IProvider { export interface IProvider {
options: { [id: string] : any } options: {[id: string]: any}
init(): Promise<{ [id: string] : any }> init(): Promise<{[id: string]: any}>
body(): JSX.Element body(): JSX.Element
sendAsync (data: JsonDataRequest): Promise<JsonDataResult> sendAsync(data: JsonDataRequest): Promise<JsonDataResult>
} }
export abstract class AbstractProvider extends Plugin implements IProvider { export abstract class AbstractProvider extends Plugin implements IProvider {
@ -33,9 +33,9 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
defaultUrl: string defaultUrl: string
connected: boolean connected: boolean
nodeUrl: string nodeUrl: string
options: { [id: string] : any } = {} options: {[id: string]: any} = {}
constructor (profile, blockchain, defaultUrl) { constructor(profile, blockchain, defaultUrl) {
super(profile) super(profile)
this.defaultUrl = defaultUrl this.defaultUrl = defaultUrl
this.provider = null this.provider = null
@ -46,11 +46,11 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
abstract body(): JSX.Element abstract body(): JSX.Element
onDeactivation () { onDeactivation() {
this.provider = null this.provider = null
} }
async init () { async init() {
this.nodeUrl = await ((): Promise<string> => { this.nodeUrl = await ((): Promise<string> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const modalContent: AppModal = { const modalContent: AppModal = {
@ -61,15 +61,15 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
okLabel: 'OK', okLabel: 'OK',
cancelLabel: 'Cancel', cancelLabel: 'Cancel',
validationFn: (value) => { validationFn: (value) => {
if (!value) return { valid: false, message: "value is empty" } if (!value) return {valid: false, message: 'value is empty'}
if (value.startsWith('https://') || value.startsWith('http://')) { if (value.startsWith('https://') || value.startsWith('http://')) {
return { return {
valid: true, valid: true,
message: '' message: ''
} }
} else { } else {
return { return {
valid: false, valid: false,
message: 'the provided value should contain the protocol ( e.g starts with http:// or https:// )' message: 'the provided value should contain the protocol ( e.g starts with http:// or https:// )'
} }
} }
@ -94,7 +94,7 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
} }
} }
sendAsync (data: JsonDataRequest): Promise<JsonDataResult> { sendAsync(data: JsonDataRequest): Promise<JsonDataResult> {
// eslint-disable-next-line no-async-promise-executor // eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
if (!this.provider) return reject(new Error('provider node set')) if (!this.provider) return reject(new Error('provider node set'))
@ -102,7 +102,7 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
}) })
} }
private async switchAway (showError) { private async switchAway(showError) {
if (!this.provider) return if (!this.provider) return
this.provider = null this.provider = null
this.connected = false this.connected = false
@ -110,19 +110,19 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
const modalContent: AlertModal = { const modalContent: AlertModal = {
id: this.profile.name, id: this.profile.name,
title: this.profile.displayName, title: this.profile.displayName,
message: `Error while connecting to the provider, provider not connected`, message: `Error while connecting to the provider, provider not connected`
} }
this.call('notification', 'alert', modalContent) this.call('notification', 'alert', modalContent)
} }
await this.call('udapp', 'setEnvironmentMode', { context: 'vm-merge'}) await this.call('udapp', 'setEnvironmentMode', {context: 'vm-merge'})
return return
} }
private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> { private async sendAsyncInternal(data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> {
if (this.provider) { if (this.provider) {
try { try {
const result = await this.provider.send(data.method, data.params) const result = await this.provider.send(data.method, data.params)
resolve({ jsonrpc: '2.0', result, id: data.id }) resolve({jsonrpc: '2.0', result, id: data.id})
} catch (error) { } catch (error) {
if (error && error.message && error.message.includes('net_version') && error.message.includes('SERVER_ERROR')) { if (error && error.message && error.message.includes('net_version') && error.message.includes('SERVER_ERROR')) {
this.switchAway(true) this.switchAway(true)
@ -131,7 +131,7 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
} }
} else { } else {
const result = data.method === 'net_listening' ? 'canceled' : [] const result = data.method === 'net_listening' ? 'canceled' : []
resolve({ jsonrpc: '2.0', result: result, id: data.id }) resolve({jsonrpc: '2.0', result: result, id: data.id})
} }
} }
} }

@ -1,23 +1,26 @@
import React, { useRef } from 'react' // eslint-disable-line import React, {useRef} from 'react' // eslint-disable-line
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { AppModal, ModalTypes } from '@remix-ui/app' import {AppModal, ModalTypes} from '@remix-ui/app'
import { BasicVMProvider } from './vm-provider' import {BasicVMProvider} from './vm-provider'
import { Hardfork } from '@ethereumjs/common' import {Hardfork} from '@ethereumjs/common'
export class CustomForkVMProvider extends BasicVMProvider { export class CustomForkVMProvider extends BasicVMProvider {
nodeUrl: string nodeUrl: string
blockNumber: number | 'latest' blockNumber: number | 'latest'
inputs: any inputs: any
constructor (blockchain) { constructor(blockchain) {
super({ super(
name: 'vm-custom-fork', {
displayName: 'Custom fork - Remix VM', name: 'vm-custom-fork',
kind: 'provider', displayName: 'Custom fork - Remix VM',
description: 'Custom fork - Remix VM', kind: 'provider',
methods: ['sendAsync', 'init'], description: 'Custom fork - Remix VM',
version: packageJson.version methods: ['sendAsync', 'init'],
}, blockchain) version: packageJson.version
},
blockchain
)
this.blockchain = blockchain this.blockchain = blockchain
this.fork = '' this.fork = ''
this.nodeUrl = '' this.nodeUrl = ''
@ -25,28 +28,41 @@ export class CustomForkVMProvider extends BasicVMProvider {
this.inputs = {} this.inputs = {}
} }
async init () { async init() {
const body = () => { const body = () => {
return <div> return (
<span>Please provide information about the custom fork. If the node URL is not provided, the VM will start with an empty state.</span>
<div> <div>
<label className="mt-3 mb-1">Node URL</label> <span>Please provide information about the custom fork. If the node URL is not provided, the VM will start with an empty state.</span>
<input data-id="CustomForkNodeUrl" name="nodeUrl" type="text" className="border form-control border-right-0" /> <div>
<label className="mt-3 mb-1">Node URL</label>
<input data-id="CustomForkNodeUrl" name="nodeUrl" type="text" className="border form-control border-right-0" />
</div>
<div>
<label className="mt-3 mb-1">Block number (or "latest")</label>
<input
data-id="CustomForkBlockNumber"
name="blockNumber"
type="text"
defaultValue="latest"
placeholder='block number or "latest"'
className="border form-control border-right-0"
/>
</div>
<div>
<label className="mt-3 mb-1">EVM</label>
<select data-id="CustomForkEvmType" name="evmType" defaultValue="merge" className="border form-control border-right-0">
{Object.keys(Hardfork).map((value, index) => {
return (
<option value={Hardfork[value]} key={index}>
{value}
</option>
)
})}
</select>
</div>
</div> </div>
<div> )
<label className="mt-3 mb-1">Block number (or "latest")</label> }
<input data-id="CustomForkBlockNumber" name="blockNumber" type="text" defaultValue="latest" placeholder='block number or "latest"' className="border form-control border-right-0" />
</div>
<div>
<label className="mt-3 mb-1">EVM</label>
<select data-id="CustomForkEvmType" name="evmType" defaultValue="merge" className="border form-control border-right-0">
{Object.keys(Hardfork).map((value, index) => {
return <option value={Hardfork[value]} key={index}>{value}</option>
})}
</select>
</div>
</div>
}
const result = await ((): Promise<any> => { const result = await ((): Promise<any> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const modalContent: AppModal = { const modalContent: AppModal = {
@ -54,7 +70,7 @@ export class CustomForkVMProvider extends BasicVMProvider {
title: this.profile.displayName, title: this.profile.displayName,
message: body(), message: body(),
validationFn: (data: any) => { validationFn: (data: any) => {
if(data.nodeUrl !== '' && !data.nodeUrl.startsWith("http")) { if (data.nodeUrl !== '' && !data.nodeUrl.startsWith('http')) {
return { return {
valid: false, valid: false,
message: 'node URL should be a valid URL' message: 'node URL should be a valid URL'
@ -96,11 +112,11 @@ export class CustomForkVMProvider extends BasicVMProvider {
this.nodeUrl = undefined this.nodeUrl = undefined
this.blockNumber = undefined this.blockNumber = undefined
} }
return { return {
'fork': this.fork, fork: this.fork,
'nodeUrl': this.nodeUrl, nodeUrl: this.nodeUrl,
'blockNumber': this.blockNumber blockNumber: this.blockNumber
} }
} }
} }

@ -1,6 +1,6 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { AbstractProvider } from './abstract-provider' import {AbstractProvider} from './abstract-provider'
const profile = { const profile = {
name: 'basic-http-provider', name: 'basic-http-provider',
@ -12,25 +12,38 @@ const profile = {
} }
export class ExternalHttpProvider extends AbstractProvider { export class ExternalHttpProvider extends AbstractProvider {
constructor (blockchain) { constructor(blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545') super(profile, blockchain, 'http://127.0.0.1:8545')
} }
body (): JSX.Element { body(): JSX.Element {
const thePath = '<path/to/local/folder/for/test/chain>' const thePath = '<path/to/local/folder/for/test/chain>'
return ( return (
<> <>
<div className=""> <div className="">
Note: To use Geth & https://remix.ethereum.org, configure it to allow requests from Remix:(see <a href="https://geth.ethereum.org/docs/rpc/server" target="_blank" rel="noreferrer">Geth Docs on rpc server</a>) Note: To use Geth & https://remix.ethereum.org, configure it to allow requests from Remix:(see{' '}
<div className="border p-1">geth --http --http.corsdomain https://remix.ethereum.org</div> <a href="https://geth.ethereum.org/docs/rpc/server" target="_blank" rel="noreferrer">
Geth Docs on rpc server
</a>
)<div className="border p-1">geth --http --http.corsdomain https://remix.ethereum.org</div>
<br /> <br />
To run Remix & a local Geth test node, use this command: (see <a href="https://geth.ethereum.org/getting-started/dev-mode" target="_blank" rel="noreferrer">Geth Docs on Dev mode</a>) To run Remix & a local Geth test node, use this command: (see{' '}
<div className="border p-1">geth --http --http.corsdomain="{window.origin}" --http.api web3,eth,debug,personal,net --vmdebug --datadir {thePath} --dev console</div> <a href="https://geth.ethereum.org/getting-started/dev-mode" target="_blank" rel="noreferrer">
Geth Docs on Dev mode
</a>
)
<div className="border p-1">
geth --http --http.corsdomain="{window.origin}" --http.api web3,eth,debug,personal,net --vmdebug --datadir {thePath} --dev console
</div>
<br /> <br />
<br /> <br />
<b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b> <b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b>
<br /> <br />
<br />For more info: <a href="https://remix-ide.readthedocs.io/en/latest/run.html#more-about-web3-provider" target="_blank" rel="noreferrer">Remix Docs on External HTTP Provider</a> <br />
For more info:{' '}
<a href="https://remix-ide.readthedocs.io/en/latest/run.html#more-about-web3-provider" target="_blank" rel="noreferrer">
Remix Docs on External HTTP Provider
</a>
<br /> <br />
<br /> <br />
External HTTP Provider Endpoint External HTTP Provider Endpoint
@ -38,4 +51,4 @@ export class ExternalHttpProvider extends AbstractProvider {
</> </>
) )
} }
} }

@ -1,6 +1,6 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { AbstractProvider } from './abstract-provider' import {AbstractProvider} from './abstract-provider'
const profile = { const profile = {
name: 'foundry-provider', name: 'foundry-provider',
@ -12,20 +12,29 @@ const profile = {
} }
export class FoundryProvider extends AbstractProvider { export class FoundryProvider extends AbstractProvider {
constructor (blockchain) { constructor(blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545') super(profile, blockchain, 'http://127.0.0.1:8545')
} }
body (): JSX.Element { body(): JSX.Element {
return ( return (
<div> Note: To run Anvil on your system, run: <div>
<div className="p-1 pl-3"><b>curl -L https://foundry.paradigm.xyz | bash</b></div> {' '}
<div className="p-1 pl-3"><b>anvil</b></div> Note: To run Anvil on your system, run:
<div className="p-1 pl-3">
<b>curl -L https://foundry.paradigm.xyz | bash</b>
</div>
<div className="p-1 pl-3">
<b>anvil</b>
</div>
<div className="pt-2 pb-4"> <div className="pt-2 pb-4">
For more info, visit: <a href="https://github.com/foundry-rs/foundry" target="_blank">Foundry Documentation</a> For more info, visit:{' '}
<a href="https://github.com/foundry-rs/foundry" target="_blank">
Foundry Documentation
</a>
</div> </div>
<div>Anvil JSON-RPC Endpoint:</div> <div>Anvil JSON-RPC Endpoint:</div>
</div> </div>
) )
} }
} }

@ -1,6 +1,6 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { AbstractProvider } from './abstract-provider' import {AbstractProvider} from './abstract-provider'
const profile = { const profile = {
name: 'ganache-provider', name: 'ganache-provider',
@ -12,20 +12,29 @@ const profile = {
} }
export class GanacheProvider extends AbstractProvider { export class GanacheProvider extends AbstractProvider {
constructor (blockchain) { constructor(blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545') super(profile, blockchain, 'http://127.0.0.1:8545')
} }
body (): JSX.Element { body(): JSX.Element {
return ( return (
<div> Note: To run Ganache on your system, run: <div>
<div className="p-1 pl-3"><b>yarn global add ganache</b></div> {' '}
<div className="p-1 pl-3"><b>ganache</b></div> Note: To run Ganache on your system, run:
<div className="p-1 pl-3">
<b>yarn global add ganache</b>
</div>
<div className="p-1 pl-3">
<b>ganache</b>
</div>
<div className="pt-2 pb-4"> <div className="pt-2 pb-4">
For more info, visit: <a href="https://github.com/trufflesuite/ganache" target="_blank">Ganache Documentation</a> For more info, visit:{' '}
<a href="https://github.com/trufflesuite/ganache" target="_blank">
Ganache Documentation
</a>
</div> </div>
<div>Ganache JSON-RPC Endpoint:</div> <div>Ganache JSON-RPC Endpoint:</div>
</div> </div>
) )
} }
} }

@ -1,29 +1,32 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { BasicVMProvider } from './vm-provider' import {BasicVMProvider} from './vm-provider'
export class GoerliForkVMProvider extends BasicVMProvider { export class GoerliForkVMProvider extends BasicVMProvider {
nodeUrl: string nodeUrl: string
blockNumber: number | 'latest' blockNumber: number | 'latest'
constructor (blockchain) { constructor(blockchain) {
super({ super(
name: 'vm-goerli-fork', {
displayName: 'Goerli fork - Remix VM (London)', name: 'vm-goerli-fork',
kind: 'provider', displayName: 'Goerli fork - Remix VM (London)',
description: 'Remix VM (London)', kind: 'provider',
methods: ['sendAsync', 'init'], description: 'Remix VM (London)',
version: packageJson.version methods: ['sendAsync', 'init'],
}, blockchain) version: packageJson.version
},
blockchain
)
this.blockchain = blockchain this.blockchain = blockchain
this.fork = 'shanghai' this.fork = 'shanghai'
this.nodeUrl = 'https://remix-sepolia.ethdevops.io' this.nodeUrl = 'https://remix-sepolia.ethdevops.io'
this.blockNumber = 'latest' this.blockNumber = 'latest'
} }
async init () { async init() {
return { return {
'fork': this.fork, fork: this.fork,
'nodeUrl': this.nodeUrl, nodeUrl: this.nodeUrl,
'blockNumber': this.blockNumber blockNumber: this.blockNumber
} }
} }
} }

@ -1,6 +1,6 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { AbstractProvider } from './abstract-provider' import {AbstractProvider} from './abstract-provider'
const profile = { const profile = {
name: 'hardhat-provider', name: 'hardhat-provider',
@ -12,19 +12,26 @@ const profile = {
} }
export class HardhatProvider extends AbstractProvider { export class HardhatProvider extends AbstractProvider {
constructor (blockchain) { constructor(blockchain) {
super(profile, blockchain, 'http://127.0.0.1:8545') super(profile, blockchain, 'http://127.0.0.1:8545')
} }
body (): JSX.Element { body(): JSX.Element {
return ( return (
<div> Note: To run Hardhat network node on your system, go to hardhat project folder and run command: <div>
<div className="p-1 pl-3"><b>npx hardhat node</b></div> {' '}
Note: To run Hardhat network node on your system, go to hardhat project folder and run command:
<div className="p-1 pl-3">
<b>npx hardhat node</b>
</div>
<div className="pt-2 pb-4"> <div className="pt-2 pb-4">
For more info, visit: <a href="https://hardhat.org/getting-started/#connecting-a-wallet-or-dapp-to-hardhat-network" target="_blank">Hardhat Documentation</a> For more info, visit:{' '}
<a href="https://hardhat.org/getting-started/#connecting-a-wallet-or-dapp-to-hardhat-network" target="_blank">
Hardhat Documentation
</a>
</div> </div>
<div>Hardhat JSON-RPC Endpoint:</div> <div>Hardhat JSON-RPC Endpoint:</div>
</div> </div>
) )
} }
} }

@ -1,32 +1,31 @@
import { InjectedProviderDefaultBase } from './injected-provider-default' import {InjectedProviderDefaultBase} from './injected-provider-default'
export class InjectedL2Provider extends InjectedProviderDefaultBase { export class InjectedL2Provider extends InjectedProviderDefaultBase {
chainName: string chainName: string
chainId: string chainId: string
rpcUrls: Array<string> rpcUrls: Array<string>
constructor (profile: any, chainName: string, chainId: string, rpcUrls: Array<string>) { constructor(profile: any, chainName: string, chainId: string, rpcUrls: Array<string>) {
super(profile) super(profile)
this.chainName = chainName this.chainName = chainName
this.chainId = chainId this.chainId = chainId
this.rpcUrls = rpcUrls this.rpcUrls = rpcUrls
} }
async init () { async init() {
await super.init() await super.init()
if (this.chainName && this.rpcUrls && this.rpcUrls.length > 0) await addL2Network(this.chainName, this.chainId, this.rpcUrls) if (this.chainName && this.rpcUrls && this.rpcUrls.length > 0) await addL2Network(this.chainName, this.chainId, this.rpcUrls)
else else throw new Error('Cannot add the L2 network to main injected provider')
throw new Error('Cannot add the L2 network to main injected provider')
return {} return {}
} }
} }
export const addL2Network = async (chainName: string, chainId: string, rpcUrls: Array<string>) => { export const addL2Network = async (chainName: string, chainId: string, rpcUrls: Array<string>) => {
try { try {
await (window as any).ethereum.request({ await (window as any).ethereum.request({
method: 'wallet_switchEthereumChain', method: 'wallet_switchEthereumChain',
params: [{ chainId: chainId }], params: [{chainId: chainId}]
}); })
} catch (switchError) { } catch (switchError) {
// This error code indicates that the chain has not been added to MetaMask. // This error code indicates that the chain has not been added to MetaMask.
if (switchError.code === 4902) { if (switchError.code === 4902) {
@ -37,19 +36,19 @@ export const addL2Network = async (chainName: string, chainId: string, rpcUrls:
{ {
chainId: chainId, chainId: chainId,
chainName: chainName, chainName: chainName,
rpcUrls: rpcUrls, rpcUrls: rpcUrls
}, }
], ]
}); })
await (window as any).ethereum.request({ await (window as any).ethereum.request({
method: 'wallet_switchEthereumChain', method: 'wallet_switchEthereumChain',
params: [{ chainId: chainId }], params: [{chainId: chainId}]
}); })
} catch (addError) { } catch (addError) {
// handle "add" error // handle "add" error
} }
} }
// handle other "switch" errors // handle other "switch" errors
} }
} }

@ -1,5 +1,5 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { InjectedL2Provider } from './injected-L2-provider' import {InjectedL2Provider} from './injected-L2-provider'
const profile = { const profile = {
name: 'injected-arbitrum-one-provider', name: 'injected-arbitrum-one-provider',
@ -11,8 +11,7 @@ const profile = {
} }
export class InjectedArbitrumOneProvider extends InjectedL2Provider { export class InjectedArbitrumOneProvider extends InjectedL2Provider {
constructor() {
constructor () {
super(profile, 'Arbitrum One', '0xa4b1', ['https://arb1.arbitrum.io/rpc']) super(profile, 'Arbitrum One', '0xa4b1', ['https://arb1.arbitrum.io/rpc'])
} }
} }

@ -1,5 +1,5 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { InjectedL2Provider } from './injected-L2-provider' import {InjectedL2Provider} from './injected-L2-provider'
const profile = { const profile = {
name: 'injected-optimism-provider', name: 'injected-optimism-provider',
@ -11,8 +11,7 @@ const profile = {
} }
export class Injected0ptimismProvider extends InjectedL2Provider { export class Injected0ptimismProvider extends InjectedL2Provider {
constructor() {
constructor () {
super(profile, 'Optimism', '0xa', ['https://mainnet.optimism.io']) super(profile, 'Optimism', '0xa', ['https://mainnet.optimism.io'])
} }
} }

@ -1,25 +1,25 @@
/* global ethereum */ /* global ethereum */
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { InjectedProvider } from './injected-provider' import {InjectedProvider} from './injected-provider'
export class InjectedProviderDefaultBase extends InjectedProvider { export class InjectedProviderDefaultBase extends InjectedProvider {
constructor (profile) { constructor(profile) {
super(profile) super(profile)
} }
async init () { async init() {
const injectedProvider = this.getInjectedProvider() const injectedProvider = this.getInjectedProvider()
if (injectedProvider && injectedProvider._metamask && injectedProvider._metamask.isUnlocked) { if (injectedProvider && injectedProvider._metamask && injectedProvider._metamask.isUnlocked) {
if (!await injectedProvider._metamask.isUnlocked()) this.call('notification', 'toast', 'Please make sure the injected provider is unlocked (e.g Metamask).') if (!(await injectedProvider._metamask.isUnlocked())) this.call('notification', 'toast', 'Please make sure the injected provider is unlocked (e.g Metamask).')
} }
return super.init() return super.init()
} }
getInjectedProvider () { getInjectedProvider() {
return (window as any).ethereum return (window as any).ethereum
} }
notFound () { notFound() {
return 'No injected provider found. Make sure your provider (e.g. MetaMask, ...) is active and running (when recently activated you may have to reload the page).' return 'No injected provider found. Make sure your provider (e.g. MetaMask, ...) is active and running (when recently activated you may have to reload the page).'
} }
} }
@ -34,7 +34,7 @@ const profile = {
} }
export class InjectedProviderDefault extends InjectedProviderDefaultBase { export class InjectedProviderDefault extends InjectedProviderDefaultBase {
constructor () { constructor() {
super(profile) super(profile)
} }
} }

@ -1,6 +1,6 @@
/* global ethereum */ /* global ethereum */
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { InjectedProvider } from './injected-provider' import {InjectedProvider} from './injected-provider'
const profile = { const profile = {
name: 'injected-trustwallet', name: 'injected-trustwallet',
@ -11,16 +11,16 @@ const profile = {
version: packageJson.version version: packageJson.version
} }
export class InjectedProviderTrustWallet extends InjectedProvider { export class InjectedProviderTrustWallet extends InjectedProvider {
constructor () { constructor() {
super(profile) super(profile)
} }
getInjectedProvider () { getInjectedProvider() {
return (window as any).trustwallet return (window as any).trustwallet
} }
notFound () { notFound() {
return 'Could not find Trust Wallet provider. Please make sure the Trust Wallet extension is active. Download the latest version from https://trustwallet.com/browser-extension' return 'Could not find Trust Wallet provider. Please make sure the Trust Wallet extension is active. Download the latest version from https://trustwallet.com/browser-extension'
} }
} }

@ -1,15 +1,15 @@
/* global ethereum */ /* global ethereum */
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { JsonDataRequest, RejectRequest, SuccessRequest } from '../providers/abstract-provider' import {JsonDataRequest, RejectRequest, SuccessRequest} from '../providers/abstract-provider'
import { IProvider } from './abstract-provider' import {IProvider} from './abstract-provider'
export abstract class InjectedProvider extends Plugin implements IProvider { export abstract class InjectedProvider extends Plugin implements IProvider {
options: { [id: string] : any } = {} options: {[id: string]: any} = {}
listenerAccountsChanged: (accounts: Array<string>) => void listenerAccountsChanged: (accounts: Array<string>) => void
listenerChainChanged: (chainId: number) => void listenerChainChanged: (chainId: number) => void
constructor (profile) { constructor(profile) {
super(profile) super(profile)
this.listenerAccountsChanged = (accounts: Array<string>) => { this.listenerAccountsChanged = (accounts: Array<string>) => {
this.emit('accountsChanged', accounts) this.emit('accountsChanged', accounts)
@ -25,8 +25,8 @@ export abstract class InjectedProvider extends Plugin implements IProvider {
onActivation(): void { onActivation(): void {
try { try {
const web3Provider = this.getInjectedProvider() const web3Provider = this.getInjectedProvider()
web3Provider.on('accountsChanged', this.listenerAccountsChanged); web3Provider.on('accountsChanged', this.listenerAccountsChanged)
web3Provider.on('chainChanged', this.listenerChainChanged); web3Provider.on('chainChanged', this.listenerChainChanged)
} catch (error) { } catch (error) {
console.log('unable to listen on context changed') console.log('unable to listen on context changed')
} }
@ -42,22 +42,20 @@ export abstract class InjectedProvider extends Plugin implements IProvider {
} }
} }
askPermission (throwIfNoInjectedProvider) { askPermission(throwIfNoInjectedProvider) {
const web3Provider = this.getInjectedProvider() const web3Provider = this.getInjectedProvider()
if (typeof web3Provider !== "undefined" && typeof web3Provider.request === "function") { if (typeof web3Provider !== 'undefined' && typeof web3Provider.request === 'function') {
web3Provider.request({ method: "eth_requestAccounts" }) web3Provider.request({method: 'eth_requestAccounts'})
} else if (throwIfNoInjectedProvider) { } else if (throwIfNoInjectedProvider) {
throw new Error(this.notFound()) throw new Error(this.notFound())
} }
} }
body (): JSX.Element { body(): JSX.Element {
return ( return <div></div>
<div></div>
)
} }
async init () { async init() {
const injectedProvider = this.getInjectedProvider() const injectedProvider = this.getInjectedProvider()
if (injectedProvider === undefined) { if (injectedProvider === undefined) {
this.call('notification', 'toast', this.notFound()) this.call('notification', 'toast', this.notFound())
@ -68,38 +66,46 @@ export abstract class InjectedProvider extends Plugin implements IProvider {
return {} return {}
} }
sendAsync (data: JsonDataRequest): Promise<any> { sendAsync(data: JsonDataRequest): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.sendAsyncInternal(data, resolve, reject) this.sendAsyncInternal(data, resolve, reject)
}) })
} }
private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> { private async sendAsyncInternal(data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> {
// Check the case where current environment is VM on UI and it still sends RPC requests // Check the case where current environment is VM on UI and it still sends RPC requests
// This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!' // This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!'
const web3Provider = this.getInjectedProvider() const web3Provider = this.getInjectedProvider()
if (!web3Provider) { if (!web3Provider) {
this.call('notification', 'toast', 'No injected provider (e.g Metamask) has been found.') this.call('notification', 'toast', 'No injected provider (e.g Metamask) has been found.')
return resolve({ jsonrpc: '2.0', error: 'no injected provider found', id: data.id }) return resolve({
jsonrpc: '2.0',
error: 'no injected provider found',
id: data.id
})
} }
try { try {
let resultData let resultData
if (web3Provider.request) resultData = await web3Provider.request({ method: data.method, params: data.params}) if (web3Provider.request) resultData = await web3Provider.request({method: data.method, params: data.params})
else if (web3Provider.send) resultData = await web3Provider.send(data.method, data.params) else if (web3Provider.send) resultData = await web3Provider.send(data.method, data.params)
else { else {
resolve({ jsonrpc: '2.0', error: 'provider not valid', id: data.id }) resolve({jsonrpc: '2.0', error: 'provider not valid', id: data.id})
return return
} }
if (resultData) { if (resultData) {
if (resultData.jsonrpc && resultData.jsonrpc === '2.0') { if (resultData.jsonrpc && resultData.jsonrpc === '2.0') {
resultData = resultData.result resultData = resultData.result
} }
resolve({ jsonrpc: '2.0', result: resultData, id: data.id }) resolve({jsonrpc: '2.0', result: resultData, id: data.id})
} else { } else {
resolve({ jsonrpc: '2.0', error: 'no return data provided', id: data.id }) resolve({jsonrpc: '2.0', error: 'no return data provided', id: data.id})
} }
} catch (error) { } catch (error) {
resolve({ jsonrpc: '2.0', error: error.data && error.data.message ? error.data.message : error.message, id: data.id }) resolve({
jsonrpc: '2.0',
error: error.data && error.data.message ? error.data.message : error.message,
id: data.id
})
} }
} }
} }

@ -1,29 +1,32 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { BasicVMProvider } from './vm-provider' import {BasicVMProvider} from './vm-provider'
export class MainnetForkVMProvider extends BasicVMProvider { export class MainnetForkVMProvider extends BasicVMProvider {
nodeUrl: string nodeUrl: string
blockNumber: number | 'latest' blockNumber: number | 'latest'
constructor (blockchain) { constructor(blockchain) {
super({ super(
name: 'vm-mainnet-fork', {
displayName: 'Mainet fork -Remix VM (London)', name: 'vm-mainnet-fork',
kind: 'provider', displayName: 'Mainet fork -Remix VM (London)',
description: 'Remix VM (London)', kind: 'provider',
methods: ['sendAsync', 'init'], description: 'Remix VM (London)',
version: packageJson.version methods: ['sendAsync', 'init'],
}, blockchain) version: packageJson.version
},
blockchain
)
this.blockchain = blockchain this.blockchain = blockchain
this.fork = 'shanghai' this.fork = 'shanghai'
this.nodeUrl = 'https://mainnet.infura.io/v3/08b2a484451e4635a28b3d8234f24332' this.nodeUrl = 'https://mainnet.infura.io/v3/08b2a484451e4635a28b3d8234f24332'
this.blockNumber = 'latest' this.blockNumber = 'latest'
} }
async init () { async init() {
return { return {
'fork': this.fork, fork: this.fork,
'nodeUrl': this.nodeUrl, nodeUrl: this.nodeUrl,
'blockNumber': this.blockNumber blockNumber: this.blockNumber
} }
} }
} }

@ -1,29 +1,32 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { BasicVMProvider } from './vm-provider' import {BasicVMProvider} from './vm-provider'
export class SepoliaForkVMProvider extends BasicVMProvider { export class SepoliaForkVMProvider extends BasicVMProvider {
nodeUrl: string nodeUrl: string
blockNumber: number | 'latest' blockNumber: number | 'latest'
constructor (blockchain) { constructor(blockchain) {
super({ super(
name: 'vm-sepolia-fork', {
displayName: 'Sepolia fork - Remix VM (London)', name: 'vm-sepolia-fork',
kind: 'provider', displayName: 'Sepolia fork - Remix VM (London)',
description: 'Remix VM (London)', kind: 'provider',
methods: ['sendAsync', 'init'], description: 'Remix VM (London)',
version: packageJson.version methods: ['sendAsync', 'init'],
}, blockchain) version: packageJson.version
},
blockchain
)
this.blockchain = blockchain this.blockchain = blockchain
this.fork = 'shanghai' this.fork = 'shanghai'
this.nodeUrl = 'https://remix-sepolia.ethdevops.io' this.nodeUrl = 'https://remix-sepolia.ethdevops.io'
this.blockNumber = 'latest' this.blockNumber = 'latest'
} }
async init () { async init() {
return { return {
'fork': this.fork, fork: this.fork,
'nodeUrl': this.nodeUrl, nodeUrl: this.nodeUrl,
'blockNumber': this.blockNumber blockNumber: this.blockNumber
} }
} }
} }

@ -1,41 +1,41 @@
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { JsonDataRequest, RejectRequest, SuccessRequest } from '../providers/abstract-provider' import {JsonDataRequest, RejectRequest, SuccessRequest} from '../providers/abstract-provider'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { IProvider } from './abstract-provider' import {IProvider} from './abstract-provider'
export class BasicVMProvider extends Plugin implements IProvider { export class BasicVMProvider extends Plugin implements IProvider {
blockchain blockchain
fork: string fork: string
options: { [id: string] : any } = {} options: {[id: string]: any} = {}
constructor (profile, blockchain) { constructor(profile, blockchain) {
super(profile) super(profile)
this.blockchain = blockchain this.blockchain = blockchain
this.fork = '' this.fork = ''
} }
async init (): Promise<{ [id: string] : any }> { return {} } async init(): Promise<{[id: string]: any}> {
return {}
}
body (): JSX.Element { body(): JSX.Element {
return ( return <div></div>
<div></div>
)
} }
sendAsync (data: JsonDataRequest): Promise<any> { sendAsync(data: JsonDataRequest): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.sendAsyncInternal(data, resolve, reject) this.sendAsyncInternal(data, resolve, reject)
}) })
} }
private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> { private async sendAsyncInternal(data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> {
try { try {
await this.blockchain.providers.vm.provider.sendAsync(data, (error, result) => { await this.blockchain.providers.vm.provider.sendAsync(data, (error, result) => {
if (error) return reject(error) if (error) return reject(error)
else { else {
resolve({ jsonrpc: '2.0', result, id: data.id }) resolve({jsonrpc: '2.0', result, id: data.id})
} }
}) })
} catch (error) { } catch (error) {
reject(error) reject(error)
} }
@ -43,61 +43,73 @@ export class BasicVMProvider extends Plugin implements IProvider {
} }
export class MergeVMProvider extends BasicVMProvider { export class MergeVMProvider extends BasicVMProvider {
constructor (blockchain) { constructor(blockchain) {
super({ super(
name: 'vm-merge', {
displayName: 'Remix VM (Merge)', name: 'vm-merge',
kind: 'provider', displayName: 'Remix VM (Merge)',
description: 'Remix VM (Merge)', kind: 'provider',
methods: ['sendAsync', 'init'], description: 'Remix VM (Merge)',
version: packageJson.version methods: ['sendAsync', 'init'],
}, blockchain) version: packageJson.version
},
blockchain
)
this.blockchain = blockchain this.blockchain = blockchain
this.fork = 'merge' this.fork = 'merge'
} }
} }
export class LondonVMProvider extends BasicVMProvider { export class LondonVMProvider extends BasicVMProvider {
constructor (blockchain) { constructor(blockchain) {
super({ super(
name: 'vm-london', {
displayName: 'Remix VM (London)', name: 'vm-london',
kind: 'provider', displayName: 'Remix VM (London)',
description: 'Remix VM (London)', kind: 'provider',
methods: ['sendAsync', 'init'], description: 'Remix VM (London)',
version: packageJson.version methods: ['sendAsync', 'init'],
}, blockchain) version: packageJson.version
},
blockchain
)
this.blockchain = blockchain this.blockchain = blockchain
this.fork = 'london' this.fork = 'london'
} }
} }
export class BerlinVMProvider extends BasicVMProvider { export class BerlinVMProvider extends BasicVMProvider {
constructor (blockchain) { constructor(blockchain) {
super({ super(
name: 'vm-berlin', {
displayName: 'Remix VM (Berlin)', name: 'vm-berlin',
kind: 'provider', displayName: 'Remix VM (Berlin)',
description: 'Remix VM (Berlin)', kind: 'provider',
methods: ['sendAsync', 'init'], description: 'Remix VM (Berlin)',
version: packageJson.version methods: ['sendAsync', 'init'],
}, blockchain) version: packageJson.version
},
blockchain
)
this.blockchain = blockchain this.blockchain = blockchain
this.fork = 'berlin' this.fork = 'berlin'
} }
} }
export class ShanghaiVMProvider extends BasicVMProvider { export class ShanghaiVMProvider extends BasicVMProvider {
constructor (blockchain) { constructor(blockchain) {
super({ super(
name: 'vm-shanghai', {
displayName: 'Remix VM (Shanghai)', name: 'vm-shanghai',
kind: 'provider', displayName: 'Remix VM (Shanghai)',
description: 'Remix VM (Shanghai)', kind: 'provider',
methods: ['sendAsync', 'init'], description: 'Remix VM (Shanghai)',
version: packageJson.version methods: ['sendAsync', 'init'],
}, blockchain) version: packageJson.version
},
blockchain
)
this.blockchain = blockchain this.blockchain = blockchain
this.fork = 'shanghai' this.fork = 'shanghai'
} }
} }

@ -1,7 +1,7 @@
import { ViewPlugin } from '@remixproject/engine-web' import {ViewPlugin} from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { SearchTab } from '@remix-ui/search' import {SearchTab} from '@remix-ui/search'
const profile = { const profile = {
name: 'search', name: 'search',
displayName: 'Search in files', displayName: 'Search in files',
@ -17,17 +17,15 @@ const profile = {
} }
export class SearchPlugin extends ViewPlugin { export class SearchPlugin extends ViewPlugin {
constructor() {
constructor () {
super(profile) super(profile)
} }
render() { render() {
return ( return (
<div id='searchTab'> <div id="searchTab">
<SearchTab plugin={this}></SearchTab> <SearchTab plugin={this}></SearchTab>
</div> </div>
); )
} }
} }

@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { ViewPlugin } from '@remixproject/engine-web' import {ViewPlugin} from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { RemixUiSettings } from '@remix-ui/settings' //eslint-disable-line import {RemixUiSettings} from '@remix-ui/settings' //eslint-disable-line
import Registry from '../state/registry' import Registry from '../state/registry'
import { PluginViewWrapper } from '@remix-ui/helper' import {PluginViewWrapper} from '@remix-ui/helper'
const profile = { const profile = {
name: 'settings', name: 'settings',
@ -18,7 +18,7 @@ const profile = {
documentation: 'https://remix-ide.readthedocs.io/en/latest/settings.html', documentation: 'https://remix-ide.readthedocs.io/en/latest/settings.html',
version: packageJson.version, version: packageJson.version,
permission: true, permission: true,
maintainedBy: "Remix" maintainedBy: 'Remix'
} }
module.exports = class SettingsTab extends ViewPlugin { module.exports = class SettingsTab extends ViewPlugin {
@ -31,7 +31,7 @@ module.exports = class SettingsTab extends ViewPlugin {
element: HTMLDivElement element: HTMLDivElement
public useMatomoAnalytics: any public useMatomoAnalytics: any
dispatch: React.Dispatch<any> = () => {} dispatch: React.Dispatch<any> = () => {}
constructor (config, editor) { constructor(config, editor) {
super(profile) super(profile)
this.config = config this.config = config
this.config.events.on('configChanged', (changedConfig) => { this.config.events.on('configChanged', (changedConfig) => {
@ -47,37 +47,41 @@ module.exports = class SettingsTab extends ViewPlugin {
this.useMatomoAnalytics = null this.useMatomoAnalytics = null
} }
setDispatch (dispatch: React.Dispatch<any>) { setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch this.dispatch = dispatch
this.renderComponent() this.renderComponent()
} }
render() { render() {
return <div id='settingsTab'> return (
<PluginViewWrapper plugin={this} /> <div id="settingsTab">
</div> <PluginViewWrapper plugin={this} />
</div>
)
} }
updateComponent(state: any){ updateComponent(state: any) {
return <RemixUiSettings return (
config={state.config} <RemixUiSettings
editor={state.editor} config={state.config}
_deps={state._deps} editor={state.editor}
useMatomoAnalytics={state.useMatomoAnalytics} _deps={state._deps}
themeModule = {state._deps.themeModule} useMatomoAnalytics={state.useMatomoAnalytics}
localeModule={state._deps.localeModule} themeModule={state._deps.themeModule}
/> localeModule={state._deps.localeModule}
/>
)
} }
renderComponent () { renderComponent() {
this.dispatch(this) this.dispatch(this)
} }
get (key) { get(key) {
return this.config.get(key) return this.config.get(key)
} }
updateMatomoAnalyticsChoice (isChecked) { updateMatomoAnalyticsChoice(isChecked) {
this.config.set('settings/matomo-analytics', isChecked) this.config.set('settings/matomo-analytics', isChecked)
this.useMatomoAnalytics = isChecked this.useMatomoAnalytics = isChecked
this.dispatch({ this.dispatch({

@ -1,23 +1,23 @@
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { fromWei, toBigInt, toWei } from 'web3-utils' import {fromWei, toBigInt, toWei} from 'web3-utils'
import { Plugin } from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import { toBuffer, addHexPrefix } from '@ethereumjs/util' import {toBuffer, addHexPrefix} from '@ethereumjs/util'
import { EventEmitter } from 'events' import {EventEmitter} from 'events'
import { format } from 'util' import {format} from 'util'
import { ExecutionContext } from './execution-context' import {ExecutionContext} from './execution-context'
import Config from '../config' import Config from '../config'
import { VMProvider } from './providers/vm' import {VMProvider} from './providers/vm'
import { InjectedProvider } from './providers/injected' import {InjectedProvider} from './providers/injected'
import { NodeProvider } from './providers/node' import {NodeProvider} from './providers/node'
import { execution, EventManager, helpers } from '@remix-project/remix-lib' import {execution, EventManager, helpers} from '@remix-project/remix-lib'
import { etherScanLink } from './helper' import {etherScanLink} from './helper'
import { logBuilder, cancelUpgradeMsg, cancelProxyMsg, addressToString } from "@remix-ui/helper" import {logBuilder, cancelUpgradeMsg, cancelProxyMsg, addressToString} from '@remix-ui/helper'
const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution const {txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper} = execution
const { txResultHelper } = helpers const {txResultHelper} = helpers
const { resultToRemixTx } = txResultHelper const {resultToRemixTx} = txResultHelper
import * as packageJson from '../../../../package.json' import * as packageJson from '../../../../package.json'
const _paq = window._paq = window._paq || [] //eslint-disable-line const _paq = (window._paq = window._paq || []) //eslint-disable-line
const profile = { const profile = {
name: 'blockchain', name: 'blockchain',
@ -28,19 +28,19 @@ const profile = {
} }
export type TransactionContextAPI = { export type TransactionContextAPI = {
getAddress: (cb: (error: Error, result: string) => void) => void, getAddress: (cb: (error: Error, result: string) => void) => void
getValue: (cb: (error: Error, result: string) => void) => void, getValue: (cb: (error: Error, result: string) => void) => void
getGasLimit: (cb: (error: Error, result: string) => void) => void getGasLimit: (cb: (error: Error, result: string) => void) => void
} }
// see TxRunner.ts in remix-lib // see TxRunner.ts in remix-lib
export type Transaction = { export type Transaction = {
from: string, from: string
to: string, to: string
value: string, value: string
data: string, data: string
gasLimit: number, gasLimit: number
useCall: boolean, useCall: boolean
timestamp?: number timestamp?: number
} }
@ -54,16 +54,16 @@ export class Blockchain extends Plugin {
networkcallid: number networkcallid: number
networkStatus: { networkStatus: {
network: { network: {
name: string, name: string
id: string id: string
} }
error?: string error?: string
} }
providers: { [key: string]: VMProvider | InjectedProvider | NodeProvider } providers: {[key: string]: VMProvider | InjectedProvider | NodeProvider}
transactionContextAPI: TransactionContextAPI transactionContextAPI: TransactionContextAPI
// NOTE: the config object will need to be refactored out in remix-lib // NOTE: the config object will need to be refactored out in remix-lib
constructor (config: Config) { constructor(config: Config) {
super(profile) super(profile)
this.active = false this.active = false
this.event = new EventManager() this.event = new EventManager()
@ -71,55 +71,61 @@ export class Blockchain extends Plugin {
this.events = new EventEmitter() this.events = new EventEmitter()
this.config = config this.config = config
const web3Runner = new TxRunnerWeb3({ const web3Runner = new TxRunnerWeb3(
config: this.config, {
detectNetwork: (cb) => { config: this.config,
this.executionContext.detectNetwork(cb) detectNetwork: (cb) => {
this.executionContext.detectNetwork(cb)
},
isVM: () => {
return this.executionContext.isVM()
},
personalMode: () => {
return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false
}
}, },
isVM: () => { return this.executionContext.isVM() }, (_) => this.executionContext.web3(),
personalMode: () => { (_) => this.executionContext.currentblockGasLimit()
return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false )
}
}, _ => this.executionContext.web3(), _ => this.executionContext.currentblockGasLimit())
this.txRunner = new TxRunner(web3Runner, {}) this.txRunner = new TxRunner(web3Runner, {})
this.networkcallid = 0 this.networkcallid = 0
this.networkStatus = { network: { name: ' - ', id: ' - ' } } this.networkStatus = {network: {name: ' - ', id: ' - '}}
this.setupEvents() this.setupEvents()
this.setupProviders() this.setupProviders()
} }
_triggerEvent (name, args) { _triggerEvent(name, args) {
if (!this.active) return if (!this.active) return
this.event.trigger(name, args) this.event.trigger(name, args)
this.emit(name, ...args) this.emit(name, ...args)
} }
onActivation () { onActivation() {
this.active = true this.active = true
this.on('injected', 'chainChanged', () => { this.on('injected', 'chainChanged', () => {
this.detectNetwork((error, network) => { this.detectNetwork((error, network) => {
this.networkStatus = { network, error } this.networkStatus = {network, error}
this._triggerEvent('networkStatus', [this.networkStatus]) this._triggerEvent('networkStatus', [this.networkStatus])
}) })
}) })
this.on('injected-trustwallet', 'chainChanged', () => { this.on('injected-trustwallet', 'chainChanged', () => {
this.detectNetwork((error, network) => { this.detectNetwork((error, network) => {
this.networkStatus = { network, error } this.networkStatus = {network, error}
this._triggerEvent('networkStatus', [this.networkStatus]) this._triggerEvent('networkStatus', [this.networkStatus])
}) })
}) })
this.on('walletconnect', 'chainChanged', () => { this.on('walletconnect', 'chainChanged', () => {
this.detectNetwork((error, network) => { this.detectNetwork((error, network) => {
this.networkStatus = { network, error } this.networkStatus = {network, error}
this._triggerEvent('networkStatus', [this.networkStatus]) this._triggerEvent('networkStatus', [this.networkStatus])
}) })
}) })
} }
onDeactivation () { onDeactivation() {
this.active = false this.active = false
this.off('injected', 'chainChanged') this.off('injected', 'chainChanged')
this.off('injected-trustwallet', 'chainChanged') this.off('injected-trustwallet', 'chainChanged')
@ -127,12 +133,12 @@ export class Blockchain extends Plugin {
this.off('walletconnect', 'accountsChanged') this.off('walletconnect', 'accountsChanged')
} }
setupEvents () { setupEvents() {
this.executionContext.event.register('contextChanged', async (context) => { this.executionContext.event.register('contextChanged', async (context) => {
await this.resetEnvironment() await this.resetEnvironment()
this._triggerEvent('contextChanged', [context]) this._triggerEvent('contextChanged', [context])
this.detectNetwork((error, network) => { this.detectNetwork((error, network) => {
this.networkStatus = { network, error } this.networkStatus = {network, error}
this._triggerEvent('networkStatus', [this.networkStatus]) this._triggerEvent('networkStatus', [this.networkStatus])
}) })
}) })
@ -147,17 +153,17 @@ export class Blockchain extends Plugin {
setInterval(() => { setInterval(() => {
this.detectNetwork((error, network) => { this.detectNetwork((error, network) => {
this.networkStatus = { network, error } this.networkStatus = {network, error}
this._triggerEvent('networkStatus', [this.networkStatus]) this._triggerEvent('networkStatus', [this.networkStatus])
}) })
}, 30000) }, 30000)
} }
getCurrentNetworkStatus () { getCurrentNetworkStatus() {
return this.networkStatus return this.networkStatus
} }
setupProviders () { setupProviders() {
const vmProvider = new VMProvider(this.executionContext) const vmProvider = new VMProvider(this.executionContext)
this.providers = {} this.providers = {}
this.providers['vm'] = vmProvider this.providers['vm'] = vmProvider
@ -165,7 +171,7 @@ export class Blockchain extends Plugin {
this.providers.web3 = new NodeProvider(this.executionContext, this.config) this.providers.web3 = new NodeProvider(this.executionContext, this.config)
} }
getCurrentProvider () { getCurrentProvider() {
const provider = this.getProvider() const provider = this.getProvider()
if (provider && provider.startsWith('vm')) return this.providers['vm'] if (provider && provider.startsWith('vm')) return this.providers['vm']
if (this.providers[provider]) return this.providers[provider] if (this.providers[provider]) return this.providers[provider]
@ -174,11 +180,11 @@ export class Blockchain extends Plugin {
/** Return the list of accounts */ /** Return the list of accounts */
// note: the dual promise/callback is kept for now as it was before // note: the dual promise/callback is kept for now as it was before
getAccounts (cb) { getAccounts(cb) {
console.log('getAccounts') console.log('getAccounts')
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.getCurrentProvider().getAccounts((error, accounts) => { this.getCurrentProvider().getAccounts((error, accounts) => {
console.log('get accounts res',error, accounts) console.log('get accounts res', error, accounts)
if (cb) { if (cb) {
return cb(error, accounts) return cb(error, accounts)
} }
@ -190,36 +196,53 @@ export class Blockchain extends Plugin {
}) })
} }
deployContractAndLibraries (selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) { deployContractAndLibraries(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) {
const { continueCb, promptCb, statusCb, finalCb } = callbacks const {continueCb, promptCb, statusCb, finalCb} = callbacks
const constructor = selectedContract.getConstructorInterface() const constructor = selectedContract.getConstructorInterface()
txFormat.buildData(selectedContract.name, selectedContract.object, compilerContracts, true, constructor, args, (error, data) => { txFormat.buildData(
if (error) { selectedContract.name,
return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`) selectedContract.object,
} compilerContracts,
true,
constructor,
args,
(error, data) => {
if (error) {
return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
}
statusCb(`creation of ${selectedContract.name} pending...`) statusCb(`creation of ${selectedContract.name} pending...`)
this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb) this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb)
}, statusCb, (data, runTxCallback) => { },
// called for libraries deployment statusCb,
this.runTx(data, confirmationCb, continueCb, promptCb, runTxCallback) (data, runTxCallback) => {
}) // called for libraries deployment
this.runTx(data, confirmationCb, continueCb, promptCb, runTxCallback)
}
)
} }
deployContractWithLibrary (selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) { deployContractWithLibrary(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) {
const { continueCb, promptCb, statusCb, finalCb } = callbacks const {continueCb, promptCb, statusCb, finalCb} = callbacks
const constructor = selectedContract.getConstructorInterface() const constructor = selectedContract.getConstructorInterface()
txFormat.encodeConstructorCallAndLinkLibraries(selectedContract.object, args, constructor, contractMetadata.linkReferences, selectedContract.bytecodeLinkReferences, (error, data) => { txFormat.encodeConstructorCallAndLinkLibraries(
if (error) { selectedContract.object,
return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`) args,
} constructor,
contractMetadata.linkReferences,
selectedContract.bytecodeLinkReferences,
(error, data) => {
if (error) {
return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
}
statusCb(`creation of ${selectedContract.name} pending...`) statusCb(`creation of ${selectedContract.name} pending...`)
this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb) this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb)
}) }
)
} }
async deployProxy (proxyData, implementationContractObject) { async deployProxy(proxyData, implementationContractObject) {
const proxyModal = { const proxyModal = {
id: 'confirmProxyDeployment', id: 'confirmProxyDeployment',
title: 'Confirm Deploy Proxy (ERC1967)', title: 'Confirm Deploy Proxy (ERC1967)',
@ -241,16 +264,20 @@ export class Blockchain extends Plugin {
this.call('notification', 'modal', proxyModal) this.call('notification', 'modal', proxyModal)
} }
async runProxyTx (proxyData, implementationContractObject) { async runProxyTx(proxyData, implementationContractObject) {
const args = { useCall: false, data: proxyData } const args = {useCall: false, data: proxyData}
let networkInfo let networkInfo
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
networkInfo = network networkInfo = network
// continue using original authorization given by user // continue using original authorization given by user
continueTxExecution(null) continueTxExecution(null)
} }
const continueCb = (error, continueTxExecution, cancelCb) => { continueTxExecution() } const continueCb = (error, continueTxExecution, cancelCb) => {
const promptCb = (okCb, cancelCb) => { okCb() } continueTxExecution()
}
const promptCb = (okCb, cancelCb) => {
okCb()
}
const finalCb = async (error, txResult, address, returnValue) => { const finalCb = async (error, txResult, address, returnValue) => {
if (error) { if (error) {
const log = logBuilder(error) const log = logBuilder(error)
@ -288,16 +315,20 @@ export class Blockchain extends Plugin {
this.call('notification', 'modal', upgradeModal) this.call('notification', 'modal', upgradeModal)
} }
async runUpgradeTx (proxyAddress, data, newImplementationContractObject) { async runUpgradeTx(proxyAddress, data, newImplementationContractObject) {
const args = { useCall: false, data, to: proxyAddress } const args = {useCall: false, data, to: proxyAddress}
let networkInfo let networkInfo
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
// continue using original authorization given by user // continue using original authorization given by user
networkInfo = network networkInfo = network
continueTxExecution(null) continueTxExecution(null)
} }
const continueCb = (error, continueTxExecution, cancelCb) => { continueTxExecution() } const continueCb = (error, continueTxExecution, cancelCb) => {
const promptCb = (okCb, cancelCb) => { okCb() } continueTxExecution()
}
const promptCb = (okCb, cancelCb) => {
okCb()
}
const finalCb = async (error, txResult, address, returnValue) => { const finalCb = async (error, txResult, address, returnValue) => {
if (error) { if (error) {
const log = logBuilder(error) const log = logBuilder(error)
@ -312,8 +343,8 @@ export class Blockchain extends Plugin {
this.runTx(args, confirmationCb, continueCb, promptCb, finalCb) this.runTx(args, confirmationCb, continueCb, promptCb, finalCb)
} }
async saveDeployedContractStorageLayout (contractObject, proxyAddress, networkInfo) { async saveDeployedContractStorageLayout(contractObject, proxyAddress, networkInfo) {
const { contractName, implementationAddress } = contractObject const {contractName, implementationAddress} = contractObject
const networkName = networkInfo.name === 'custom' ? networkInfo.name + '-' + networkInfo.id : networkInfo.name const networkName = networkInfo.name === 'custom' ? networkInfo.name + '-' + networkInfo.id : networkInfo.name
const hasPreviousDeploys = await this.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`) const hasPreviousDeploys = await this.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`)
// TODO: make deploys folder read only. // TODO: make deploys folder read only.
@ -336,32 +367,59 @@ export class Blockchain extends Plugin {
solcOutput: contractObject.compiler.data, solcOutput: contractObject.compiler.data,
solcInput: contractObject.compiler.source solcInput: contractObject.compiler.source
} }
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/solc-${implementationAddress}.json`, JSON.stringify({ await this.call(
solcInput: contractObject.compiler.source, 'fileManager',
solcOutput: contractObject.compiler.data 'writeFile',
}, null, 2)) `.deploys/upgradeable-contracts/${networkName}/solc-${implementationAddress}.json`,
JSON.stringify(
{
solcInput: contractObject.compiler.source,
solcOutput: contractObject.compiler.data
},
null,
2
)
)
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`, JSON.stringify(parsedDeployments, null, 2)) await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`, JSON.stringify(parsedDeployments, null, 2))
} else { } else {
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/solc-${implementationAddress}.json`, JSON.stringify({ await this.call(
solcInput: contractObject.compiler.source, 'fileManager',
solcOutput: contractObject.compiler.data 'writeFile',
}, null, 2)) `.deploys/upgradeable-contracts/${networkName}/solc-${implementationAddress}.json`,
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkName}/UUPS.json`, JSON.stringify({ JSON.stringify(
id: networkInfo.id, {
network: networkInfo.name, solcInput: contractObject.compiler.source,
deployments: { solcOutput: contractObject.compiler.data
[proxyAddress]: { },
date: new Date().toISOString(), null,
contractName: contractName, 2
fork: networkInfo.currentFork, )
implementationAddress: implementationAddress )
} await this.call(
} 'fileManager',
}, null, 2)) 'writeFile',
`.deploys/upgradeable-contracts/${networkName}/UUPS.json`,
JSON.stringify(
{
id: networkInfo.id,
network: networkInfo.name,
deployments: {
[proxyAddress]: {
date: new Date().toISOString(),
contractName: contractName,
fork: networkInfo.currentFork,
implementationAddress: implementationAddress
}
}
},
null,
2
)
)
} }
} }
async getEncodedFunctionHex (args, funABI) { async getEncodedFunctionHex(args, funABI) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
txFormat.encodeFunctionCall(args, funABI, (error, data) => { txFormat.encodeFunctionCall(args, funABI, (error, data) => {
if (error) return reject(error) if (error) return reject(error)
@ -370,7 +428,7 @@ export class Blockchain extends Plugin {
}) })
} }
async getEncodedParams (args, funABI) { async getEncodedParams(args, funABI) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
txFormat.encodeParams(args, funABI, (error, encodedParams) => { txFormat.encodeParams(args, funABI, (error, encodedParams) => {
if (error) return reject(error) if (error) return reject(error)
@ -379,27 +437,25 @@ export class Blockchain extends Plugin {
}) })
} }
createContract (selectedContract, data, continueCb, promptCb, confirmationCb, finalCb) { createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb) {
if (data) { if (data) {
data.contractName = selectedContract.name data.contractName = selectedContract.name
data.linkReferences = selectedContract.bytecodeLinkReferences data.linkReferences = selectedContract.bytecodeLinkReferences
data.contractABI = selectedContract.abi data.contractABI = selectedContract.abi
} }
this.runTx({ data: data, useCall: false }, confirmationCb, continueCb, promptCb, this.runTx({data: data, useCall: false}, confirmationCb, continueCb, promptCb, (error, txResult, address) => {
(error, txResult, address) => { if (error) {
if (error) { return finalCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
return finalCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
}
if (txResult.receipt.status === false || txResult.receipt.status === '0x0' || txResult.receipt.status === 0) {
return finalCb(`creation of ${selectedContract.name} errored: transaction execution failed`)
}
finalCb(null, selectedContract, address)
} }
) if (txResult.receipt.status === false || txResult.receipt.status === '0x0' || txResult.receipt.status === 0) {
return finalCb(`creation of ${selectedContract.name} errored: transaction execution failed`)
}
finalCb(null, selectedContract, address)
})
} }
determineGasPrice (cb) { determineGasPrice(cb) {
this.getCurrentProvider().getGasPrice((error, gasPrice) => { this.getCurrentProvider().getGasPrice((error, gasPrice) => {
const warnMessage = ' Please fix this issue before sending any transaction. ' const warnMessage = ' Please fix this issue before sending any transaction. '
if (error) { if (error) {
@ -414,29 +470,29 @@ export class Blockchain extends Plugin {
}) })
} }
getInputs (funABI) { getInputs(funABI) {
if (!funABI.inputs) { if (!funABI.inputs) {
return '' return ''
} }
return txHelper.inputParametersDeclarationToString(funABI.inputs) return txHelper.inputParametersDeclarationToString(funABI.inputs)
} }
fromWei (value, doTypeConversion, unit) { fromWei(value, doTypeConversion, unit) {
if (doTypeConversion) { if (doTypeConversion) {
return fromWei(typeConversion.toInt(value), unit || 'ether') return fromWei(typeConversion.toInt(value), unit || 'ether')
} }
return fromWei(value.toString(10), unit || 'ether') return fromWei(value.toString(10), unit || 'ether')
} }
toWei (value, unit) { toWei(value, unit) {
return toWei(value, unit || 'gwei') return toWei(value, unit || 'gwei')
} }
calculateFee (gas, gasPrice, unit?) { calculateFee(gas, gasPrice, unit?) {
return toBigInt(gas) * toBigInt(toWei(gasPrice.toString(10) as string, unit || 'gwei')) return toBigInt(gas) * toBigInt(toWei(gasPrice.toString(10) as string, unit || 'gwei'))
} }
determineGasFees (tx) { determineGasFees(tx) {
const determineGasFeesCb = (gasPrice, cb) => { const determineGasFeesCb = (gasPrice, cb) => {
let txFeeText, priceStatus let txFeeText, priceStatus
// TODO: this try catch feels like an anti pattern, can/should be // TODO: this try catch feels like an anti pattern, can/should be
@ -455,19 +511,19 @@ export class Blockchain extends Plugin {
return determineGasFeesCb return determineGasFeesCb
} }
changeExecutionContext (context, confirmCb, infoCb, cb) { changeExecutionContext(context, confirmCb, infoCb, cb) {
return this.executionContext.executionContextChange(context, null, confirmCb, infoCb, cb) return this.executionContext.executionContextChange(context, null, confirmCb, infoCb, cb)
} }
detectNetwork (cb) { detectNetwork(cb) {
return this.executionContext.detectNetwork(cb) return this.executionContext.detectNetwork(cb)
} }
getProvider () { getProvider() {
return this.executionContext.getProvider() return this.executionContext.getProvider()
} }
getInjectedWeb3Address () { getInjectedWeb3Address() {
return this.executionContext.getSelectedAddress() return this.executionContext.getSelectedAddress()
} }
@ -475,29 +531,29 @@ export class Blockchain extends Plugin {
* return the fork name applied to the current envionment * return the fork name applied to the current envionment
* @return {String} - fork name * @return {String} - fork name
*/ */
getCurrentFork () { getCurrentFork() {
return this.executionContext.getCurrentFork() return this.executionContext.getCurrentFork()
} }
isWeb3Provider () { isWeb3Provider() {
const isVM = this.executionContext.isVM() const isVM = this.executionContext.isVM()
const isInjected = this.getProvider() === 'injected' const isInjected = this.getProvider() === 'injected'
return (!isVM && !isInjected) return !isVM && !isInjected
} }
isInjectedWeb3 () { isInjectedWeb3() {
return this.getProvider() === 'injected' return this.getProvider() === 'injected'
} }
signMessage (message, account, passphrase, cb) { signMessage(message, account, passphrase, cb) {
this.getCurrentProvider().signMessage(message, account, passphrase, cb) this.getCurrentProvider().signMessage(message, account, passphrase, cb)
} }
web3VM () { web3VM() {
return (this.providers.vm as VMProvider).web3 return (this.providers.vm as VMProvider).web3
} }
web3 () { web3() {
// @todo(https://github.com/ethereum/remix-project/issues/431) // @todo(https://github.com/ethereum/remix-project/issues/431)
const isVM = this.executionContext.isVM() const isVM = this.executionContext.isVM()
if (isVM) { if (isVM) {
@ -506,7 +562,7 @@ export class Blockchain extends Plugin {
return this.executionContext.web3() return this.executionContext.web3()
} }
getTxListener (opts) { getTxListener(opts) {
opts.event = { opts.event = {
// udapp: this.udapp.event // udapp: this.udapp.event
udapp: this.event udapp: this.event
@ -515,84 +571,107 @@ export class Blockchain extends Plugin {
return txlistener return txlistener
} }
runOrCallContractMethod (contractName, contractAbi, funABI, contract, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb) { runOrCallContractMethod(contractName, contractAbi, funABI, contract, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb) {
// contractsDetails is used to resolve libraries // contractsDetails is used to resolve libraries
txFormat.buildData(contractName, contractAbi, {}, false, funABI, callType, (error, data) => { txFormat.buildData(
if (error) { contractName,
return logCallback(`${logMsg} errored: ${error.message ? error.message : error}`) contractAbi,
} {},
if (!lookupOnly) { false,
logCallback(`${logMsg} pending ... `) funABI,
} else { callType,
logCallback(`${logMsg}`) (error, data) => {
}
if (funABI.type === 'fallback') data.dataHex = value
if (data) {
data.contractName = contractName
data.contractABI = contractAbi
data.contract = contract
}
const useCall = funABI.stateMutability === 'view' || funABI.stateMutability === 'pure'
this.runTx({ to: address, data, useCall }, confirmationCb, continueCb, promptCb, (error, txResult, _address, returnValue) => {
if (error) { if (error) {
return logCallback(`${logMsg} errored: ${error.message ? error.message : error}`) return logCallback(`${logMsg} errored: ${error.message ? error.message : error}`)
} }
if (lookupOnly) { if (!lookupOnly) {
outputCb(returnValue) logCallback(`${logMsg} pending ... `)
} else {
logCallback(`${logMsg}`)
} }
}) if (funABI.type === 'fallback') data.dataHex = value
},
(msg) => { if (data) {
logCallback(msg) data.contractName = contractName
}, data.contractABI = contractAbi
(data, runTxCallback) => { data.contract = contract
// called for libraries deployment }
this.runTx(data, confirmationCb, runTxCallback, promptCb, () => { /* Do nothing. */ }) const useCall = funABI.stateMutability === 'view' || funABI.stateMutability === 'pure'
}) this.runTx({to: address, data, useCall}, confirmationCb, continueCb, promptCb, (error, txResult, _address, returnValue) => {
if (error) {
return logCallback(`${logMsg} errored: ${error.message ? error.message : error}`)
}
if (lookupOnly) {
outputCb(returnValue)
}
})
},
(msg) => {
logCallback(msg)
},
(data, runTxCallback) => {
// called for libraries deployment
this.runTx(data, confirmationCb, runTxCallback, promptCb, () => {
/* Do nothing. */
})
}
)
} }
context () { context() {
return (this.executionContext.isVM() ? 'memory' : 'blockchain') return this.executionContext.isVM() ? 'memory' : 'blockchain'
} }
// NOTE: the config is only needed because exectuionContext.init does // NOTE: the config is only needed because exectuionContext.init does
async resetAndInit (config: Config, transactionContextAPI: TransactionContextAPI) { async resetAndInit(config: Config, transactionContextAPI: TransactionContextAPI) {
this.transactionContextAPI = transactionContextAPI this.transactionContextAPI = transactionContextAPI
this.executionContext.init(config) this.executionContext.init(config)
this.executionContext.stopListenOnLastBlock() this.executionContext.stopListenOnLastBlock()
this.executionContext.listenOnLastBlock() this.executionContext.listenOnLastBlock()
} }
addProvider (provider) { addProvider(provider) {
this.executionContext.addProvider(provider) this.executionContext.addProvider(provider)
} }
removeProvider (name) { removeProvider(name) {
this.executionContext.removeProvider(name) this.executionContext.removeProvider(name)
} }
// TODO : event should be triggered by Udapp instead of TxListener // TODO : event should be triggered by Udapp instead of TxListener
/** Listen on New Transaction. (Cannot be done inside constructor because txlistener doesn't exist yet) */ /** Listen on New Transaction. (Cannot be done inside constructor because txlistener doesn't exist yet) */
startListening (txlistener) { startListening(txlistener) {
txlistener.event.register('newTransaction', (tx, receipt) => { txlistener.event.register('newTransaction', (tx, receipt) => {
this.events.emit('newTransaction', tx, receipt) this.events.emit('newTransaction', tx, receipt)
}) })
} }
async resetEnvironment () { async resetEnvironment() {
await this.getCurrentProvider().resetEnvironment() await this.getCurrentProvider().resetEnvironment()
// TODO: most params here can be refactored away in txRunner // TODO: most params here can be refactored away in txRunner
const web3Runner = new TxRunnerWeb3({ const web3Runner = new TxRunnerWeb3(
config: this.config, {
detectNetwork: (cb) => { config: this.config,
this.executionContext.detectNetwork(cb) detectNetwork: (cb) => {
this.executionContext.detectNetwork(cb)
},
isVM: () => {
return this.executionContext.isVM()
},
personalMode: () => {
return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false
}
}, },
isVM: () => { return this.executionContext.isVM() },
personalMode: () => { // isVM: () => { return this.executionContext.isVM() },
return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false // personalMode: () => {
} // return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false
}, _ => this.executionContext.web3(), _ => this.executionContext.currentblockGasLimit()) // }
// }, _ => this.executionContext.web3(), _ => this.executionContext.currentblockGasLimit())
(_) => this.executionContext.web3(),
(_) => this.executionContext.currentblockGasLimit()
)
web3Runner.event.register('transactionBroadcasted', (txhash) => { web3Runner.event.register('transactionBroadcasted', (txhash) => {
this.executionContext.detectNetwork((error, network) => { this.executionContext.detectNetwork((error, network) => {
@ -601,10 +680,13 @@ export class Blockchain extends Plugin {
const viewEtherScanLink = etherScanLink(network.name, txhash) const viewEtherScanLink = etherScanLink(network.name, txhash)
if (viewEtherScanLink) { if (viewEtherScanLink) {
this.call('terminal', 'logHtml', this.call(
(<a href={etherScanLink(network.name, txhash)} target="_blank"> 'terminal',
view on etherscan 'logHtml',
</a>)) <a href={etherScanLink(network.name, txhash)} target="_blank">
view on etherscan
</a>
)
} }
}) })
}) })
@ -615,23 +697,23 @@ export class Blockchain extends Plugin {
* Create a VM Account * Create a VM Account
* @param {{privateKey: string, balance: string}} newAccount The new account to create * @param {{privateKey: string, balance: string}} newAccount The new account to create
*/ */
createVMAccount (newAccount) { createVMAccount(newAccount) {
if (!this.executionContext.isVM()) { if (!this.executionContext.isVM()) {
throw new Error('plugin API does not allow creating a new account through web3 connection. Only vm mode is allowed') throw new Error('plugin API does not allow creating a new account through web3 connection. Only vm mode is allowed')
} }
return (this.providers.vm as VMProvider).createVMAccount(newAccount) return (this.providers.vm as VMProvider).createVMAccount(newAccount)
} }
newAccount (_password, passwordPromptCb, cb) { newAccount(_password, passwordPromptCb, cb) {
return this.getCurrentProvider().newAccount(passwordPromptCb, cb) return this.getCurrentProvider().newAccount(passwordPromptCb, cb)
} }
/** Get the balance of an address, and convert wei to ether */ /** Get the balance of an address, and convert wei to ether */
getBalanceInEther (address) { getBalanceInEther(address) {
return this.getCurrentProvider().getBalanceInEther(address) return this.getCurrentProvider().getBalanceInEther(address)
} }
pendingTransactionsCount () { pendingTransactionsCount() {
return Object.keys(this.txRunner.pendingTxs).length return Object.keys(this.txRunner.pendingTxs).length
} }
@ -639,7 +721,7 @@ export class Blockchain extends Plugin {
return await this.web3().eth.getCode(address) return await this.web3().eth.getCode(address)
} }
async getTransactionReceipt (hash) { async getTransactionReceipt(hash) {
return await this.web3().eth.getTransactionReceipt(hash) return await this.web3().eth.getTransactionReceipt(hash)
} }
@ -649,7 +731,7 @@ export class Blockchain extends Plugin {
* *
* @param {Object} tx - transaction. * @param {Object} tx - transaction.
*/ */
sendTransaction (tx: Transaction) { sendTransaction(tx: Transaction) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.executionContext.detectNetwork((error, network) => { this.executionContext.detectNetwork((error, network) => {
if (error) return reject(error) if (error) return reject(error)
@ -659,17 +741,26 @@ export class Blockchain extends Plugin {
this.txRunner.rawRun( this.txRunner.rawRun(
tx, tx,
(network, tx, gasEstimation, continueTxExecution, cancelCb) => { continueTxExecution() }, (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
(error, continueTxExecution, cancelCb) => { if (error) { reject(error) } else { continueTxExecution() } }, continueTxExecution()
(okCb, cancelCb) => { okCb() }, },
(error, continueTxExecution, cancelCb) => {
if (error) {
reject(error)
} else {
continueTxExecution()
}
},
(okCb, cancelCb) => {
okCb()
},
async (error, result) => { async (error, result) => {
if (error) return reject(error) if (error) return reject(error)
try { try {
if (this.executionContext.isVM()) { if (this.executionContext.isVM()) {
const execResult = await this.web3().testPlugin.getExecutionResultFromSimulator(result.transactionHash) const execResult = await this.web3().testPlugin.getExecutionResultFromSimulator(result.transactionHash)
resolve(resultToRemixTx(result, execResult)) resolve(resultToRemixTx(result, execResult))
} else } else resolve(resultToRemixTx(result))
resolve(resultToRemixTx(result))
} catch (e) { } catch (e) {
reject(e) reject(e)
} }
@ -679,7 +770,7 @@ export class Blockchain extends Plugin {
}) })
} }
async runTx (args, confirmationCb, continueCb, promptCb, cb) { async runTx(args, confirmationCb, continueCb, promptCb, cb) {
const getGasLimit = () => { const getGasLimit = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.transactionContextAPI.getGasLimit) { if (this.transactionContextAPI.getGasLimit) {
@ -713,7 +804,10 @@ export class Blockchain extends Plugin {
if (this.transactionContextAPI.getAddress) { if (this.transactionContextAPI.getAddress) {
return this.transactionContextAPI.getAddress(function (err, address) { return this.transactionContextAPI.getAddress(function (err, address) {
if (err) return reject(err) if (err) return reject(err)
if (!address) return reject('"from" is not defined. Please make sure an account is selected. If you are using a public node, it is likely that no account will be provided. In that case, add the public node to your injected provider (type Metamask) and use injected provider in Remix.') if (!address)
return reject(
'"from" is not defined. Please make sure an account is selected. If you are using a public node, it is likely that no account will be provided. In that case, add the public node to your injected provider (type Metamask) and use injected provider in Remix.'
)
return resolve(address) return resolve(address)
}) })
} }
@ -744,46 +838,67 @@ export class Blockchain extends Plugin {
return return
} }
const tx = { to: args.to, data: args.data.dataHex, useCall: args.useCall, from: fromAddress, value: value, gasLimit: gasLimit, timestamp: args.data.timestamp } const tx = {
const payLoad = { funAbi: args.data.funAbi, funArgs: args.data.funArgs, contractBytecode: args.data.contractBytecode, contractName: args.data.contractName, contractABI: args.data.contractABI, linkReferences: args.data.linkReferences } to: args.to,
data: args.data.dataHex,
useCall: args.useCall,
from: fromAddress,
value: value,
gasLimit: gasLimit,
timestamp: args.data.timestamp
}
const payLoad = {
funAbi: args.data.funAbi,
funArgs: args.data.funArgs,
contractBytecode: args.data.contractBytecode,
contractName: args.data.contractName,
contractABI: args.data.contractABI,
linkReferences: args.data.linkReferences
}
if (!tx.timestamp) tx.timestamp = Date.now() if (!tx.timestamp) tx.timestamp = Date.now()
const timestamp = tx.timestamp const timestamp = tx.timestamp
this._triggerEvent('initiatingTransaction', [timestamp, tx, payLoad]) this._triggerEvent('initiatingTransaction', [timestamp, tx, payLoad])
try { try {
this.txRunner.rawRun(tx, confirmationCb, continueCb, promptCb, this.txRunner.rawRun(tx, confirmationCb, continueCb, promptCb, async (error, result) => {
async (error, result) => { if (error) {
if (error) { if (typeof error !== 'string') {
if (typeof (error) !== 'string') { if (error.message) error = error.message
if (error.message) error = error.message else {
else { try {
try { error = 'error: ' + JSON.stringify(error) } catch (e) { console.log(e) } error = 'error: ' + JSON.stringify(error)
} catch (e) {
console.log(e)
} }
} }
return reject(error)
} }
return reject(error)
}
const isVM = this.executionContext.isVM() const isVM = this.executionContext.isVM()
if (isVM && tx.useCall) { if (isVM && tx.useCall) {
try { try {
result.transactionHash = await this.web3().testPlugin.getHashFromTagBySimulator(timestamp) result.transactionHash = await this.web3().eth.getHashFromTagBySimulator(timestamp)
} catch (e) { } catch (e) {
console.log('unable to retrieve back the "call" hash', e) console.log('unable to retrieve back the "call" hash', e)
}
} }
const eventName = (tx.useCall ? 'callExecuted' : 'transactionExecuted')
this._triggerEvent(eventName, [error, tx.from, tx.to, tx.data, tx.useCall, result, timestamp, payLoad])
return resolve({ result, tx })
} }
) const eventName = tx.useCall ? 'callExecuted' : 'transactionExecuted'
this._triggerEvent(eventName, [error, tx.from, tx.to, tx.data, tx.useCall, result, timestamp, payLoad])
return resolve({result, tx})
})
} catch (err) { } catch (err) {
let error = err let error = err
if (error && (typeof (error) !== 'string')) { if (error && typeof error !== 'string') {
if (error.message) error = error.message if (error.message) error = error.message
else { else {
try { error = 'error: ' + JSON.stringify(error) } catch (e) { console.log(e) } try {
error = 'error: ' + JSON.stringify(error)
} catch (e) {
console.log(e)
}
} }
} }
return reject(error) return reject(error)
@ -808,9 +923,12 @@ export class Blockchain extends Plugin {
const hhlogs = await this.web3().testPlugin.getHHLogsForTx(txResult.transactionHash) const hhlogs = await this.web3().testPlugin.getHHLogsForTx(txResult.transactionHash)
if (hhlogs && hhlogs.length) { if (hhlogs && hhlogs.length) {
const finalLogs = <div><div><b>console.log:</b></div> const finalLogs = (
{ <div>
hhlogs.map((log) => { <div>
<b>console.log:</b>
</div>
{hhlogs.map((log) => {
let formattedLog let formattedLog
// Hardhat implements the same formatting options that can be found in Node.js' console.log, // Hardhat implements the same formatting options that can be found in Node.js' console.log,
// which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args // which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args
@ -823,14 +941,17 @@ export class Blockchain extends Plugin {
} }
return <div>{formattedLog}</div> return <div>{formattedLog}</div>
})} })}
</div> </div>
)
_paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log']) _paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log'])
this.call('terminal', 'logHtml', finalLogs) this.call('terminal', 'logHtml', finalLogs)
} }
execResult = await this.web3().testPlugin.getExecutionResultFromSimulator(txResult.transactionHash) execResult = await this.web3().testPlugin.getExecutionResultFromSimulator(txResult.transactionHash)
if (execResult) { if (execResult) {
// if it's not the VM, we don't have return value. We only have the transaction, and it does not contain the return value. // if it's not the VM, we don't have return value. We only have the transaction, and it does not contain the return value.
returnValue = execResult ? toBuffer(execResult.returnValue) : toBuffer(addHexPrefix(txResult.result) || '0x0000000000000000000000000000000000000000000000000000000000000000') returnValue = execResult
? toBuffer(execResult.returnValue)
: toBuffer(addHexPrefix(txResult.result) || '0x0000000000000000000000000000000000000000000000000000000000000000')
const compiledContracts = await this.call('compilerArtefacts', 'getAllContractDatas') const compiledContracts = await this.call('compilerArtefacts', 'getAllContractDatas')
const vmError = txExecution.checkVMError(execResult, compiledContracts) const vmError = txExecution.checkVMError(execResult, compiledContracts)
if (vmError.error) { if (vmError.error) {

@ -1,19 +1,18 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React from 'react'
import { render } from 'react-dom' import {render} from 'react-dom'
import './index.css' import './index.css'
import { ThemeModule } from './app/tabs/theme-module' import {ThemeModule} from './app/tabs/theme-module'
import { Preload } from './app/components/preload' import {Preload} from './app/components/preload'
import Config from './config' import Config from './config'
import Registry from './app/state/registry' import Registry from './app/state/registry'
import { Storage } from '@remix-project/remix-lib' import {Storage} from '@remix-project/remix-lib'
;(async function () {
(async function () {
try { try {
const configStorage = new Storage('config-v0.8:') const configStorage = new Storage('config-v0.8:')
const config = new Config(configStorage); const config = new Config(configStorage)
Registry.getInstance().put({ api: config, name: 'config' }) Registry.getInstance().put({api: config, name: 'config'})
} catch (e) { } } catch (e) {}
const theme = new ThemeModule() const theme = new ThemeModule()
theme.initTheme() theme.initTheme()
@ -24,5 +23,3 @@ import { Storage } from '@remix-project/remix-lib'
document.getElementById('root') document.getElementById('root')
) )
})() })()

@ -1,11 +1,11 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react') const {withReact} = require('@nrwl/react')
const webpack = require('webpack') const webpack = require('webpack')
const CopyPlugin = require("copy-webpack-plugin") const CopyPlugin = require('copy-webpack-plugin')
const version = require('../../package.json').version const version = require('../../package.json').version
const fs = require('fs') const fs = require('fs')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const axios = require('axios') const axios = require('axios')
const versionData = { const versionData = {
@ -16,7 +16,7 @@ const versionData = {
const loadLocalSolJson = async () => { const loadLocalSolJson = async () => {
// execute apps/remix-ide/ci/downloadsoljson.sh // execute apps/remix-ide/ci/downloadsoljson.sh
const child = require('child_process').execSync('bash ./apps/remix-ide/ci/downloadsoljson.sh', { encoding: 'utf8', cwd: process.cwd(), shell: true }) const child = require('child_process').execSync('bash ./apps/remix-ide/ci/downloadsoljson.sh', {encoding: 'utf8', cwd: process.cwd(), shell: true})
// show output // show output
console.log(child) console.log(child)
} }
@ -32,9 +32,8 @@ const implicitDependencies = JSON.parse(project).implicitDependencies
const copyPatterns = implicitDependencies.map((dep) => { const copyPatterns = implicitDependencies.map((dep) => {
try { try {
fs.statSync(__dirname + `/../../dist/apps/${dep}`).isDirectory() fs.statSync(__dirname + `/../../dist/apps/${dep}`).isDirectory()
return { from: `../../dist/apps/${dep}`, to: `plugins/${dep}` } return {from: `../../dist/apps/${dep}`, to: `plugins/${dep}`}
} } catch (e) {
catch (e) {
console.log('error', e) console.log('error', e)
return false return false
} }
@ -50,30 +49,29 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify')
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
@ -83,12 +81,14 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
config.output.filename = `[name].${versionData.version}.${versionData.timestamp}.js` config.output.filename = `[name].${versionData.version}.${versionData.timestamp}.js`
config.output.chunkFilename = `[name].${versionData.version}.${versionData.timestamp}.js` config.output.chunkFilename = `[name].${versionData.version}.${versionData.timestamp}.js`
// add copy & provide plugin // add copy & provide plugin
config.plugins.push( config.plugins.push(
new CopyPlugin({ new CopyPlugin({
patterns: [ patterns: [
{ from: '../../node_modules/monaco-editor/min/vs', to: 'assets/js/monaco-editor/min/vs' }, {
from: '../../node_modules/monaco-editor/min/vs',
to: 'assets/js/monaco-editor/min/vs'
},
...copyPatterns ...copyPatterns
].filter(Boolean) ].filter(Boolean)
}), }),
@ -96,15 +96,15 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}) })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/, /require function/] // ignore source-map-loader warnings & AST warnings config.ignoreWarnings = [/Failed to parse source map/, /require function/] // ignore source-map-loader warnings & AST warnings
@ -118,23 +118,23 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/
} }
return config; return config
}); })
class CopyFileAfterBuild { class CopyFileAfterBuild {
apply(compiler) { apply(compiler) {
const onEnd = async () => { const onEnd = async () => {
try { try {
console.log('runnning CopyFileAfterBuild') console.log('runnning CopyFileAfterBuild')
@ -142,17 +142,15 @@ class CopyFileAfterBuild {
// This is needed because by default the etherscan resources are served from the /plugins/etherscan/ folder, // This is needed because by default the etherscan resources are served from the /plugins/etherscan/ folder,
// but the raw-loader try to access the resources from the root folder. // but the raw-loader try to access the resources from the root folder.
const files = fs.readdirSync('./dist/apps/etherscan') const files = fs.readdirSync('./dist/apps/etherscan')
files.forEach(file => { files.forEach((file) => {
if (file.includes('plugin-etherscan')) { if (file.includes('plugin-etherscan')) {
fs.copyFileSync('./dist/apps/etherscan/' + file, './dist/apps/remix-ide/' + file) fs.copyFileSync('./dist/apps/etherscan/' + file, './dist/apps/remix-ide/' + file)
} }
}) })
} catch (e) { } catch (e) {
console.error('running CopyFileAfterBuild failed with error: ' + e.message) console.error('running CopyFileAfterBuild failed with error: ' + e.message)
} }
} }
compiler.hooks.afterEmit.tapPromise('FileManagerPlugin', onEnd); compiler.hooks.afterEmit.tapPromise('FileManagerPlugin', onEnd)
} }
} }

@ -1,8 +1,8 @@
import React, { useEffect, useState } from "react"; import React, {useEffect, useState} from 'react'
import { SolHint } from "./SolhintPluginClient"; import {SolHint} from './SolhintPluginClient'
const client = new SolHint(); const client = new SolHint()
export default function App() { export default function App() {
return <></>; return <></>
} }

@ -1,18 +1,18 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react') const {withReact} = require('@nrwl/react')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => { module.exports = composePlugins(withNx(), withReact(), (config) => {
// Update the webpack config as needed here. // Update the webpack config as needed here.
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"path": false, path: false,
"os": false, os: false,
"fs": false, fs: false,
"module": false, module: false
} }
// add public path // add public path
@ -23,18 +23,18 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}), }),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
"BROWSER": true, BROWSER: true
}), })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
@ -48,13 +48,13 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
return config; return config
}) })

@ -1,9 +1,9 @@
/* eslint-disable no-use-before-define */ /* eslint-disable no-use-before-define */
import React from 'react' import React from 'react'
import { SolidityCompiler } from '@remix-ui/solidity-compiler' // eslint-disable-line import {SolidityCompiler} from '@remix-ui/solidity-compiler' // eslint-disable-line
import { CompilerClientApi } from './compiler' import {CompilerClientApi} from './compiler'
const remix = new CompilerClientApi() const remix = new CompilerClientApi()

@ -1,10 +1,10 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react') const {withReact} = require('@nrwl/react')
const webpack = require('webpack') const webpack = require('webpack')
const version = require('../../package.json').version const version = require('../../package.json').version
const fs = require('fs') const fs = require('fs')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const versionData = { const versionData = {
version: version, version: version,
@ -22,56 +22,52 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify')
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
config.output.publicPath = '/' config.output.publicPath = '/'
// add copy & provide plugin // add copy & provide plugin
config.plugins.push( config.plugins.push(
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}) })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer // set minimizer
config.optimization.minimizer = [ config.optimization.minimizer = [
new TerserPlugin({ new TerserPlugin({
@ -81,17 +77,17 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/
} }
return config; return config
}); })

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react' import React, {useState, useEffect} from 'react'
import { VyperCompilationOutput, remixClient } from './utils' import {VyperCompilationOutput, remixClient} from './utils'
import { CompilationResult } from '@remixproject/plugin-api' import {CompilationResult} from '@remixproject/plugin-api'
// Components // Components
import CompilerButton from './components/CompilerButton' import CompilerButton from './components/CompilerButton'
@ -38,7 +38,7 @@ const App: React.FC = () => {
async function start() { async function start() {
try { try {
await remixClient.loaded() await remixClient.loaded()
remixClient.onFileChange(name => setContract(name)) remixClient.onFileChange((name) => setContract(name))
remixClient.onNoFileSelected(() => setContract('')) remixClient.onNoFileSelected(() => setContract(''))
} catch (err) { } catch (err) {
console.log(err) console.log(err)
@ -53,17 +53,15 @@ const App: React.FC = () => {
/** Update the environment state value */ /** Update the environment state value */
function setEnvironment(environment: 'local' | 'remote') { function setEnvironment(environment: 'local' | 'remote') {
setState({ ...state, environment }) setState({...state, environment})
} }
function setLocalUrl(url: string) { function setLocalUrl(url: string) {
setState({ ...state, localUrl: url }) setState({...state, localUrl: url})
} }
function compilerUrl() { function compilerUrl() {
return state.environment === 'remote' return state.environment === 'remote' ? 'https://vyper.remixproject.org/compile' : state.localUrl
? 'https://vyper.remixproject.org/compile'
: state.localUrl
} }
return ( return (
@ -73,12 +71,8 @@ const App: React.FC = () => {
<img src={'assets/logo.svg'} alt="Vyper logo" /> <img src={'assets/logo.svg'} alt="Vyper logo" />
<h4>yper Compiler</h4> <h4>yper Compiler</h4>
</div> </div>
<a <a rel="noopener noreferrer" href="https://github.com/ethereum/remix-project/tree/master/apps/vyper" target="_blank">
rel="noopener noreferrer" <i className="fab fa-github"></i>
href="https://github.com/ethereum/remix-project/tree/master/apps/vyper"
target="_blank"
>
<i className="fab fa-github"></i>
</a> </a>
</header> </header>
<section> <section>
@ -87,12 +81,7 @@ const App: React.FC = () => {
Clone Vyper examples repository Clone Vyper examples repository
</Button> </Button>
</div> </div>
<ToggleButtonGroup <ToggleButtonGroup name="remote" onChange={setEnvironment} type="radio" value={state.environment}>
name="remote"
onChange={setEnvironment}
type="radio"
value={state.environment}
>
<ToggleButton data-id="remote-compiler" variant="secondary" name="remote" value="remote"> <ToggleButton data-id="remote-compiler" variant="secondary" name="remote" value="remote">
Remote Compiler v0.2.16 Remote Compiler v0.2.16
</ToggleButton> </ToggleButton>
@ -100,20 +89,10 @@ const App: React.FC = () => {
Local Compiler Local Compiler
</ToggleButton> </ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup>
<LocalUrlInput <LocalUrlInput url={state.localUrl} setUrl={setLocalUrl} environment={state.environment} />
url={state.localUrl}
setUrl={setLocalUrl}
environment={state.environment}
/>
<WarnRemote environment={state.environment} /> <WarnRemote environment={state.environment} />
<div className="px-4" id="compile-btn"> <div className="px-4" id="compile-btn">
<CompilerButton <CompilerButton compilerUrl={compilerUrl()} contract={contract} setOutput={(name, update) => setOutput({...output, [name]: update})} />
compilerUrl={compilerUrl()}
contract={contract}
setOutput={(name, update) =>
setOutput({ ...output, [name]: update })
}
/>
</div> </div>
<article id="result" className="px-2"> <article id="result" className="px-2">
<VyperResult output={contract ? output[contract] : undefined} /> <VyperResult output={contract ? output[contract] : undefined} />

@ -1,22 +1,14 @@
import React from 'react' import React from 'react'
import { import {isVyper, compile, toStandardOutput, VyperCompilationOutput, isCompilationError, remixClient} from '../utils'
isVyper,
compile,
toStandardOutput,
VyperCompilationOutput,
isCompilationError,
remixClient
} from '../utils'
import Button from 'react-bootstrap/Button' import Button from 'react-bootstrap/Button'
interface Props { interface Props {
compilerUrl: string compilerUrl: string
contract?: string, contract?: string
setOutput: (name: string, output: VyperCompilationOutput) => void setOutput: (name: string, output: VyperCompilationOutput) => void
} }
function CompilerButton({ contract, setOutput, compilerUrl }: Props) { function CompilerButton({contract, setOutput, compilerUrl}: Props) {
if (!contract || !contract) { if (!contract || !contract) {
return <Button disabled>No contract selected</Button> return <Button disabled>No contract selected</Button>
} }
@ -33,9 +25,9 @@ function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
try { try {
_contract = await remixClient.getContract() _contract = await remixClient.getContract()
} catch (e: any) { } catch (e: any) {
setOutput('', { status: 'failed', message: e.message}) setOutput('', {status: 'failed', message: e.message})
return return
} }
remixClient.changeStatus({ remixClient.changeStatus({
key: 'loading', key: 'loading',
type: 'info', type: 'info',
@ -45,17 +37,17 @@ function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
try { try {
output = await compile(compilerUrl, _contract) output = await compile(compilerUrl, _contract)
} catch (e: any) { } catch (e: any) {
setOutput(_contract.name, { status: 'failed', message: e.message}) setOutput(_contract.name, {status: 'failed', message: e.message})
return return
} }
setOutput(_contract.name, output) setOutput(_contract.name, output)
// ERROR // ERROR
if (isCompilationError(output)) { if (isCompilationError(output)) {
const line = output.line const line = output.line
if (line) { if (line) {
const lineColumnPos = { const lineColumnPos = {
start: { line: line - 1, column: 10 }, start: {line: line - 1, column: 10},
end: { line: line - 1, column: 10 } end: {line: line - 1, column: 10}
} }
remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4') remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4')
} else { } else {
@ -70,12 +62,12 @@ function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
if (message && message.split('\n\n').length > 0) { if (message && message.split('\n\n').length > 0) {
try { try {
message = message.split('\n\n')[message.split('\n\n').length - 1] message = message.split('\n\n')[message.split('\n\n').length - 1]
} catch (e) {} } catch (e) {}
} }
if (location.length > 0) { if (location.length > 0) {
const lineColumnPos = { const lineColumnPos = {
start: { line: parseInt(location[0]) - 1, column: 10 }, start: {line: parseInt(location[0]) - 1, column: 10},
end: { line: parseInt(location[0]) - 1, column: 10 } end: {line: parseInt(location[0]) - 1, column: 10}
} }
remixClient.highlight(lineColumnPos as any, _contract.name, message) remixClient.highlight(lineColumnPos as any, _contract.name, message)
} }
@ -105,7 +97,7 @@ function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
return ( return (
<Button data-id="compile" onClick={compileContract} variant="primary" title={contract} className="d-flex flex-column"> <Button data-id="compile" onClick={compileContract} variant="primary" title={contract} className="d-flex flex-column">
<span>Compile</span> <span>Compile</span>
<span className="overflow-hidden text-truncate text-nowrap" >{contract}</span> <span className="overflow-hidden text-truncate text-nowrap">{contract}</span>
</Button> </Button>
) )
} }

@ -3,12 +3,11 @@ import Form from 'react-bootstrap/Form'
interface Props { interface Props {
url: string url: string
setUrl: (url: string) => void, setUrl: (url: string) => void
environment: 'remote' | 'local' environment: 'remote' | 'local'
} }
function LocalUrlInput({ url, setUrl, environment }: Props) { function LocalUrlInput({url, setUrl, environment}: Props) {
if (environment === 'remote') { if (environment === 'remote') {
return <></> return <></>
} }
@ -20,17 +19,13 @@ function LocalUrlInput({ url, setUrl, environment }: Props) {
return ( return (
<Form id="local-url"> <Form id="local-url">
<Form.Group controlId="localUrl"> <Form.Group controlId="localUrl">
<Form.Text className="text-warning pb-2">Currently we support vyper version > 0.2.16</Form.Text> <Form.Text className="text-warning pb-2">{'Currently we support vyper version > 0.2.16'}</Form.Text>
<Form.Label>Local Compiler Url</Form.Label> <Form.Label>Local Compiler Url</Form.Label>
<Form.Control onBlur={updateUrl} <Form.Control onBlur={updateUrl} defaultValue={url} type="email" placeholder="eg http://localhost:8000/compile" />
defaultValue={url} <Form.Text className="text-muted"></Form.Text>
type="email"
placeholder="eg http://localhost:8000/compile" />
<Form.Text className="text-muted">
</Form.Text>
</Form.Group> </Form.Group>
</Form> </Form>
) )
} }
export default LocalUrlInput; export default LocalUrlInput

@ -1,43 +1,45 @@
import React, { useState } from 'react'; import React, {useState} from 'react'
import { import {VyperCompilationResult, VyperCompilationOutput, isCompilationError} from '../utils'
VyperCompilationResult,
VyperCompilationOutput,
isCompilationError,
} from '../utils';
import Tabs from 'react-bootstrap/Tabs' import Tabs from 'react-bootstrap/Tabs'
import Tab from 'react-bootstrap/Tab' import Tab from 'react-bootstrap/Tab'
import Button from 'react-bootstrap/Button'; import Button from 'react-bootstrap/Button'
import JSONTree from 'react-json-view' import JSONTree from 'react-json-view'
import { CopyToClipboard } from '@remix-ui/clipboard' import {CopyToClipboard} from '@remix-ui/clipboard'
interface VyperResultProps { interface VyperResultProps {
output?: VyperCompilationOutput; output?: VyperCompilationOutput
} }
export type ExampleContract = { export type ExampleContract = {
name: string, name: string
address: string address: string
} }
function VyperResult({ output }: VyperResultProps) { function VyperResult({output}: VyperResultProps) {
const [ active, setActive ] = useState<keyof VyperCompilationResult>('abi') const [active, setActive] = useState<keyof VyperCompilationResult>('abi')
if (!output) return ( if (!output)
return (
<div id="result"> <div id="result">
<p className="my-3">No contract compiled yet.</p> <p className="my-3">No contract compiled yet.</p>
</div> </div>
) )
if (isCompilationError(output)) { if (isCompilationError(output)) {
return ( return (
<div id="result" className="error" title={output.message}> <div id="result" className="error" title={output.message}>
<i className="fas fa-exclamation-circle text-danger"></i> <i className="fas fa-exclamation-circle text-danger"></i>
<pre data-id="error-message" className="px-2 w-100 alert alert-danger" style={{ <pre
fontSize: "0.5rem", data-id="error-message"
overflowX: "hidden", className="px-2 w-100 alert alert-danger"
textOverflow: "ellipsis" style={{
}}>{output.message}</pre> fontSize: '0.5rem',
overflowX: 'hidden',
textOverflow: 'ellipsis'
}}
>
{output.message}
</pre>
</div> </div>
) )
} }
@ -46,30 +48,38 @@ function VyperResult({ output }: VyperResultProps) {
<Tabs id="result" activeKey={active} onSelect={(key: any) => setActive(key)}> <Tabs id="result" activeKey={active} onSelect={(key: any) => setActive(key)}>
<Tab eventKey="abi" title="ABI"> <Tab eventKey="abi" title="ABI">
<CopyToClipboard getContent={() => JSON.stringify(output.abi)}> <CopyToClipboard getContent={() => JSON.stringify(output.abi)}>
<Button variant="info" className="copy" data-id="copy-abi">Copy ABI</Button> <Button variant="info" className="copy" data-id="copy-abi">
Copy ABI
</Button>
</CopyToClipboard> </CopyToClipboard>
<JSONTree src={output.abi} /> <JSONTree src={output.abi} />
</Tab> </Tab>
<Tab eventKey="bytecode" title="Bytecode"> <Tab eventKey="bytecode" title="Bytecode">
<CopyToClipboard getContent={() => output.bytecode}> <CopyToClipboard getContent={() => output.bytecode}>
<Button variant="info" className="copy">Copy Bytecode</Button> <Button variant="info" className="copy">
Copy Bytecode
</Button>
</CopyToClipboard> </CopyToClipboard>
<textarea defaultValue={output.bytecode}></textarea> <textarea defaultValue={output.bytecode}></textarea>
</Tab> </Tab>
<Tab eventKey="bytecode_runtime" title="Runtime Bytecode"> <Tab eventKey="bytecode_runtime" title="Runtime Bytecode">
<CopyToClipboard getContent={() => output.bytecode_runtime}> <CopyToClipboard getContent={() => output.bytecode_runtime}>
<Button variant="info" className="copy">Copy Runtime Bytecode</Button> <Button variant="info" className="copy">
Copy Runtime Bytecode
</Button>
</CopyToClipboard> </CopyToClipboard>
<textarea defaultValue={output.bytecode_runtime}></textarea> <textarea defaultValue={output.bytecode_runtime}></textarea>
</Tab> </Tab>
<Tab eventKey="ir" title="LLL"> <Tab eventKey="ir" title="LLL">
<CopyToClipboard getContent={() => output.ir}> <CopyToClipboard getContent={() => output.ir}>
<Button variant="info" className="copy">Copy LLL Code</Button> <Button variant="info" className="copy">
Copy LLL Code
</Button>
</CopyToClipboard> </CopyToClipboard>
<textarea defaultValue={output.ir}></textarea> <textarea defaultValue={output.ir}></textarea>
</Tab> </Tab>
</Tabs> </Tabs>
); )
} }
export default VyperResult; export default VyperResult

@ -4,8 +4,7 @@ interface Props {
environment: 'remote' | 'local' environment: 'remote' | 'local'
} }
function WarnRemoteLabel({ environment }: Props) { function WarnRemoteLabel({environment}: Props) {
if (environment === 'local') { if (environment === 'local') {
return <></> return <></>
} }
@ -15,4 +14,4 @@ function WarnRemoteLabel({ environment }: Props) {
) )
} }
export default WarnRemoteLabel; export default WarnRemoteLabel

@ -1,16 +1,16 @@
import { CompilationResult, ABIDescription } from "@remixproject/plugin-api"; import {CompilationResult, ABIDescription} from '@remixproject/plugin-api'
export interface Contract { export interface Contract {
name: string; name: string
content: string; content: string
} }
export interface VyperCompilationResult { export interface VyperCompilationResult {
status: 'success', status: 'success'
bytecode: string, bytecode: string
bytecode_runtime: string, bytecode_runtime: string
abi: ABIDescription[], abi: ABIDescription[]
ir: string, ir: string
method_identifiers: { method_identifiers: {
[method: string]: string [method: string]: string
} }
@ -32,7 +32,7 @@ export function isCompilationError(output: VyperCompilationOutput): output is Vy
/** /**
* Compile the a contract * Compile the a contract
* @param url The url of the compiler * @param url The url of the compiler
* @param contract The name and content of the contract * @param contract The name and content of the contract
*/ */
export async function compile(url: string, contract: Contract): Promise<VyperCompilationOutput> { export async function compile(url: string, contract: Contract): Promise<VyperCompilationOutput> {
@ -45,8 +45,8 @@ export async function compile(url: string, contract: Contract): Promise<VyperCom
} }
const response = await fetch(url, { const response = await fetch(url, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ code: contract.content }) body: JSON.stringify({code: contract.content})
}) })
if (response.status === 404) { if (response.status === 404) {
@ -64,8 +64,8 @@ export async function compile(url: string, contract: Contract): Promise<VyperCom
* @param compilationResult Result returned by the compiler * @param compilationResult Result returned by the compiler
*/ */
export function toStandardOutput(fileName: string, compilationResult: VyperCompilationResult): CompilationResult { export function toStandardOutput(fileName: string, compilationResult: VyperCompilationResult): CompilationResult {
const contractName = fileName.split('/').slice(-1)[0].split('.')[0]; const contractName = fileName.split('/').slice(-1)[0].split('.')[0]
const methodIdentifiers = JSON.parse(JSON.stringify(compilationResult['method_identifiers']).replace(/0x/g,'')); const methodIdentifiers = JSON.parse(JSON.stringify(compilationResult['method_identifiers']).replace(/0x/g, ''))
return { return {
sources: { sources: {
[fileName]: { [fileName]: {
@ -84,23 +84,22 @@ export function toStandardOutput(fileName: string, compilationResult: VyperCompi
evm: { evm: {
bytecode: { bytecode: {
linkReferences: {}, linkReferences: {},
object: compilationResult['bytecode'].replace('0x',''), object: compilationResult['bytecode'].replace('0x', ''),
opcodes: "" opcodes: ''
}, },
deployedBytecode: { deployedBytecode: {
linkReferences: {}, linkReferences: {},
object: compilationResult['bytecode_runtime'].replace('0x',''), object: compilationResult['bytecode_runtime'].replace('0x', ''),
opcodes: "" opcodes: ''
}, },
methodIdentifiers: methodIdentifiers methodIdentifiers: methodIdentifiers
} }
} }
} as any } as any
} }
}; }
} }
/* /*
export function createCompilationResultMessage(name: string, result: any) { export function createCompilationResultMessage(name: string, result: any) {
if(result.status == 'success') { if(result.status == 'success') {
@ -136,4 +135,4 @@ export function createCompilationResultMessage(name: string, result: any) {
ir: "" ir: ""
} }
} }
*/ */

@ -1,12 +1,12 @@
import { HighlightPosition, CompilationResult, RemixApi } from '@remixproject/plugin-api'; import {HighlightPosition, CompilationResult, RemixApi} from '@remixproject/plugin-api'
import { Api, Status } from '@remixproject/plugin-utils'; import {Api, Status} from '@remixproject/plugin-utils'
import { createClient } from '@remixproject/plugin-webview' import {createClient} from '@remixproject/plugin-webview'
import { PluginClient } from '@remixproject/plugin'; import {PluginClient} from '@remixproject/plugin'
import { Contract } from './compiler'; import {Contract} from './compiler'
import { ExampleContract } from '../components/VyperResult'; import {ExampleContract} from '../components/VyperResult'
export class RemixClient extends PluginClient { export class RemixClient extends PluginClient {
private client = createClient<Api, Readonly<RemixApi>>(this); private client = createClient<Api, Readonly<RemixApi>>(this)
loaded() { loaded() {
return this.client.onload() return this.client.onload()
@ -42,10 +42,19 @@ export class RemixClient extends PluginClient {
// @ts-ignore // @ts-ignore
this.call('notification', 'toast', 'cloning Vyper repository...') this.call('notification', 'toast', 'cloning Vyper repository...')
await this.call('manager', 'activatePlugin', 'dGitProvider') await this.call('manager', 'activatePlugin', 'dGitProvider')
// @ts-ignore await this.call(
await this.call('dGitProvider', 'clone', { url: 'https://github.com/vyperlang/vyper', token: null }, 'vyper-lang') 'dGitProvider',
// @ts-ignore 'clone',
this.call('notification', 'toast', 'Vyper repository cloned, the workspace Vyper has been created.') {url: 'https://github.com/vyperlang/vyper', token: null},
// @ts-ignore
'vyper-lang'
)
this.call(
// @ts-ignore
'notification',
'toast',
'Vyper repository cloned, the workspace Vyper has been created.'
)
} catch (e) { } catch (e) {
// @ts-ignore // @ts-ignore
this.call('notification', 'toast', e.message) this.call('notification', 'toast', e.message)
@ -54,7 +63,7 @@ export class RemixClient extends PluginClient {
/** Update the status of the plugin in remix */ /** Update the status of the plugin in remix */
changeStatus(status: Status) { changeStatus(status: Status) {
this.client.emit('statusChanged', status); this.client.emit('statusChanged', status)
} }
/** Highlight a part of the editor */ /** Highlight a part of the editor */
@ -94,15 +103,15 @@ export class RemixClient extends PluginClient {
const content = await this.client.call('fileManager', 'getFile', name) const content = await this.client.call('fileManager', 'getFile', name)
return { return {
name, name,
content, content
} }
} }
/** Emit an event to Remix with compilation result */ /** Emit an event to Remix with compilation result */
compilationFinish(title: string, content: string, data: CompilationResult) { compilationFinish(title: string, content: string, data: CompilationResult) {
this.client.emit('compilationFinished', title, content, 'vyper', data); this.client.emit('compilationFinished', title, content, 'vyper', data)
} }
} }
export const remixClient = new RemixClient() export const remixClient = new RemixClient()
// export const RemixClientContext = React.createContext(new RemixClient()) // export const RemixClientContext = React.createContext(new RemixClient())

@ -1,7 +1,11 @@
import { StrictMode } from 'react'; import {StrictMode} from 'react'
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom'
import App from './app/app'
import App from './app/app'; ReactDOM.render(
<StrictMode>
ReactDOM.render(<StrictMode><App /></StrictMode>, document.getElementById('root')); <App />
</StrictMode>,
document.getElementById('root')
)

@ -1,8 +1,7 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => { module.exports = composePlugins(withNx(), (config) => {
@ -12,56 +11,52 @@ module.exports = composePlugins(withNx(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify')
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
config.output.publicPath = '/' config.output.publicPath = '/'
// add copy & provide plugin // add copy & provide plugin
config.plugins.push( config.plugins.push(
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}) })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer // set minimizer
config.optimization.minimizer = [ config.optimization.minimizer = [
new TerserPlugin({ new TerserPlugin({
@ -71,17 +66,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/
} }
return config; return config
}); })

@ -1,9 +1,9 @@
import React, { useEffect, useState } from 'react' import React, {useEffect, useState} from 'react'
import '../css/app.css' import '../css/app.css'
import '@fortawesome/fontawesome-free/css/all.css' import '@fortawesome/fontawesome-free/css/all.css'
import type { EthereumClient } from '@web3modal/ethereum' import type {EthereumClient} from '@web3modal/ethereum'
import { WalletConnectRemixClient } from '../services/WalletConnectRemixClient' import {WalletConnectRemixClient} from '../services/WalletConnectRemixClient'
import { WalletConnectUI } from './walletConnectUI' import {WalletConnectUI} from './walletConnectUI'
const remix = new WalletConnectRemixClient() const remix = new WalletConnectRemixClient()
@ -13,12 +13,12 @@ function App() {
const [theme, setTheme] = useState<string>('dark') const [theme, setTheme] = useState<string>('dark')
useEffect(() => { useEffect(() => {
(async () => { ;(async () => {
await remix.initClient() await remix.initClient()
remix.internalEvents.on('themeChanged', (theme: string) => { remix.internalEvents.on('themeChanged', (theme: string) => {
setTheme(theme) setTheme(theme)
}) })
setWagmiConfig(remix.wagmiConfig) setWagmiConfig(remix.wagmiConfig)
setEthereumClient(remix.ethereumClient) setEthereumClient(remix.ethereumClient)
})() })()
@ -26,10 +26,10 @@ function App() {
return ( return (
<div className="App"> <div className="App">
<h4 className='mt-1'>WalletConnect</h4> <h4 className="mt-1">WalletConnect</h4>
{ ethereumClient && wagmiConfig && <WalletConnectUI wagmiConfig={wagmiConfig} ethereumClient={ethereumClient} theme={theme} /> } {ethereumClient && wagmiConfig && <WalletConnectUI wagmiConfig={wagmiConfig} ethereumClient={ethereumClient} theme={theme} />}
</div> </div>
) )
} }
export default App export default App

@ -1,17 +1,16 @@
import { Web3Button, Web3Modal } from "@web3modal/react" import {Web3Button, Web3Modal} from '@web3modal/react'
import { WagmiConfig } from "wagmi" import {WagmiConfig} from 'wagmi'
import { PROJECT_ID } from "../services/constant" import {PROJECT_ID} from '../services/constant'
export function WalletConnectUI ({ ethereumClient, wagmiConfig, theme }) { export function WalletConnectUI({ethereumClient, wagmiConfig, theme}) {
return (
return ( <div>
<div> <div style={{display: 'inline-block'}}>
<div style={{ display: 'inline-block' }}> <WagmiConfig config={wagmiConfig}>
<WagmiConfig config={wagmiConfig}> <Web3Button label="Connect to a wallet" />
<Web3Button label='Connect to a wallet' /> </WagmiConfig>
</WagmiConfig> </div>
</div> <Web3Modal projectId={PROJECT_ID} ethereumClient={ethereumClient} themeMode={theme} />
<Web3Modal projectId={PROJECT_ID} ethereumClient={ethereumClient} themeMode={theme} /> </div>
</div> )
)
} }

@ -2,7 +2,4 @@ import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import App from './app/app' import App from './app/app'
ReactDOM.render( ReactDOM.render(<App />, document.getElementById('root'))
<App />,
document.getElementById('root')
)

@ -1,7 +1,7 @@
const { composePlugins, withNx } = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => { module.exports = composePlugins(withNx(), (config) => {
@ -10,30 +10,29 @@ module.exports = composePlugins(withNx(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify')
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
solc: 'solc', solc: 'solc'
} }
// add public path // add public path
@ -44,27 +43,26 @@ module.exports = composePlugins(withNx(), (config) => {
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'], url: ['url', 'URL'],
process: 'process/browser', process: 'process/browser'
}) })
) )
// set the define plugin to load the WALLET_CONNECT_PROJECT_ID // set the define plugin to load the WALLET_CONNECT_PROJECT_ID
config.plugins.push( config.plugins.push(
new webpack.DefinePlugin({ new webpack.DefinePlugin({
WALLET_CONNECT_PROJECT_ID: JSON.stringify(process.env.WALLET_CONNECT_PROJECT_ID), WALLET_CONNECT_PROJECT_ID: JSON.stringify(process.env.WALLET_CONNECT_PROJECT_ID)
}) })
) )
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer // set minimizer
config.optimization.minimizer = [ config.optimization.minimizer = [
new TerserPlugin({ new TerserPlugin({
@ -74,17 +72,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false, compress: false,
mangle: false, mangle: false,
format: { format: {
comments: false, comments: false
}, }
}, },
extractComments: false, extractComments: false
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin()
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/
} }
return config; return config
}); })

@ -10,8 +10,7 @@ const shortFilename = 'simple_storage.sol'
const inputJson = { const inputJson = {
language: 'Solidity', language: 'Solidity',
sources: { sources: {},
},
settings: { settings: {
optimizer: { optimizer: {
enabled: true, enabled: true,
@ -19,14 +18,16 @@ const inputJson = {
}, },
outputSelection: { outputSelection: {
'*': { '*': {
'': [ 'ast' ], '': ['ast'],
'*': [ 'abi', 'metadata', 'devdoc', 'userdoc', 'evm.legacyAssembly', 'evm.bytecode', 'evm.deployedBytecode', 'evm.methodIdentifiers', 'evm.gasEstimates' ] '*': ['abi', 'metadata', 'devdoc', 'userdoc', 'evm.legacyAssembly', 'evm.bytecode', 'evm.deployedBytecode', 'evm.methodIdentifiers', 'evm.gasEstimates']
} }
} }
} }
} }
inputJson.sources[shortFilename] = {content: fs.readFileSync(filename).toString()} inputJson.sources[shortFilename] = {
content: fs.readFileSync(filename).toString()
}
console.dir(inputJson) console.dir(inputJson)
@ -36,7 +37,7 @@ const compilationData = JSON.parse(solc.compileStandardWrapper(JSON.stringify(in
console.dir(Object.keys(compilationData)) console.dir(Object.keys(compilationData))
const compilation = {} const compilation = {}
compilation['data'] = compilationData compilation['data'] = compilationData
compilation['source'] = { sources: inputJson.sources } compilation['source'] = {sources: inputJson.sources}
console.dir(compilation) console.dir(compilation)
console.dir(compilation['data'].errors) console.dir(compilation['data'].errors)
@ -106,4 +107,3 @@ repl.start({
}) })
module.exports = cmdLine module.exports = cmdLine

@ -1,10 +1,10 @@
import React, { useEffect, useState, useRef } from 'react' import React, {useEffect, useState, useRef} from 'react'
import Draggable from 'react-draggable' import Draggable from 'react-draggable'
import './dragbar.css' import './dragbar.css'
interface IRemixDragBarUi { interface IRemixDragBarUi {
refObject: React.MutableRefObject<any>; refObject: React.MutableRefObject<any>
setHideStatus: (hide: boolean) => void; setHideStatus: (hide: boolean) => void
hidden: boolean hidden: boolean
minWidth: number minWidth: number
maximiseTrigger: number maximiseTrigger: number
@ -53,8 +53,7 @@ const DragBar = (props: IRemixDragBarUi) => {
useEffect(() => { useEffect(() => {
window.addEventListener('resize', handleResize) window.addEventListener('resize', handleResize)
// TODO: not a good way to wait on the ref doms element to be rendered of course // TODO: not a good way to wait on the ref doms element to be rendered of course
setTimeout(() => setTimeout(() => handleResize(), 2000)
handleResize(), 2000)
return () => window.removeEventListener('resize', handleResize) return () => window.removeEventListener('resize', handleResize)
}, []) }, [])
@ -64,7 +63,7 @@ const DragBar = (props: IRemixDragBarUi) => {
setDragBarPosX(offset) setDragBarPosX(offset)
props.setHideStatus(true) props.setHideStatus(true)
} else { } else {
props.refObject.current.style.width = (data.x - offset) + 'px' props.refObject.current.style.width = data.x - offset + 'px'
setTimeout(() => { setTimeout(() => {
props.setHideStatus(false) props.setHideStatus(false)
setDragBarPosX(offset + props.refObject.current.offsetWidth) setDragBarPosX(offset + props.refObject.current.offsetWidth)
@ -75,12 +74,14 @@ const DragBar = (props: IRemixDragBarUi) => {
function startDrag() { function startDrag() {
setDragState(true) setDragState(true)
} }
return <> return (
<div className={`overlay ${dragState ? '' : 'd-none'}`} ></div> <>
<Draggable nodeRef={nodeRef} position={{ x: dragBarPosX, y: 0 }} onStart={startDrag} onStop={stopDrag} axis="x"> <div className={`overlay ${dragState ? '' : 'd-none'}`}></div>
<div ref={nodeRef} className={`dragbar ${dragState ? 'ondrag' : ''}`}></div> <Draggable nodeRef={nodeRef} position={{x: dragBarPosX, y: 0}} onStart={startDrag} onStop={stopDrag} axis="x">
</Draggable> <div ref={nodeRef} className={`dragbar ${dragState ? 'ondrag' : ''}`}></div>
</> </Draggable>
</>
)
} }
export default DragBar export default DragBar

@ -1,13 +1,13 @@
import React, { useContext, useEffect } from 'react' import React, {useContext, useEffect} from 'react'
import { AppContext } from '../../context/context' import {AppContext} from '../../context/context'
import { useDialogDispatchers } from '../../context/provider' import {useDialogDispatchers} from '../../context/provider'
const DialogViewPlugin = () => { const DialogViewPlugin = () => {
const { modal, alert, toast } = useDialogDispatchers() const {modal, alert, toast} = useDialogDispatchers()
const app = useContext(AppContext) const app = useContext(AppContext)
useEffect(() => { useEffect(() => {
app.modal.setDispatcher({ modal, alert, toast }) app.modal.setDispatcher({modal, alert, toast})
}, []) }, [])
return <></> return <></>
} }

@ -1,16 +1,17 @@
import React from 'react' import React from 'react'
import { useDialogDispatchers, useDialogs } from '../../context/provider' import {useDialogDispatchers, useDialogs} from '../../context/provider'
import { Toaster } from '@remix-ui/toaster' import {Toaster} from '@remix-ui/toaster'
import ModalWrapper from './modal-wrapper' import ModalWrapper from './modal-wrapper'
const AppDialogs = () => { const AppDialogs = () => {
const { handleHideModal, handleToaster } = useDialogDispatchers() const {handleHideModal, handleToaster} = useDialogDispatchers()
const { focusModal, focusToaster } = useDialogs() const {focusModal, focusToaster} = useDialogs()
return ( return (
<> <>
<ModalWrapper {...focusModal} handleHide={handleHideModal}></ModalWrapper> <ModalWrapper {...focusModal} handleHide={handleHideModal}></ModalWrapper>
<Toaster message={focusToaster.message} timestamp={focusToaster.timestamp} handleHide={handleToaster} /> <Toaster message={focusToaster.message} timestamp={focusToaster.timestamp} handleHide={handleToaster} />
</>) </>
)
} }
export default AppDialogs export default AppDialogs

@ -1,30 +1,60 @@
import React, { useContext, useEffect, useState } from 'react' import React, {useContext, useEffect, useState} from 'react'
import { AppContext } from '../../context/context' import {AppContext} from '../../context/context'
import { useDialogDispatchers } from '../../context/provider' import {useDialogDispatchers} from '../../context/provider'
declare global { declare global {
interface Window { interface Window {
_paq: any _paq: any
} }
} }
const _paq = window._paq = window._paq || [] const _paq = (window._paq = window._paq || [])
const MatomoDialog = (props) => { const MatomoDialog = (props) => {
const { settings, showMatamo, appManager } = useContext(AppContext) const {settings, showMatamo, appManager} = useContext(AppContext)
const { modal } = useDialogDispatchers() const {modal} = useDialogDispatchers()
const [visible, setVisible] = useState<boolean>(props.hide) const [visible, setVisible] = useState<boolean>(props.hide)
const message = () => { const message = () => {
return (<><p>An Opt-in version of <a href="https://matomo.org" target="_blank" rel="noreferrer">Matomo</a>, an open source data analytics platform is being used to improve Remix IDE.</p> return (
<p>We realize that our users have sensitive information in their code and that their privacy - your privacy - must be protected.</p> <>
<p>All data collected through Matomo is stored on our own server - no data is ever given to third parties. Our analytics reports are public: <a href="https://matomo.ethereum.org/index.php?module=MultiSites&action=index&idSite=23&period=day&date=yesterday" target="_blank" rel="noreferrer">take a look</a>.</p> <p>
<p>We do not collect nor store any personally identifiable information (PII).</p> An Opt-in version of{' '}
<p>For more info, see: <a href="https://medium.com/p/66ef69e14931/" target="_blank" rel="noreferrer">Matomo Analyitcs on Remix iDE</a>.</p> <a href="https://matomo.org" target="_blank" rel="noreferrer">
<p>You can change your choice in the Settings panel anytime.</p></>) Matomo
</a>
, an open source data analytics platform is being used to improve Remix IDE.
</p>
<p>We realize that our users have sensitive information in their code and that their privacy - your privacy - must be protected.</p>
<p>
All data collected through Matomo is stored on our own server - no data is ever given to third parties. Our analytics reports are public:{' '}
<a href="https://matomo.ethereum.org/index.php?module=MultiSites&action=index&idSite=23&period=day&date=yesterday" target="_blank" rel="noreferrer">
take a look
</a>
.
</p>
<p>We do not collect nor store any personally identifiable information (PII).</p>
<p>
For more info, see:{' '}
<a href="https://medium.com/p/66ef69e14931/" target="_blank" rel="noreferrer">
Matomo Analyitcs on Remix iDE
</a>
.
</p>
<p>You can change your choice in the Settings panel anytime.</p>
</>
)
} }
useEffect(() => { useEffect(() => {
if (visible && showMatamo) { if (visible && showMatamo) {
modal({ id: 'matomoModal', title: 'Help us to improve Remix IDE', message: message(), okLabel: 'Accept', okFn: handleModalOkClick, cancelLabel: 'Decline', cancelFn: declineModal }) modal({
id: 'matomoModal',
title: 'Help us to improve Remix IDE',
message: message(),
okLabel: 'Accept',
okFn: handleModalOkClick,
cancelLabel: 'Decline',
cancelFn: declineModal
})
} }
}, [visible]) }, [visible])
@ -44,7 +74,7 @@ const MatomoDialog = (props) => {
setVisible(false) setVisible(false)
} }
return (<></>) return <></>
} }
export default MatomoDialog export default MatomoDialog

@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState } from 'react' import React, {useEffect, useRef, useState} from 'react'
import { ModalDialog, ModalDialogProps, ValidationResult } from '@remix-ui/modal-dialog' import {ModalDialog, ModalDialogProps, ValidationResult} from '@remix-ui/modal-dialog'
import { ModalTypes } from '../../types' import {ModalTypes} from '../../types'
interface ModalWrapperProps extends ModalDialogProps { interface ModalWrapperProps extends ModalDialogProps {
modalType?: ModalTypes modalType?: ModalTypes
@ -28,26 +28,33 @@ const ModalWrapper = (props: ModalWrapperProps) => {
if (ref.current === undefined && formRef.current === undefined) { if (ref.current === undefined && formRef.current === undefined) {
onOkFn() onOkFn()
} else if (formRef.current) { } else if (formRef.current) {
(props.okFn) ? props.okFn(getFormData()) : props.resolve(getFormData()) props.okFn ? props.okFn(getFormData()) : props.resolve(getFormData())
} else if(ref.current) { } else if (ref.current) {
// @ts-ignore: Object is possibly 'null'. props.okFn
(props.okFn) ? props.okFn(ref.current.value) : props.resolve(ref.current.value) ? // @ts-ignore: Object is possibly 'null'.
props.okFn(ref.current.value)
: // @ts-ignore: Object is possibly 'null'.
props.resolve(ref.current.value)
} }
} }
const onOkFn = async () => { const onOkFn = async () => {
(props.okFn) ? props.okFn(data.current) : props.resolve(data.current || true) props.okFn ? props.okFn(data.current) : props.resolve(data.current || true)
} }
const onCancelFn = async () => { const onCancelFn = async () => {
(props.cancelFn) ? props.cancelFn() : props.resolve(false) props.cancelFn ? props.cancelFn() : props.resolve(false)
} }
const onInputChanged = (event) => { const onInputChanged = (event) => {
if (props.validationFn) { if (props.validationFn) {
const validation = props.validationFn(event.target.value) const validation = props.validationFn(event.target.value)
setState(prevState => { setState((prevState) => {
return { ...prevState, message: createModalMessage(props.defaultValue, validation), validation } return {
...prevState,
message: createModalMessage(props.defaultValue, validation),
validation
}
}) })
} }
} }
@ -56,8 +63,15 @@ const ModalWrapper = (props: ModalWrapperProps) => {
return ( return (
<> <>
{props.message} {props.message}
<input onChange={onInputChanged} type={props.modalType === ModalTypes.password ? 'password' : 'text'} defaultValue={defaultValue} data-id="modalDialogCustomPromp" ref={ref} className="form-control" /> <input
{validation && !validation.valid && <span className='text-warning'>{validation.message}</span>} onChange={onInputChanged}
type={props.modalType === ModalTypes.password ? 'password' : 'text'}
defaultValue={defaultValue}
data-id="modalDialogCustomPromp"
ref={ref}
className="form-control"
/>
{validation && !validation.valid && <span className="text-warning">{validation.message}</span>}
</> </>
) )
} }
@ -65,8 +79,8 @@ const ModalWrapper = (props: ModalWrapperProps) => {
const onFormChanged = () => { const onFormChanged = () => {
if (props.validationFn) { if (props.validationFn) {
const validation = props.validationFn(getFormData()) const validation = props.validationFn(getFormData())
setState(prevState => { setState((prevState) => {
return { ...prevState, message: createForm(validation), validation } return {...prevState, message: createForm(validation), validation}
}) })
} }
} }
@ -77,7 +91,7 @@ const ModalWrapper = (props: ModalWrapperProps) => {
<form onChange={onFormChanged} ref={formRef}> <form onChange={onFormChanged} ref={formRef}>
{props.message} {props.message}
</form> </form>
{validation && !validation.valid && <span className='text-warning'>{validation.message}</span>} {validation && !validation.valid && <span className="text-warning">{validation.message}</span>}
</> </>
) )
} }
@ -92,7 +106,7 @@ const ModalWrapper = (props: ModalWrapperProps) => {
...props, ...props,
okFn: onFinishPrompt, okFn: onFinishPrompt,
cancelFn: onCancelFn, cancelFn: onCancelFn,
message: createModalMessage(props.defaultValue, { valid: true }) message: createModalMessage(props.defaultValue, {valid: true})
}) })
break break
case ModalTypes.form: case ModalTypes.form:
@ -100,7 +114,7 @@ const ModalWrapper = (props: ModalWrapperProps) => {
...props, ...props,
okFn: onFinishPrompt, okFn: onFinishPrompt,
cancelFn: onCancelFn, cancelFn: onCancelFn,
message: createForm({ valid: true }) message: createForm({valid: true})
}) })
break break
default: default:
@ -122,14 +136,12 @@ const ModalWrapper = (props: ModalWrapperProps) => {
// reset the message and input if any, so when the modal is shown again it doesn't show the previous value. // reset the message and input if any, so when the modal is shown again it doesn't show the previous value.
const handleHide = () => { const handleHide = () => {
setState(prevState => { setState((prevState) => {
return { ...prevState, message: '' } return {...prevState, message: ''}
}) })
props.handleHide() props.handleHide()
} }
return ( return <ModalDialog id={props.id} {...state} handleHide={handleHide} />
<ModalDialog id={props.id} {...state} handleHide={handleHide} />
)
} }
export default ModalWrapper export default ModalWrapper

@ -1,22 +1,26 @@
import React, { useEffect, useState } from 'react' import React, {useEffect, useState} from 'react'
import { ModalDialog } from '@remix-ui/modal-dialog' import {ModalDialog} from '@remix-ui/modal-dialog'
import { useDialogDispatchers } from '../../context/provider' import {useDialogDispatchers} from '../../context/provider'
const OriginWarning = () => { const OriginWarning = () => {
const { alert } = useDialogDispatchers() const {alert} = useDialogDispatchers()
const [content, setContent] = useState<string>(null) const [content, setContent] = useState<string>(null)
useEffect(() => { useEffect(() => {
// check the origin and warn message // check the origin and warn message
if (window.location.hostname === 'yann300.github.io') { if (window.location.hostname === 'yann300.github.io') {
setContent('This UNSTABLE ALPHA branch of Remix has been moved to http://ethereum.github.io/remix-live-alpha.') setContent('This UNSTABLE ALPHA branch of Remix has been moved to http://ethereum.github.io/remix-live-alpha.')
} else if (window.location.hostname === 'remix-alpha.ethereum.org' || } else if (
(window.location.hostname === 'ethereum.github.io' && window.location.pathname.indexOf('/remix-live-alpha') === 0)) { window.location.hostname === 'remix-alpha.ethereum.org' ||
(window.location.hostname === 'ethereum.github.io' && window.location.pathname.indexOf('/remix-live-alpha') === 0)
) {
setContent('Welcome to the Remix alpha instance. Please use it to try out latest features. But use preferably https://remix.ethereum.org for any production work.') setContent('Welcome to the Remix alpha instance. Please use it to try out latest features. But use preferably https://remix.ethereum.org for any production work.')
} else if (window.location.protocol.indexOf('http') === 0 && } else if (
window.location.hostname !== 'remix.ethereum.org' && window.location.protocol.indexOf('http') === 0 &&
window.location.hostname !== 'localhost' && window.location.hostname !== 'remix.ethereum.org' &&
window.location.hostname !== '127.0.0.1') { window.location.hostname !== 'localhost' &&
window.location.hostname !== '127.0.0.1'
) {
setContent(`The Remix IDE has moved to http://remix.ethereum.org.\n setContent(`The Remix IDE has moved to http://remix.ethereum.org.\n
This instance of Remix you are visiting WILL NOT BE UPDATED.\n This instance of Remix you are visiting WILL NOT BE UPDATED.\n
Please make a backup of your contracts and start using http://remix.ethereum.org`) Please make a backup of your contracts and start using http://remix.ethereum.org`)
@ -25,11 +29,11 @@ const OriginWarning = () => {
useEffect(() => { useEffect(() => {
if (content) { if (content) {
alert({ id: 'warningOriging', title: null, message: content }) alert({id: 'warningOriging', title: null, message: content})
} }
}, [content]) }, [content])
return (<></>) return <></>
} }
export default OriginWarning export default OriginWarning

@ -1,16 +1,19 @@
import React from 'react' import React from 'react'
const RemixSplashScreen = (props) => { const RemixSplashScreen = (props) => {
return (<> <div style={{ display: props.hide ? 'none' : 'block' }} className='centered'> return (
<svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100"> <>
<path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z"/> {' '}
<path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z"/> <div style={{display: props.hide ? 'none' : 'block'}} className="centered">
<path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z"/> <svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100">
</svg> <path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z" />
<div className="info-secondary splash"> <path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z" />
REMIX IDE <path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z" />
</div> </svg>
</div></>) <div className="info-secondary splash">REMIX IDE</div>
</div>
</>
)
} }
export default RemixSplashScreen export default RemixSplashScreen

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import { AlertModal, AppModal } from '../interface' import {AlertModal, AppModal} from '../interface'
import { ModalInitialState } from '../state/modals' import {ModalInitialState} from '../state/modals'
export const AppContext = React.createContext<any>(null) export const AppContext = React.createContext<any>(null)
@ -8,12 +8,12 @@ export interface dispatchModalInterface {
modal: (data: AppModal) => void modal: (data: AppModal) => void
toast: (message: string | JSX.Element) => void toast: (message: string | JSX.Element) => void
alert: (data: AlertModal) => void alert: (data: AlertModal) => void
handleHideModal: () => void, handleHideModal: () => void
handleToaster: () => void handleToaster: () => void
} }
export const dispatchModalContext = React.createContext<dispatchModalInterface>({ export const dispatchModalContext = React.createContext<dispatchModalInterface>({
modal: (data: AppModal) => { }, modal: (data: AppModal) => {},
toast: (message: string | JSX.Element) => {}, toast: (message: string | JSX.Element) => {},
alert: (data: AlertModal) => {}, alert: (data: AlertModal) => {},
handleHideModal: () => {}, handleHideModal: () => {},

@ -1,13 +1,13 @@
import React, { useReducer } from 'react' import React, {useReducer} from 'react'
import { modalActionTypes } from '../actions/modals' import {modalActionTypes} from '../actions/modals'
import { AlertModal, AppModal } from '../interface' import {AlertModal, AppModal} from '../interface'
import { modalReducer } from '../reducer/modals' import {modalReducer} from '../reducer/modals'
import { ModalInitialState } from '../state/modals' import {ModalInitialState} from '../state/modals'
import { ModalTypes } from '../types' import {ModalTypes} from '../types'
import { AppContext, dispatchModalContext, modalContext } from './context' import {AppContext, dispatchModalContext, modalContext} from './context'
export const ModalProvider = ({ children = [], reducer = modalReducer, initialState = ModalInitialState } = {}) => { export const ModalProvider = ({children = [], reducer = modalReducer, initialState = ModalInitialState} = {}) => {
const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, initialState) const [{modals, toasters, focusModal, focusToaster}, dispatch] = useReducer(reducer, initialState)
const onNextFn = async () => { const onNextFn = async () => {
dispatch({ dispatch({
@ -16,17 +16,40 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
} }
const modal = (modalData: AppModal) => { const modal = (modalData: AppModal) => {
const { id, title, message, validationFn, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn, data } = modalData const {id, title, message, validationFn, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn, data} = modalData
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
dispatch({ dispatch({
type: modalActionTypes.setModal, type: modalActionTypes.setModal,
payload: { id, title, message, okLabel, validationFn, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn, resolve, next: onNextFn, data } payload: {
id,
title,
message,
okLabel,
validationFn,
okFn,
cancelLabel,
cancelFn,
modalType: modalType || ModalTypes.default,
defaultValue: defaultValue,
hideFn,
resolve,
next: onNextFn,
data
}
}) })
}) })
} }
const alert = (modalData: AlertModal) => { const alert = (modalData: AlertModal) => {
return modal({ id: modalData.id, title: modalData.title || 'Alert', message: modalData.message || modalData.title, okLabel: 'OK', okFn: (value?:any) => {}, cancelLabel: '', cancelFn: () => {} }) return modal({
id: modalData.id,
title: modalData.title || 'Alert',
message: modalData.message || modalData.title,
okLabel: 'OK',
okFn: (value?: any) => {},
cancelLabel: '',
cancelFn: () => {}
})
} }
const handleHideModal = () => { const handleHideModal = () => {
@ -39,7 +62,7 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
const toast = (message: string | JSX.Element) => { const toast = (message: string | JSX.Element) => {
dispatch({ dispatch({
type: modalActionTypes.setToast, type: modalActionTypes.setToast,
payload: { message, timestamp: Date.now() } payload: {message, timestamp: Date.now()}
}) })
} }
@ -50,17 +73,19 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
}) })
} }
return (<dispatchModalContext.Provider value={{ modal, toast, alert, handleHideModal, handleToaster }}> return (
<modalContext.Provider value={{ modals, toasters, focusModal, focusToaster }}> <dispatchModalContext.Provider value={{modal, toast, alert, handleHideModal, handleToaster}}>
{children} <modalContext.Provider value={{modals, toasters, focusModal, focusToaster}}>{children}</modalContext.Provider>
</modalContext.Provider> </dispatchModalContext.Provider>
</dispatchModalContext.Provider>) )
} }
export const AppProvider = ({ children = [], value = {} } = {}) => { export const AppProvider = ({children = [], value = {}} = {}) => {
return <AppContext.Provider value={value}> return (
<ModalProvider>{children}</ModalProvider> <AppContext.Provider value={value}>
</AppContext.Provider> <ModalProvider>{children}</ModalProvider>
</AppContext.Provider>
)
} }
export const useDialogs = () => { export const useDialogs = () => {

@ -1,15 +1,15 @@
import React, { useEffect, useRef, useState } from 'react' import React, {useEffect, useRef, useState} from 'react'
import './style/remix-app.css' import './style/remix-app.css'
import { RemixUIMainPanel } from '@remix-ui/panel' import {RemixUIMainPanel} from '@remix-ui/panel'
import MatomoDialog from './components/modals/matomo' import MatomoDialog from './components/modals/matomo'
import OriginWarning from './components/modals/origin-warning' import OriginWarning from './components/modals/origin-warning'
import DragBar from './components/dragbar/dragbar' import DragBar from './components/dragbar/dragbar'
import { AppProvider } from './context/provider' import {AppProvider} from './context/provider'
import AppDialogs from './components/modals/dialogs' import AppDialogs from './components/modals/dialogs'
import DialogViewPlugin from './components/modals/dialogViewPlugin' import DialogViewPlugin from './components/modals/dialogViewPlugin'
import { AppContext } from './context/context' import {AppContext} from './context/context'
import { IntlProvider } from 'react-intl' import {IntlProvider} from 'react-intl'
import { CustomTooltip } from '@remix-ui/helper'; import {CustomTooltip} from '@remix-ui/helper'
interface IRemixAppUi { interface IRemixAppUi {
app: any app: any
@ -20,11 +20,14 @@ const RemixApp = (props: IRemixAppUi) => {
const [hideSidePanel, setHideSidePanel] = useState<boolean>(false) const [hideSidePanel, setHideSidePanel] = useState<boolean>(false)
const [maximiseTrigger, setMaximiseTrigger] = useState<number>(0) const [maximiseTrigger, setMaximiseTrigger] = useState<number>(0)
const [resetTrigger, setResetTrigger] = useState<number>(0) const [resetTrigger, setResetTrigger] = useState<number>(0)
const [locale, setLocale] = useState<{ code:string; messages:any }>({ code:'en', messages:{} }); const [locale, setLocale] = useState<{code: string; messages: any}>({
code: 'en',
messages: {}
})
const sidePanelRef = useRef(null) const sidePanelRef = useRef(null)
useEffect(() => { useEffect(() => {
async function activateApp () { async function activateApp() {
props.app.themeModule.initTheme(() => { props.app.themeModule.initTheme(() => {
setAppReady(true) setAppReady(true)
props.app.activate() props.app.activate()
@ -37,9 +40,9 @@ const RemixApp = (props: IRemixAppUi) => {
} }
}, []) }, [])
function setListeners () { function setListeners() {
props.app.sidePanel.events.on('toggle', () => { props.app.sidePanel.events.on('toggle', () => {
setHideSidePanel(prev => { setHideSidePanel((prev) => {
return !prev return !prev
}) })
}) })
@ -55,13 +58,13 @@ const RemixApp = (props: IRemixAppUi) => {
}) })
props.app.layout.event.on('maximisesidepanel', () => { props.app.layout.event.on('maximisesidepanel', () => {
setMaximiseTrigger(prev => { setMaximiseTrigger((prev) => {
return prev + 1 return prev + 1
}) })
}) })
props.app.layout.event.on('resetsidepanel', () => { props.app.layout.event.on('resetsidepanel', () => {
setResetTrigger(prev => { setResetTrigger((prev) => {
return prev + 1 return prev + 1
}) })
}) })
@ -85,17 +88,24 @@ const RemixApp = (props: IRemixAppUi) => {
<OriginWarning></OriginWarning> <OriginWarning></OriginWarning>
<MatomoDialog hide={!appReady}></MatomoDialog> <MatomoDialog hide={!appReady}></MatomoDialog>
<div className={`remixIDE ${appReady ? '' : 'd-none'}`} data-id="remixIDE"> <div className={`remixIDE ${appReady ? '' : 'd-none'}`} data-id="remixIDE">
<div id="icon-panel" data-id="remixIdeIconPanel" className="custom_icon_panel iconpanel bg-light">{props.app.menuicons.render()}</div> <div id="icon-panel" data-id="remixIdeIconPanel" className="custom_icon_panel iconpanel bg-light">
<div ref={sidePanelRef} id="side-panel" data-id="remixIdeSidePanel" className={`sidepanel border-right border-left ${hideSidePanel ? 'd-none' : ''}`}>{props.app.sidePanel.render()}</div> {props.app.menuicons.render()}
<DragBar resetTrigger={resetTrigger} maximiseTrigger={maximiseTrigger} minWidth={285} refObject={sidePanelRef} hidden={hideSidePanel} setHideStatus={setHideSidePanel}></DragBar> </div>
<div id="main-panel" data-id="remixIdeMainPanel" className='mainpanel d-flex'> <div ref={sidePanelRef} id="side-panel" data-id="remixIdeSidePanel" className={`sidepanel border-right border-left ${hideSidePanel ? 'd-none' : ''}`}>
{props.app.sidePanel.render()}
</div>
<DragBar
resetTrigger={resetTrigger}
maximiseTrigger={maximiseTrigger}
minWidth={285}
refObject={sidePanelRef}
hidden={hideSidePanel}
setHideStatus={setHideSidePanel}
></DragBar>
<div id="main-panel" data-id="remixIdeMainPanel" className="mainpanel d-flex">
<RemixUIMainPanel Context={AppContext}></RemixUIMainPanel> <RemixUIMainPanel Context={AppContext}></RemixUIMainPanel>
<CustomTooltip <CustomTooltip placement="bottom" tooltipId="overlay-tooltip-all-tabs" tooltipText="Scroll to see all tabs">
placement="bottom" <div className="remix-ui-tabs_end remix-bg-opacity position-absolute position-fixed"></div>
tooltipId="overlay-tooltip-all-tabs"
tooltipText="Scroll to see all tabs"
>
<div className='remix-ui-tabs_end remix-bg-opacity position-absolute position-fixed'></div>
</CustomTooltip> </CustomTooltip>
</div> </div>
</div> </div>

@ -1,8 +1,7 @@
import { CustomTooltip } from '@remix-ui/helper'; import {CustomTooltip} from '@remix-ui/helper'
import React, { CSSProperties } from 'react' //eslint-disable-line import React, {CSSProperties} from 'react' //eslint-disable-line
import './remix-ui-checkbox.css' import './remix-ui-checkbox.css'
type Placement = import('react-overlays/usePopper').Placement; type Placement = import('react-overlays/usePopper').Placement
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface RemixUiCheckboxProps { export interface RemixUiCheckboxProps {
@ -38,27 +37,23 @@ export const RemixUiCheckbox = ({
optionalClassName = '', optionalClassName = '',
display = 'flex', display = 'flex',
disabled, disabled,
tooltipPlacement = 'right', tooltipPlacement = 'right'
}: RemixUiCheckboxProps) => { }: RemixUiCheckboxProps) => {
const childJSXWithTooltip = ( const childJSXWithTooltip = (
<CustomTooltip <CustomTooltip tooltipText={title} tooltipId={`${name}Tooltip`} placement={tooltipPlacement}>
tooltipText={title} <div
tooltipId={`${name}Tooltip`} className={`listenOnNetwork_2A0YE0 custom-control custom-checkbox ${optionalClassName}`}
placement={tooltipPlacement} style={
> {
<div className={`listenOnNetwork_2A0YE0 custom-control custom-checkbox ${optionalClassName}`} style={{ display: display, alignItems: 'center', visibility: visibility } as CSSProperties } onClick={onClick}> display: display,
<input alignItems: 'center',
id={id} visibility: visibility
type={inputType} } as CSSProperties
onChange={onChange} }
style={{ verticalAlign: 'bottom' }} onClick={onClick}
name={name} >
className="custom-control-input" <input id={id} type={inputType} onChange={onChange} style={{verticalAlign: 'bottom'}} name={name} className="custom-control-input" checked={checked} disabled={disabled} />
checked={checked} <label className="form-check-label custom-control-label" id={`heading${categoryId}`} style={{paddingTop: '0.15rem'}} aria-disabled={disabled}>
disabled={disabled}
/>
<label className="form-check-label custom-control-label" id={`heading${categoryId}`} style={{ paddingTop: '0.15rem' }} aria-disabled={disabled}>
{name ? <div className="font-weight-bold">{itemName}</div> : ''} {name ? <div className="font-weight-bold">{itemName}</div> : ''}
{label} {label}
</label> </label>
@ -66,25 +61,25 @@ export const RemixUiCheckbox = ({
</CustomTooltip> </CustomTooltip>
) )
const childJSX = ( const childJSX = (
<div className="listenOnNetwork_2A0YE0 custom-control custom-checkbox" style={{ display: display, alignItems: 'center', visibility: visibility } as CSSProperties } onClick={onClick}> <div
<input className="listenOnNetwork_2A0YE0 custom-control custom-checkbox"
id={id} style={
type={inputType} {
onChange={onChange} display: display,
style={{ verticalAlign: 'bottom' }} alignItems: 'center',
name={name} visibility: visibility
className="custom-control-input" } as CSSProperties
checked={checked} }
/> onClick={onClick}
<label className="form-check-label custom-control-label" id={`heading${categoryId}`} style={{ paddingTop: '0.15rem' }}> >
<input id={id} type={inputType} onChange={onChange} style={{verticalAlign: 'bottom'}} name={name} className="custom-control-input" checked={checked} />
<label className="form-check-label custom-control-label" id={`heading${categoryId}`} style={{paddingTop: '0.15rem'}}>
{name ? <div className="font-weight-bold">{itemName}</div> : ''} {name ? <div className="font-weight-bold">{itemName}</div> : ''}
{label} {label}
</label> </label>
</div> </div>
) )
return ( return title ? childJSXWithTooltip : childJSX
title ? (childJSXWithTooltip) : (childJSX)
)
} }
export default RemixUiCheckbox export default RemixUiCheckbox

@ -1,23 +1,23 @@
import React, { useState } from 'react' import React, {useState} from 'react'
import copy from 'copy-to-clipboard' import copy from 'copy-to-clipboard'
import { Placement } from 'react-bootstrap/esm/Overlay' import {Placement} from 'react-bootstrap/esm/Overlay'
import './copy-to-clipboard.css' import './copy-to-clipboard.css'
import { CustomTooltip } from '@remix-ui/helper' import {CustomTooltip} from '@remix-ui/helper'
interface ICopyToClipboard { interface ICopyToClipboard {
content?: any, content?: any
tip?: string, tip?: string
icon?: string, icon?: string
direction?: Placement, direction?: Placement
className?: string, className?: string
title?: string, title?: string
children?: JSX.Element, children?: JSX.Element
getContent?: () => any getContent?: () => any
} }
export const CopyToClipboard = (props: ICopyToClipboard) => { export const CopyToClipboard = (props: ICopyToClipboard) => {
const { tip = 'Copy', icon = 'fa-copy', direction = 'right', getContent, children, ...otherProps } = props const {tip = 'Copy', icon = 'fa-copy', direction = 'right', getContent, children, ...otherProps} = props
let { content } = props let {content} = props
const [message, setMessage] = useState(tip) const [message, setMessage] = useState(tip)
const copyData = () => { const copyData = () => {
@ -37,7 +37,8 @@ export const CopyToClipboard = (props: ICopyToClipboard) => {
} }
const handleClick = (e: any) => { const handleClick = (e: any) => {
if (content) { // module `copy` keeps last copied thing in the memory, so don't show tooltip if nothing is copied, because nothing was added to memory if (content) {
// module `copy` keeps last copied thing in the memory, so don't show tooltip if nothing is copied, because nothing was added to memory
copyData() copyData()
} else { } else {
content = getContent && getContent() content = getContent && getContent()
@ -50,19 +51,11 @@ export const CopyToClipboard = (props: ICopyToClipboard) => {
setTimeout(() => setMessage(tip), 500) setTimeout(() => setMessage(tip), 500)
} }
const childJSX = ( const childJSX = children || <i className={`far ${icon} ml-1 p-2`} aria-hidden="true" {...otherProps}></i>
children || (<i className={`far ${icon} ml-1 p-2`} aria-hidden="true"
{...otherProps}
></i>)
)
return ( return (
<a href='#' onClick={handleClick} onMouseLeave={reset}> <a href="#" onClick={handleClick} onMouseLeave={reset}>
<CustomTooltip <CustomTooltip tooltipText={message} tooltipId="overlay-tooltip" placement={direction}>
tooltipText={message}
tooltipId="overlay-tooltip"
placement={direction}
>
{childJSX} {childJSX}
</CustomTooltip> </CustomTooltip>
</a> </a>

@ -1,11 +1,11 @@
import React, { useState, useEffect } from 'react' // eslint-disable-line import React, {useState, useEffect} from 'react' // eslint-disable-line
import { ExtractData, ExtractFunc } from '../types' // eslint-disable-line import {ExtractData, ExtractFunc} from '../types' // eslint-disable-line
export const useExtractData = (json, extractFunc?: ExtractFunc): Array<{ key: string, data: ExtractData }> => { export const useExtractData = (json, extractFunc?: ExtractFunc): Array<{key: string; data: ExtractData}> => {
const [data, setData] = useState([]) const [data, setData] = useState([])
useEffect(() => { useEffect(() => {
const data: Array<{ key: string, data: ExtractData }> = Object.keys(json).map((innerKey) => { const data: Array<{key: string; data: ExtractData}> = Object.keys(json).map((innerKey) => {
if (extractFunc) { if (extractFunc) {
return { return {
key: innerKey, key: innerKey,
@ -31,14 +31,14 @@ export const useExtractData = (json, extractFunc?: ExtractFunc): Array<{ key: st
if (item instanceof Array) { if (item instanceof Array) {
ret.children = item.map((item, index) => { ret.children = item.map((item, index) => {
return { key: index, value: item } return {key: index, value: item}
}) })
ret.self = 'Array' ret.self = 'Array'
ret.isNode = true ret.isNode = true
ret.isLeaf = false ret.isLeaf = false
} else if (item instanceof Object) { } else if (item instanceof Object) {
ret.children = Object.keys(item).map((key) => { ret.children = Object.keys(item).map((key) => {
return { key: key, value: item[key] } return {key: key, value: item[key]}
}) })
ret.self = 'Object' ret.self = 'Object'
ret.isNode = true ret.isNode = true

@ -1,8 +1,20 @@
import { CustomTooltip } from '@remix-ui/helper' import {CustomTooltip} from '@remix-ui/helper'
import React, { useState, useEffect } from 'react' // eslint-disable-line import React, {useState, useEffect} from 'react' // eslint-disable-line
import './button-navigator.css' import './button-navigator.css'
export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward, stepOverForward, jumpOut, jumpPreviousBreakpoint, jumpNextBreakpoint, jumpToException, revertedReason, stepState, jumpOutDisabled }) => { export const ButtonNavigation = ({
stepOverBack,
stepIntoBack,
stepIntoForward,
stepOverForward,
jumpOut,
jumpPreviousBreakpoint,
jumpNextBreakpoint,
jumpToException,
revertedReason,
stepState,
jumpOutDisabled
}) => {
const [state, setState] = useState({ const [state, setState] = useState({
intoBackDisabled: true, intoBackDisabled: true,
overBackDisabled: true, overBackDisabled: true,
@ -46,7 +58,7 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
intoForwardDisabled: stepState === 'end', intoForwardDisabled: stepState === 'end',
overForwardDisabled: stepState === 'end', overForwardDisabled: stepState === 'end',
jumpNextBreakpointDisabled: stepState === 'end', jumpNextBreakpointDisabled: stepState === 'end',
jumpOutDisabled: (jumpOutDisabled !== null) && (jumpOutDisabled !== undefined) ? jumpOutDisabled : true jumpOutDisabled: jumpOutDisabled !== null && jumpOutDisabled !== undefined ? jumpOutDisabled : true
} }
}) })
} }
@ -55,20 +67,51 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
const disableStepBtnStyle = 'stepButtonDisabled' const disableStepBtnStyle = 'stepButtonDisabled'
const disableJumpBtnStyle = 'jumpButtonDisabled' const disableJumpBtnStyle = 'jumpButtonDisabled'
const stepMarkupStructure = { const stepMarkupStructure = {
stepOverBackJSX : { stepOverBackJSX: {
markup: (<div className={state.overBackDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { stepOverBack && stepOverBack() }}> markup: (
<button id='overback' className='btn btn-link btn-sm stepButton m-0 p-0' onClick={() => { stepOverBack && stepOverBack() }} disabled={state.overBackDisabled} style={{ pointerEvents: 'none', color: 'white' }}> <div
<span className="fas fa-reply"></span> className={state.overBackDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}` : `${stepBtnStyle}`}
</button> onClick={() => {
</div>), stepOverBack && stepOverBack()
}}
>
<button
id="overback"
className="btn btn-link btn-sm stepButton m-0 p-0"
onClick={() => {
stepOverBack && stepOverBack()
}}
disabled={state.overBackDisabled}
style={{pointerEvents: 'none', color: 'white'}}
>
<span className="fas fa-reply"></span>
</button>
</div>
),
placement: 'top-start', placement: 'top-start',
tagId: 'overbackTooltip', tagId: 'overbackTooltip',
tooltipMsg: 'Step over back' tooltipMsg: 'Step over back'
}, },
stepBackJSX : { stepBackJSX: {
markup: ( markup: (
<div className={state.intoBackDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { stepIntoBack && stepIntoBack() }} data-id="buttonNavigatorIntoBack" id="buttonNavigatorIntoBackContainer"> <div
<button id='intoback' data-id="buttonNavigatorIntoBack" className='btn btn-link btn-sm stepButton m-0 p-0' onClick={() => { stepIntoBack && stepIntoBack() }} disabled={state.intoBackDisabled} style={{ pointerEvents: 'none', color: 'white' }}> className={state.intoBackDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}` : `${stepBtnStyle}`}
onClick={() => {
stepIntoBack && stepIntoBack()
}}
data-id="buttonNavigatorIntoBack"
id="buttonNavigatorIntoBackContainer"
>
<button
id="intoback"
data-id="buttonNavigatorIntoBack"
className="btn btn-link btn-sm stepButton m-0 p-0"
onClick={() => {
stepIntoBack && stepIntoBack()
}}
disabled={state.intoBackDisabled}
style={{pointerEvents: 'none', color: 'white'}}
>
<span className="fas fa-level-up-alt"></span> <span className="fas fa-level-up-alt"></span>
</button> </button>
</div> </div>
@ -77,12 +120,26 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
tagId: 'intobackTooltip', tagId: 'intobackTooltip',
tooltipMsg: 'Step back' tooltipMsg: 'Step back'
}, },
stepIntoJSX : { stepIntoJSX: {
markup: ( markup: (
<div className={state.intoForwardDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { stepIntoForward && stepIntoForward() }} data-id="buttonNavigatorIntoForward" id="buttonNavigatorIntoFowardContainer"> <div
<button id='intoforward' data-id="buttonNavigatorIntoForward" className='btn btn-link btn-sm stepButton m-0 p-0' onClick={() => { stepIntoForward && stepIntoForward() }} disabled={state.intoForwardDisabled} className={state.intoForwardDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}` : `${stepBtnStyle}`}
style={{ pointerEvents: 'none', color: 'white' }} onClick={() => {
stepIntoForward && stepIntoForward()
}}
data-id="buttonNavigatorIntoForward"
id="buttonNavigatorIntoFowardContainer"
>
<button
id="intoforward"
data-id="buttonNavigatorIntoForward"
className="btn btn-link btn-sm stepButton m-0 p-0"
onClick={() => {
stepIntoForward && stepIntoForward()
}}
disabled={state.intoForwardDisabled}
style={{pointerEvents: 'none', color: 'white'}}
> >
<span className="fas fa-level-down-alt"></span> <span className="fas fa-level-down-alt"></span>
</button> </button>
@ -92,24 +149,59 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
tagId: 'intoforwardTooltip', tagId: 'intoforwardTooltip',
tooltipMsg: 'Step into' tooltipMsg: 'Step into'
}, },
stepOverForwardJSX : { stepOverForwardJSX: {
markup: ( markup: (
<div className={state.overForwardDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { stepOverForward && stepOverForward() }} data-id="buttonNavigatorOverForward" id="buttonNavigatorOverForwardContainer"> <div
<button id='overforward' className='btn btn-link btn-sm stepButton m-0 p-0' onClick={() => { stepOverForward && stepOverForward() }} disabled={state.overForwardDisabled} style={{ pointerEvents: 'none', color: 'white' }}> className={state.overForwardDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}` : `${stepBtnStyle}`}
onClick={() => {
stepOverForward && stepOverForward()
}}
data-id="buttonNavigatorOverForward"
id="buttonNavigatorOverForwardContainer"
>
<button
id="overforward"
className="btn btn-link btn-sm stepButton m-0 p-0"
onClick={() => {
stepOverForward && stepOverForward()
}}
disabled={state.overForwardDisabled}
style={{pointerEvents: 'none', color: 'white'}}
>
<span className="fas fa-share"></span> <span className="fas fa-share"></span>
</button> </button>
</div> </div>
), ),
placement: 'top-end', placement: 'top-end',
tagId: 'overbackTooltip', tagId: 'overbackTooltip',
tooltipMsg: 'Step over forward', tooltipMsg: 'Step over forward'
} }
} }
const jumpMarkupStructure = { const jumpMarkupStructure = {
jumpPreviousBreakpointJSX : { jumpPreviousBreakpointJSX: {
markup: ( markup: (
<div className={state.jumpPreviousBreakpointDisabled ? `${stepBtnStyle} ${disableJumpBtnStyle}`: `${stepBtnStyle}`} id="buttonNavigatorJumpPreviousBreakpointContainer" onClick={() => { jumpPreviousBreakpoint && jumpPreviousBreakpoint() }} data-id="buttonNavigatorJumpPreviousBreakpoint"> <div
<button className='btn btn-link btn-sm jumpButton m-0 p-0' id='jumppreviousbreakpoint' data-id="buttonNavigatorJumpPreviousBreakpoint" onClick={() => { jumpPreviousBreakpoint && jumpPreviousBreakpoint() }} disabled={state.jumpPreviousBreakpointDisabled} style={{ pointerEvents: 'none', backgroundColor: 'inherit', color: 'white' }}> className={state.jumpPreviousBreakpointDisabled ? `${stepBtnStyle} ${disableJumpBtnStyle}` : `${stepBtnStyle}`}
id="buttonNavigatorJumpPreviousBreakpointContainer"
onClick={() => {
jumpPreviousBreakpoint && jumpPreviousBreakpoint()
}}
data-id="buttonNavigatorJumpPreviousBreakpoint"
>
<button
className="btn btn-link btn-sm jumpButton m-0 p-0"
id="jumppreviousbreakpoint"
data-id="buttonNavigatorJumpPreviousBreakpoint"
onClick={() => {
jumpPreviousBreakpoint && jumpPreviousBreakpoint()
}}
disabled={state.jumpPreviousBreakpointDisabled}
style={{
pointerEvents: 'none',
backgroundColor: 'inherit',
color: 'white'
}}
>
<span className="fas fa-step-backward"></span> <span className="fas fa-step-backward"></span>
</button> </button>
</div> </div>
@ -118,10 +210,30 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
tagId: 'jumppreviousbreakpointTooltip', tagId: 'jumppreviousbreakpointTooltip',
tooltipMsg: 'Jump to the previous breakpoint' tooltipMsg: 'Jump to the previous breakpoint'
}, },
jumpOutJSX : { jumpOutJSX: {
markup: ( markup: (
<div className={state.jumpOutDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { jumpOut && jumpOut() }} data-id="buttonNavigatorJumpOut" id="buttonNavigatorJumpOutContainer"> <div
<button className='btn btn-link btn-sm jumpButton m-0 p-0' id='jumpout' onClick={() => { jumpOut && jumpOut() }} disabled={state.jumpOutDisabled} style={{ pointerEvents: 'none', backgroundColor: 'inherit', color: 'white' }} data-id="buttonNavigatorJumpOut"> className={state.jumpOutDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}` : `${stepBtnStyle}`}
onClick={() => {
jumpOut && jumpOut()
}}
data-id="buttonNavigatorJumpOut"
id="buttonNavigatorJumpOutContainer"
>
<button
className="btn btn-link btn-sm jumpButton m-0 p-0"
id="jumpout"
onClick={() => {
jumpOut && jumpOut()
}}
disabled={state.jumpOutDisabled}
style={{
pointerEvents: 'none',
backgroundColor: 'inherit',
color: 'white'
}}
data-id="buttonNavigatorJumpOut"
>
<span className="fas fa-eject"></span> <span className="fas fa-eject"></span>
</button> </button>
</div> </div>
@ -130,10 +242,26 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
tagId: 'jumpoutTooltip', tagId: 'jumpoutTooltip',
tooltipMsg: 'Jump out' tooltipMsg: 'Jump out'
}, },
jumpNextBreakpointJSX : { jumpNextBreakpointJSX: {
markup: ( markup: (
<div className={state.jumpNextBreakpointDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { jumpNextBreakpoint && jumpNextBreakpoint() }} data-id="buttonNavigatorJumpNextBreakpoint" id="buttonNavigatorJumpNextBreakpointContainer"> <div
<button className='btn btn-link btn-sm jumpButton m-0 p-0' id='jumpnextbreakpoint' data-id="buttonNavigatorJumpNextBreakpoint" onClick={() => { jumpNextBreakpoint && jumpNextBreakpoint() }} disabled={state.jumpNextBreakpointDisabled} style={{ pointerEvents: 'none', color: 'white' }}> className={state.jumpNextBreakpointDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}` : `${stepBtnStyle}`}
onClick={() => {
jumpNextBreakpoint && jumpNextBreakpoint()
}}
data-id="buttonNavigatorJumpNextBreakpoint"
id="buttonNavigatorJumpNextBreakpointContainer"
>
<button
className="btn btn-link btn-sm jumpButton m-0 p-0"
id="jumpnextbreakpoint"
data-id="buttonNavigatorJumpNextBreakpoint"
onClick={() => {
jumpNextBreakpoint && jumpNextBreakpoint()
}}
disabled={state.jumpNextBreakpointDisabled}
style={{pointerEvents: 'none', color: 'white'}}
>
<span className="fas fa-step-forward"></span> <span className="fas fa-step-forward"></span>
</button> </button>
</div> </div>
@ -147,39 +275,58 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
return ( return (
<div className="buttons"> <div className="buttons">
<div className="stepButtons btn-group py-1"> <div className="stepButtons btn-group py-1">
{ {Object.keys(stepMarkupStructure).map((x) => (
Object.keys(stepMarkupStructure).map(x => ( <CustomTooltip
<CustomTooltip placement={stepMarkupStructure[x].placement}
placement={stepMarkupStructure[x].placement} tooltipId={stepMarkupStructure[x].tagId}
tooltipId={stepMarkupStructure[x].tagId} tooltipText={stepMarkupStructure[x].tooltipMsg}
tooltipText={stepMarkupStructure[x].tooltipMsg} key={`${stepMarkupStructure[x].placement}-${stepMarkupStructure[x].tooltipMsg}-${stepMarkupStructure[x].tagId}`}
key={`${stepMarkupStructure[x].placement}-${stepMarkupStructure[x].tooltipMsg}-${stepMarkupStructure[x].tagId}`} >
> {stepMarkupStructure[x].markup}
{stepMarkupStructure[x].markup} </CustomTooltip>
</CustomTooltip> ))}
))
}
</div> </div>
<div className="jumpButtons btn-group py-1"> <div className="jumpButtons btn-group py-1">
{ {Object.keys(jumpMarkupStructure).map((x) => (
Object.keys(jumpMarkupStructure).map(x => ( <CustomTooltip
<CustomTooltip placement={jumpMarkupStructure[x].placement}
placement={jumpMarkupStructure[x].placement} tooltipText={jumpMarkupStructure[x].tooltipMsg}
tooltipText={jumpMarkupStructure[x].tooltipMsg} tooltipId={jumpMarkupStructure[x].tooltipId}
tooltipId={jumpMarkupStructure[x].tooltipId} key={`${jumpMarkupStructure[x].placement}-${jumpMarkupStructure[x].tooltipMsg}-${jumpMarkupStructure[x].tagId}`}
key={`${jumpMarkupStructure[x].placement}-${jumpMarkupStructure[x].tooltipMsg}-${jumpMarkupStructure[x].tagId}`} >
> {jumpMarkupStructure[x].markup}
{jumpMarkupStructure[x].markup} </CustomTooltip>
</CustomTooltip> ))}
))
}
</div> </div>
<div id='reverted' style={{ display: revertedReason === '' ? 'none' : 'block' }}> <div id="reverted" style={{display: revertedReason === '' ? 'none' : 'block'}}>
<span className='text-warning'>This call has reverted, state changes made during the call will be reverted.</span> <span className="text-warning">This call has reverted, state changes made during the call will be reverted.</span>
<span className='text-warning' id='outofgas' style={{ display: revertedReason === 'outofgas' ? 'inline' : 'none' }}>This call will run out of gas.</span> <span className="text-warning" id="outofgas" style={{display: revertedReason === 'outofgas' ? 'inline' : 'none'}}>
<span className='text-warning' id='parenthasthrown' style={{ display: revertedReason === 'parenthasthrown' ? 'inline' : 'none' }}>The parent call will throw an exception</span> This call will run out of gas.
<div className='text-warning'>Click <u data-id="debugGoToRevert" className="cursorPointerRemixDebugger" role="button" onClick={() => { jumpToException && jumpToException() }}>here</u> to jump where the call reverted.</div> </span>
<span
className="text-warning"
id="parenthasthrown"
style={{
display: revertedReason === 'parenthasthrown' ? 'inline' : 'none'
}}
>
The parent call will throw an exception
</span>
<div className="text-warning">
Click{' '}
<u
data-id="debugGoToRevert"
className="cursorPointerRemixDebugger"
role="button"
onClick={() => {
jumpToException && jumpToException()
}}
>
here
</u>{' '}
to jump where the call reverted.
</div>
</div> </div>
</div> </div>
) )

@ -1,16 +1,16 @@
import React, { useState, useEffect, useRef } from 'react' // eslint-disable-line import React, {useState, useEffect, useRef} from 'react' // eslint-disable-line
import { FormattedMessage } from 'react-intl' import {FormattedMessage} from 'react-intl'
import TxBrowser from './tx-browser/tx-browser' // eslint-disable-line import TxBrowser from './tx-browser/tx-browser' // eslint-disable-line
import StepManager from './step-manager/step-manager' // eslint-disable-line import StepManager from './step-manager/step-manager' // eslint-disable-line
import VmDebugger from './vm-debugger/vm-debugger' // eslint-disable-line import VmDebugger from './vm-debugger/vm-debugger' // eslint-disable-line
import VmDebuggerHead from './vm-debugger/vm-debugger-head' // eslint-disable-line import VmDebuggerHead from './vm-debugger/vm-debugger-head' // eslint-disable-line
import { TransactionDebugger as Debugger } from '@remix-project/remix-debug' // eslint-disable-line import {TransactionDebugger as Debugger} from '@remix-project/remix-debug' // eslint-disable-line
import { DebuggerUIProps } from './idebugger-api' // eslint-disable-line import {DebuggerUIProps} from './idebugger-api' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line import {Toaster} from '@remix-ui/toaster' // eslint-disable-line
import { CustomTooltip, isValidHash } from '@remix-ui/helper' import {CustomTooltip, isValidHash} from '@remix-ui/helper'
/* eslint-disable-next-line */ /* eslint-disable-next-line */
import './debugger-ui.css' import './debugger-ui.css'
const _paq = (window as any)._paq = (window as any)._paq || [] const _paq = ((window as any)._paq = (window as any)._paq || [])
export const DebuggerUI = (props: DebuggerUIProps) => { export const DebuggerUI = (props: DebuggerUIProps) => {
const debuggerModule = props.debuggerAPI const debuggerModule = props.debuggerAPI
@ -54,7 +54,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const handleResize = () => { const handleResize = () => {
if (panelsRef.current && debuggerTopRef.current) { if (panelsRef.current && debuggerTopRef.current) {
panelsRef.current.style.height = (window.innerHeight - debuggerTopRef.current.clientHeight) - debuggerTopRef.current.offsetTop - 7 +'px' panelsRef.current.style.height = window.innerHeight - debuggerTopRef.current.clientHeight - debuggerTopRef.current.offsetTop - 7 + 'px'
} }
} }
@ -65,8 +65,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
useEffect(() => { useEffect(() => {
window.addEventListener('resize', handleResize) window.addEventListener('resize', handleResize)
// TODO: not a good way to wait on the ref doms element to be rendered of course // TODO: not a good way to wait on the ref doms element to be rendered of course
setTimeout(() => setTimeout(() => handleResize(), 2000)
handleResize(), 2000)
return () => window.removeEventListener('resize', handleResize) return () => window.removeEventListener('resize', handleResize)
}, [state.debugging, state.isActive]) }, [state.debugging, state.isActive])
@ -85,11 +84,15 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
useEffect(() => { useEffect(() => {
const setEditor = () => { const setEditor = () => {
debuggerModule.onBreakpointCleared((fileName, row) => { debuggerModule.onBreakpointCleared((fileName, row) => {
if (state.debugger) state.debugger.breakPointManager.remove({ fileName: fileName, row: row }) if (state.debugger)
state.debugger.breakPointManager.remove({
fileName: fileName,
row: row
})
}) })
debuggerModule.onBreakpointAdded((fileName, row) => { debuggerModule.onBreakpointAdded((fileName, row) => {
if (state.debugger) state.debugger.breakPointManager.add({ fileName: fileName, row: row }) if (state.debugger) state.debugger.breakPointManager.add({fileName: fileName, row: row})
}) })
debuggerModule.onEditorContentChanged(() => { debuggerModule.onEditorContentChanged(() => {
@ -101,9 +104,9 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const providerChanged = () => { const providerChanged = () => {
debuggerModule.onEnvChanged((provider) => { debuggerModule.onEnvChanged((provider) => {
setState(prevState => { setState((prevState) => {
const isLocalNodeUsed = !provider.startsWith('vm') && provider !== 'injected' const isLocalNodeUsed = !provider.startsWith('vm') && provider !== 'injected'
return { ...prevState, isLocalNodeUsed: isLocalNodeUsed } return {...prevState, isLocalNodeUsed: isLocalNodeUsed}
}) })
}) })
} }
@ -116,34 +119,38 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
debuggerInstance.event.register('debuggerStatus', async (isActive) => { debuggerInstance.event.register('debuggerStatus', async (isActive) => {
await debuggerModule.discardHighlight() await debuggerModule.discardHighlight()
setState(prevState => { setState((prevState) => {
return { ...prevState, isActive } return {...prevState, isActive}
}) })
}) })
debuggerInstance.event.register('locatingBreakpoint', async (isActive) => { debuggerInstance.event.register('locatingBreakpoint', async (isActive) => {
setState(prevState => { setState((prevState) => {
return { ...prevState, sourceLocationStatus: 'Locating breakpoint, this might take a while...' } return {
...prevState,
sourceLocationStatus: 'Locating breakpoint, this might take a while...'
}
}) })
}) })
debuggerInstance.event.register('noBreakpointHit', async (isActive) => { debuggerInstance.event.register('noBreakpointHit', async (isActive) => {
setState(prevState => { setState((prevState) => {
return { ...prevState, sourceLocationStatus: '' } return {...prevState, sourceLocationStatus: ''}
}) })
}) })
debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources, address, stepDetail, lineGasCost) => { debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources, address, stepDetail, lineGasCost) => {
if (!lineColumnPos) { if (!lineColumnPos) {
await debuggerModule.discardHighlight() await debuggerModule.discardHighlight()
setState(prevState => { setState((prevState) => {
return { ...prevState, sourceLocationStatus: 'Source location not available, neither in Sourcify nor in Etherscan. Please make sure the Etherscan api key is provided in the settings.' } return {
...prevState,
sourceLocationStatus: 'Source location not available, neither in Sourcify nor in Etherscan. Please make sure the Etherscan api key is provided in the settings.'
}
}) })
return return
} }
const contracts = await debuggerModule.fetchContractAndCompile( const contracts = await debuggerModule.fetchContractAndCompile(address || currentReceipt.contractAddress || currentReceipt.to, currentReceipt)
address || currentReceipt.contractAddress || currentReceipt.to,
currentReceipt)
if (contracts) { if (contracts) {
let path = contracts.getSourceName(rawLocation.file) let path = contracts.getSourceName(rawLocation.file)
if (!path) { if (!path) {
@ -155,7 +162,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
try { try {
content = await debuggerModule.getFile(path) content = await debuggerModule.getFile(path)
} catch (e) { } catch (e) {
const message = 'Unable to fetch generated sources, the file probably doesn\'t exist yet.' const message = "Unable to fetch generated sources, the file probably doesn't exist yet."
console.log(message, ' ', e) console.log(message, ' ', e)
} }
if (content !== source.contents) { if (content !== source.contents) {
@ -166,8 +173,8 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
} }
} }
if (path) { if (path) {
setState(prevState => { setState((prevState) => {
return { ...prevState, sourceLocationStatus: '' } return {...prevState, sourceLocationStatus: ''}
}) })
await debuggerModule.discardHighlight() await debuggerModule.discardHighlight()
await debuggerModule.highlight(lineColumnPos, path, rawLocation, stepDetail, lineGasCost) await debuggerModule.highlight(lineColumnPos, path, rawLocation, stepDetail, lineGasCost)
@ -183,7 +190,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
} }
const updateTxNumberFlag = (empty: boolean) => { const updateTxNumberFlag = (empty: boolean) => {
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
txNumberIsEmpty: empty, txNumberIsEmpty: empty,
@ -194,7 +201,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const unloadRequested = (blockNumber, txIndex, tx) => { const unloadRequested = (blockNumber, txIndex, tx) => {
unLoad() unLoad()
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
sourceLocationStatus: '' sourceLocationStatus: ''
@ -205,7 +212,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const unLoad = () => { const unLoad = () => {
debuggerModule.onStopDebugging() debuggerModule.onStopDebugging()
if (state.debugger) state.debugger.unload() if (state.debugger) state.debugger.unload()
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
isActive: false, isActive: false,
@ -228,10 +235,10 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const startDebugging = async (blockNumber, txNumber, tx, optWeb3?) => { const startDebugging = async (blockNumber, txNumber, tx, optWeb3?) => {
if (state.debugger) { if (state.debugger) {
unLoad() unLoad()
await (new Promise((resolve) => setTimeout(() => resolve({}), 1000))) await new Promise((resolve) => setTimeout(() => resolve({}), 1000))
} }
if (!txNumber) return if (!txNumber) return
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
txNumber: txNumber, txNumber: txNumber,
@ -239,7 +246,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
} }
}) })
if (!isValidHash(txNumber)) { if (!isValidHash(txNumber)) {
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
validationError: 'Invalid transaction hash.' validationError: 'Invalid transaction hash.'
@ -253,7 +260,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const networkId = await web3.eth.net.getId() const networkId = await web3.eth.net.getId()
_paq.push(['trackEvent', 'debugger', 'startDebugging', networkId]) _paq.push(['trackEvent', 'debugger', 'startDebugging', networkId])
if (networkId === 42) { if (networkId === 42) {
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
validationError: 'Unfortunately, the Kovan network is not supported.' validationError: 'Unfortunately, the Kovan network is not supported.'
@ -272,7 +279,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
currentBlock = await web3.eth.getBlock(currentReceipt.blockHash) currentBlock = await web3.eth.getBlock(currentReceipt.blockHash)
currentTransaction = await web3.eth.getTransaction(txNumber) currentTransaction = await web3.eth.getTransaction(txNumber)
} catch (e) { } catch (e) {
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
validationError: e.message validationError: e.message
@ -298,12 +305,12 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
debugWithGeneratedSources: state.opt.debugWithGeneratedSources debugWithGeneratedSources: state.opt.debugWithGeneratedSources
}) })
setTimeout(async() => { setTimeout(async () => {
debuggerModule.onStartDebugging(debuggerInstance) debuggerModule.onStartDebugging(debuggerInstance)
try { try {
await debuggerInstance.debug(blockNumber, txNumber, tx, () => { await debuggerInstance.debug(blockNumber, txNumber, tx, () => {
listenToEvents(debuggerInstance, currentReceipt) listenToEvents(debuggerInstance, currentReceipt)
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
blockNumber, blockNumber,
@ -320,7 +327,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}) })
} catch (error) { } catch (error) {
unLoad() unLoad()
setState(prevState => { setState((prevState) => {
let errorMsg = error.message || error let errorMsg = error.message || error
if (typeof errorMsg !== 'string') { if (typeof errorMsg !== 'string') {
errorMsg = JSON.stringify(errorMsg) + '. Possible error: the current endpoint does not support retrieving the trace of a transaction.' errorMsg = JSON.stringify(errorMsg) + '. Possible error: the current endpoint does not support retrieving the trace of a transaction.'
@ -338,7 +345,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
} }
const debug = (txHash, web3?) => { const debug = (txHash, web3?) => {
setState(prevState => { setState((prevState) => {
return { return {
...prevState, ...prevState,
validationError: '', validationError: '',
@ -370,16 +377,22 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
const customJSX = ( const customJSX = (
<span className="p-0 m-0"> <span className="p-0 m-0">
<input className="custom-control-input" id="debugGeneratedSourcesInput" onChange={({ target: { checked } }) => { <input
setState(prevState => { className="custom-control-input"
return { ...prevState, opt: { ...prevState.opt, debugWithGeneratedSources: checked } } id="debugGeneratedSourcesInput"
}) onChange={({target: {checked}}) => {
}} type="checkbox" /> setState((prevState) => {
<label return {
data-id="debugGeneratedSourcesLabel" ...prevState,
className="form-check-label custom-control-label" opt: {...prevState.opt, debugWithGeneratedSources: checked}
htmlFor="debugGeneratedSourcesInput"> }
<FormattedMessage id='debugger.useGeneratedSources' />(Solidity {'>='} v0.7.2) })
}}
type="checkbox"
/>
<label data-id="debugGeneratedSourcesLabel" className="form-check-label custom-control-label" htmlFor="debugGeneratedSourcesInput">
<FormattedMessage id="debugger.useGeneratedSources" />
(Solidity {'>='} v0.7.2)
</label> </label>
</span> </span>
) )
@ -389,50 +402,74 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
<div className="px-2" ref={debuggerTopRef}> <div className="px-2" ref={debuggerTopRef}>
<div> <div>
<div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox"> <div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox">
<CustomTooltip <CustomTooltip tooltipId="debuggerGenSourceCheckbox" tooltipText={<FormattedMessage id="debugger.debugWithGeneratedSources" />} placement="bottom-start">
tooltipId="debuggerGenSourceCheckbox"
tooltipText={<FormattedMessage id='debugger.debugWithGeneratedSources' />}
placement="bottom-start"
>
{customJSX} {customJSX}
</CustomTooltip> </CustomTooltip>
</div> </div>
{ state.isLocalNodeUsed && <div className="mb-2 debuggerConfig custom-control custom-checkbox"> {state.isLocalNodeUsed && (
<CustomTooltip <div className="mb-2 debuggerConfig custom-control custom-checkbox">
tooltipId="debuggerGenSourceInput" <CustomTooltip tooltipId="debuggerGenSourceInput" tooltipText="Force the debugger to use the current local node" placement="right">
tooltipText="Force the debugger to use the current local node" <input
placement='right' className="custom-control-input"
> id="debugWithLocalNodeInput"
<input onChange={({target: {checked}}) => {
className="custom-control-input" setState((prevState) => {
id="debugWithLocalNodeInput" return {
onChange={({ target: { checked } }) => { ...prevState,
setState(prevState => { opt: {...prevState.opt, debugWithLocalNode: checked}
return { ...prevState, opt: { ...prevState.opt, debugWithLocalNode: checked } } }
}) })
}} }}
type="checkbox" type="checkbox"
/> />
</CustomTooltip> </CustomTooltip>
<label data-id="debugLocaNodeLabel" className="form-check-label custom-control-label" htmlFor="debugWithLocalNodeInput"><FormattedMessage id='debugger.debugLocaNodeLabel' /></label> <label data-id="debugLocaNodeLabel" className="form-check-label custom-control-label" htmlFor="debugWithLocalNodeInput">
</div> <FormattedMessage id="debugger.debugLocaNodeLabel" />
} </label>
{ state.validationError && <span className="w-100 py-1 text-danger validationError">{state.validationError}</span> } </div>
)}
{state.validationError && <span className="w-100 py-1 text-danger validationError">{state.validationError}</span>}
</div> </div>
<TxBrowser requestDebug={ requestDebug } unloadRequested={ unloadRequested } updateTxNumberFlag={ updateTxNumberFlag } transactionNumber={ state.txNumber } debugging={ state.debugging } /> <TxBrowser
{ state.debugging && state.sourceLocationStatus && <div className="text-warning"><i className="fas fa-exclamation-triangle" aria-hidden="true"></i> {state.sourceLocationStatus}</div> } requestDebug={requestDebug}
{ !state.debugging && unloadRequested={unloadRequested}
<div> updateTxNumberFlag={updateTxNumberFlag}
<i className="fas fa-info-triangle" aria-hidden="true"></i> transactionNumber={state.txNumber}
<span> debugging={state.debugging}
<FormattedMessage id='debugger.introduction' />: <a href="https://docs.sourcify.dev/docs/chains/" target="__blank" >Sourcify docs</a> & <a href="https://etherscan.io/contractsVerified" target="__blank">https://etherscan.io/contractsVerified</a> />
</span> {state.debugging && state.sourceLocationStatus && (
</div> } <div className="text-warning">
{ state.debugging && <StepManager stepManager={ stepManager } /> } <i className="fas fa-exclamation-triangle" aria-hidden="true"></i> {state.sourceLocationStatus}
</div>
)}
{!state.debugging && (
<div>
<i className="fas fa-info-triangle" aria-hidden="true"></i>
<span>
<FormattedMessage id="debugger.introduction" />:{' '}
<a href="https://docs.sourcify.dev/docs/chains/" target="__blank">
Sourcify docs
</a>{' '}
&{' '}
<a href="https://etherscan.io/contractsVerified" target="__blank">
https://etherscan.io/contractsVerified
</a>
</span>
</div>
)}
{state.debugging && <StepManager stepManager={stepManager} />}
</div> </div>
<div className="debuggerPanels" ref={panelsRef}> <div className="debuggerPanels" ref={panelsRef}>
{ state.debugging && <VmDebuggerHead debugging={state.debugging} vmDebugger={ vmDebugger } /> } {state.debugging && <VmDebuggerHead debugging={state.debugging} vmDebugger={vmDebugger} />}
{ state.debugging && <VmDebugger debugging={state.debugging} vmDebugger={ vmDebugger } currentBlock={ state.currentBlock } currentReceipt={ state.currentReceipt } currentTransaction={ state.currentTransaction } /> } {state.debugging && (
<VmDebugger
debugging={state.debugging}
vmDebugger={vmDebugger}
currentBlock={state.currentBlock}
currentReceipt={state.currentReceipt}
currentTransaction={state.currentTransaction}
/>
)}
</div> </div>
</div> </div>
) )

@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from 'react' // eslint-disable-line import React, {useState, useEffect, useRef} from 'react' // eslint-disable-line
export const Slider = ({ jumpTo, sliderValue, traceLength }) => { export const Slider = ({jumpTo, sliderValue, traceLength}) => {
const onChangeId = useRef(null) const onChangeId = useRef(null)
const slider = useRef(null) const slider = useRef(null)
@ -9,13 +9,13 @@ export const Slider = ({ jumpTo, sliderValue, traceLength }) => {
}, [sliderValue]) }, [sliderValue])
const setValue = (value) => { const setValue = (value) => {
if (value < 0) return if (value < 0) return
if (value === slider.current.value) return if (value === slider.current.value) return
slider.current.value = value slider.current.value = value
if (onChangeId.current) { if (onChangeId.current) {
clearTimeout(onChangeId.current) clearTimeout(onChangeId.current)
} }
((value) => { ;((value) => {
onChangeId.current = setTimeout(() => { onChangeId.current = setTimeout(() => {
jumpTo && jumpTo(value) jumpTo && jumpTo(value)
}, 100) }, 100)
@ -30,11 +30,12 @@ export const Slider = ({ jumpTo, sliderValue, traceLength }) => {
return ( return (
<div> <div>
<input id='slider' <input
id="slider"
data-id="slider" data-id="slider"
className='w-100 my-0' className="w-100 my-0"
ref={slider} ref={slider}
type='range' type="range"
min={0} min={0}
max={traceLength ? traceLength - 1 : 0} max={traceLength ? traceLength - 1 : 0}
onMouseUp={handleChange} onMouseUp={handleChange}

@ -1,8 +1,22 @@
import React, { useState, useEffect } from 'react' // eslint-disable-line import React, {useState, useEffect} from 'react' // eslint-disable-line
import Slider from '../slider/slider' // eslint-disable-line import Slider from '../slider/slider' // eslint-disable-line
import ButtonNavigator from '../button-navigator/button-navigator' // eslint-disable-line import ButtonNavigator from '../button-navigator/button-navigator' // eslint-disable-line
export const StepManager = ({ stepManager: { jumpTo, traceLength, stepIntoBack, stepIntoForward, stepOverBack, stepOverForward, jumpOut, jumpNextBreakpoint, jumpPreviousBreakpoint, jumpToException, registerEvent } }) => { export const StepManager = ({
stepManager: {
jumpTo,
traceLength,
stepIntoBack,
stepIntoForward,
stepOverBack,
stepOverForward,
jumpOut,
jumpNextBreakpoint,
jumpPreviousBreakpoint,
jumpToException,
registerEvent
}
}) => {
const [state, setState] = useState({ const [state, setState] = useState({
sliderValue: -1, sliderValue: -1,
revertWarning: '', revertWarning: '',
@ -16,17 +30,17 @@ export const StepManager = ({ stepManager: { jumpTo, traceLength, stepIntoBack,
}, [registerEvent]) }, [registerEvent])
const setRevertWarning = (warning) => { const setRevertWarning = (warning) => {
setState(prevState => { setState((prevState) => {
return { ...prevState, revertWarning: warning } return {...prevState, revertWarning: warning}
}) })
} }
const updateStep = (step, stepState, jumpOutDisabled) => { const updateStep = (step, stepState, jumpOutDisabled) => {
setState(prevState => { setState((prevState) => {
return { ...prevState, sliderValue: step, stepState, jumpOutDisabled } return {...prevState, sliderValue: step, stepState, jumpOutDisabled}
}) })
} }
const { sliderValue, revertWarning, stepState, jumpOutDisabled } = state const {sliderValue, revertWarning, stepState, jumpOutDisabled} = state
return ( return (
<div className="py-1"> <div className="py-1">

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save