Merge branch 'master' into installSlitherCmd

pull/2450/head
Aniket 3 years ago committed by GitHub
commit 4b1ce9a116
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .github/workflows/publish-action.yml
  2. 30
      apps/remix-ide-e2e/src/commands/verifyContracts.ts
  3. 38
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  4. 1
      apps/remix-ide-e2e/src/tests/compiler_api.test.ts
  5. 6
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  6. 15
      apps/remix-ide-e2e/src/tests/publishContract.test.ts
  7. 4
      apps/remix-ide-e2e/src/tests/search.test.ts
  8. 8
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  9. 15
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  10. 2
      apps/remix-ide-e2e/src/types/index.d.ts
  11. 10
      apps/remix-ide/ci/publishIpfs
  12. 59
      apps/remix-ide/src/app/editor/editor.js
  13. 4
      apps/remix-ide/src/app/files/fileManager.ts
  14. 3
      apps/remix-ide/src/app/panels/layout.ts
  15. 7
      apps/remix-ide/src/app/panels/tab-proxy.js
  16. 25
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  17. 4
      apps/remix-ide/src/app/tabs/compile-tab.js
  18. 68
      apps/solidity-compiler/src/app/compiler-api.ts
  19. 8
      libs/remix-analyzer/package.json
  20. 6
      libs/remix-astwalker/package.json
  21. 10
      libs/remix-debug/package.json
  22. 4
      libs/remix-lib/package.json
  23. 1
      libs/remix-lib/src/types/ICompilerApi.ts
  24. 6
      libs/remix-simulator/package.json
  25. 2
      libs/remix-simulator/src/methods/blocks.ts
  26. 6
      libs/remix-solidity/package.json
  27. 10
      libs/remix-tests/package.json
  28. 2
      libs/remix-ui/editor/src/lib/remix-plugin-types.ts
  29. 4
      libs/remix-ui/editor/src/lib/web-types.ts
  30. 6
      libs/remix-ui/helper/src/lib/helper-components.tsx
  31. 12
      libs/remix-ui/home-tab/src/lib/components/rssFeed.css
  32. 42
      libs/remix-ui/home-tab/src/lib/components/rssFeed.tsx
  33. 19
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx
  34. 73
      libs/remix-ui/publish-to-storage/src/lib/publish-to-storage.tsx
  35. 9
      libs/remix-ui/publish-to-storage/src/lib/publishOnSwarm.tsx
  36. 44
      libs/remix-ui/publish-to-storage/src/lib/publishToIPFS.tsx
  37. 2
      libs/remix-ui/run-tab/src/lib/components/environment.tsx
  38. 4
      libs/remix-ui/search/src/lib/context/context.tsx
  39. 1
      libs/remix-ui/settings/src/lib/constants.ts
  40. 111
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  41. 9
      libs/remix-ui/settings/src/lib/settingsAction.ts
  42. 128
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  43. 10
      libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts
  44. 17
      libs/remix-ui/solidity-compiler/src/lib/solidity-compiler.tsx
  45. 4
      libs/remix-ui/solidity-compiler/src/lib/types/index.ts
  46. 1
      libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css
  47. 4
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  48. 4
      libs/remix-url-resolver/package.json
  49. 50
      libs/remix-ws-templates/package.json
  50. 23
      libs/remix-ws-templates/src/templates/ozerc20/scripts/ethers-lib.ts
  51. 19
      libs/remix-ws-templates/src/templates/ozerc20/scripts/web3-lib.ts
  52. 23
      libs/remix-ws-templates/src/templates/ozerc721/scripts/ethers-lib.ts
  53. 19
      libs/remix-ws-templates/src/templates/ozerc721/scripts/web3-lib.ts
  54. 21
      libs/remix-ws-templates/src/templates/remixDefault/scripts/ethers-lib.ts
  55. 21
      libs/remix-ws-templates/src/templates/remixDefault/scripts/web3-lib.ts
  56. 23
      libs/remix-ws-templates/src/templates/zeroxErc20/scripts/ethers-lib.ts
  57. 19
      libs/remix-ws-templates/src/templates/zeroxErc20/scripts/web3-lib.ts
  58. 2
      libs/remixd/package.json
  59. 5
      package.json

@ -9,18 +9,20 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 14.17.6
- run: yarn install
- run: ls
- run: pwd
- run: yarn run downloadsolc_assets
- run: yarn run build:production
- run: echo "action_state=$('./apps/remix-ide/ci/publishIpfs')" >> $GITHUB_ENV
- run: echo "action_state=$('./apps/remix-ide/ci/publishIpfs' ${{ secrets.IPFS_PROJET_ID }} ${{ secrets.IPFS_PROJECT_SECRET }})" >> $GITHUB_ENV
- uses: mshick/add-pr-comment@v1
with:
message: |
ipfs://${{ env.action_state }}
https://ipfs.remixproject.org/ipfs/${{ env.action_state }}
https://gateway.ipfs.io/ipfs/${{ env.action_state }}
https://remix-project.infura-ipfs.io/ipfs/${{ env.action_state }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
repo-token-user-login: 'github-actions[bot]' # The user.login for temporary GitHub tokens
allow-repeats: false # This is the default

@ -2,7 +2,7 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class VerifyContracts extends EventEmitter {
command (this: NightwatchBrowser, compiledContractNames: string[], opts = { wait: 1000, version: null }): NightwatchBrowser {
command (this: NightwatchBrowser, compiledContractNames: string[], opts = { wait: 1000, version: null, runs: '200' }): NightwatchBrowser {
this.api.perform((done) => {
verifyContracts(this.api, compiledContractNames, opts, () => {
done()
@ -13,13 +13,13 @@ class VerifyContracts extends EventEmitter {
}
}
function verifyContracts (browser: NightwatchBrowser, compiledContractNames: string[], opts: { wait: number, version?: string }, callback: VoidFunction) {
function verifyContracts (browser: NightwatchBrowser, compiledContractNames: string[], opts: { wait: number, version?: string, runs?: string }, callback: VoidFunction) {
browser
.clickLaunchIcon('solidity')
.pause(opts.wait)
.pause(5000)
.waitForElementPresent('*[data-id="compiledContracts"] option', 60000)
.perform((done) => {
.perform(async (done) => {
if (opts.version) {
browser
.click('*[data-id="compilation-details"]')
@ -36,10 +36,28 @@ function verifyContracts (browser: NightwatchBrowser, compiledContractNames: str
done()
callback()
})
} else {
compiledContractNames.forEach((name) => {
browser.waitForElementContainsText('[data-id="compiledContracts"]', name, 60000)
} if (opts.runs) {
browser
.click('*[data-id="compilation-details"]')
.waitForElementVisible('*[data-id="remixui_treeviewitem_metadata"]')
.pause(2000)
.click('*[data-id="remixui_treeviewitem_metadata"]')
.waitForElementVisible('*[data-id="treeViewDivtreeViewItemsettings"]')
.pause(2000)
.click('*[data-id="treeViewDivtreeViewItemsettings"]')
.waitForElementVisible('*[data-id="treeViewDivtreeViewItemoptimizer"]')
.click('*[data-id="treeViewDivtreeViewItemoptimizer"]')
.waitForElementVisible('*[data-id="treeViewDivruns"]')
.assert.containsText('*[data-id="treeViewDivruns"]', `${opts.runs}`)
.click('[data-id="workspacesModalDialog-modal-footer-ok-react"]')
.perform(() => {
done()
callback()
})
} else {
for (const index in compiledContractNames) {
await browser.waitForElementContainsText('[data-id="compiledContracts"]', compiledContractNames[index], 60000)
}
done()
callback()
}

@ -122,6 +122,24 @@ module.exports = {
})
// Test in Udapp UI , treeViewDiv0 shows returned value on method click
.assert.containsText('*[data-id="treeViewDiv0"]', 'bytes32: winnerName_ 0x48656c6c6f20576f726c64210000000000000000000000000000000000000000')
},
'Compile Ballot using config file': function (browser: NightwatchBrowser) {
browser
.addFile('cf.json', {content: configFile})
.clickLaunchIcon('solidity')
.waitForElementVisible('*[data-id="scConfigExpander"]')
.click('*[data-id="scConfigExpander"]')
.waitForElementVisible('*[data-id="scFileConfiguration"]', 10000)
.click('*[data-id="scFileConfiguration"]')
.waitForElementVisible('*[data-id="scConfigChangeFilePath"]', 10000)
.click('*[data-id="scConfigChangeFilePath"]')
.waitForElementVisible('*[data-id="scConfigFilePathInput"]', 10000)
.clearValue('*[data-id="scConfigFilePathInput"]')
.setValue('*[data-id="scConfigFilePathInput"]', 'cf.json')
.sendKeys('*[data-id$="scConfigFilePathInput"]', browser.Keys.ENTER)
.openFile('Untitled.sol')
.verifyContracts(['Ballot'], {wait: 2000, runs: '300'})
.end()
}
}
@ -190,6 +208,7 @@ const stateCheck = {
immutable: false
}
}
const ballotABI = `[
{
"inputs": [
@ -356,3 +375,22 @@ const ballotABI = `[
"type": "function"
}
]`
const configFile = `
{
"language": "Solidity",
"settings": {
"optimizer": {
"enabled": true,
"runs": 300
},
"outputSelection": {
"*": {
"": ["ast"],
"*": ["abi", "metadata", "devdoc", "userdoc", "storageLayout", "evm.legacyAssembly", "evm.bytecode", "evm.deployedBytecode", "evm.methodIdentifiers", "evm.gasEstimates", "evm.assembly"]
}
},
"evmVersion": "byzantium"
}
}
`

@ -168,3 +168,4 @@ contract DoesNotCompile {
function fStackLimit(uint u1, uint u2, uint u3, uint u4, uint u5, uint u6, uint u7, uint u8, uint u9, uint u10, uint u11, uint u12) public {
}
}`

@ -231,7 +231,6 @@ module.exports = {
'Should get current files #group7': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:readdir', {
'compiler_config.json': { isDirectory: false },
contracts: { isDirectory: true },
scripts: { isDirectory: true },
tests: { isDirectory: true },
@ -286,15 +285,12 @@ module.exports = {
'Should create empty workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, ['emptyworkspace', true])
await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'emptyworkspace', isLocalhost: false, absolutePath: '.workspaces/emptyworkspace' }, null, null)
await clickAndCheckLog(browser, 'fileManager:readdir', {
'compiler_config.json': { isDirectory: false }
}, null, '/')
await clickAndCheckLog(browser, 'fileManager:readdir', {}, null, '/')
},
'Should create workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'testspace')
await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'testspace', isLocalhost: false, absolutePath: '.workspaces/testspace' }, null, null)
await clickAndCheckLog(browser, 'fileManager:readdir', {
'compiler_config.json': { isDirectory: false },
contracts: { isDirectory: true },
scripts: { isDirectory: true },
tests: { isDirectory: true },

@ -19,17 +19,22 @@ module.exports = {
.openFile('contracts/3_Ballot.sol')
.verifyContracts(['Ballot'])
.click('#publishOnIpfs')
.pause(2000)
.waitForElementVisible('[data-id="publishToStorageModalDialogModalBody-react"]', 60000)
.click('[data-id="publishToStorage-modal-footer-ok-react"]')
.pause(8000)
.waitForElementVisible('[data-id="publishToStorageModalDialogModalBody-react"]', 60000)
.getText('[data-id="publishToStorageModalDialogModalBody-react"]', (result) => {
const value = <string>(result.value)
browser.perform((done) => {
if (value.indexOf('Metadata of "ballot" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
if (value.indexOf('Metadata and sources of "ballot" were published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
done()
})
})
.click('[data-id="publishToStorage-modal-footer-ok-react"]')
.openFile('ipfs/QmSUodhSvoorFL5m5CNqve8YvmuBpjCq17NbTf4GUX8ydw')
.openFile('ipfs/QmXYUS1ueS22EqNVRaKuZa31EgHLjKZ8uTM8vWhQLxa3pw')
},
/* Disableing the test untill refactoring and the new swarm usage
@ -41,7 +46,7 @@ module.exports = {
const value = <string>(result.value)
browser.perform((done) => {
if (value.indexOf('Metadata of "ballot" was published successfully.') === -1) browser.assert.fail('swarm deploy failed')
if (value.indexOf('Metadata and sources of "ballot" were published successfully.') === -1) browser.assert.fail('swarm deploy failed')
if (value.indexOf('bzz') === -1) browser.assert.fail('swarm deploy failed')
done()
})
@ -61,11 +66,13 @@ module.exports = {
.waitForElementVisible('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000)
.waitForElementVisible('[data-id="udappModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="udappModalDialogModalBody-react"]', 60000)
.modalFooterOKClick('udapp')
.pause(8000)
.getText('[data-id="udappModalDialogModalBody-react"]', (result) => {
const value = typeof result.value === 'string' ? result.value : null
if (value.indexOf('Metadata of "storage" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
if (value.indexOf('Metadata and sources of "storage" were published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
})
.modalFooterOKClick('udapp')
},

@ -33,11 +33,11 @@ module.exports = {
.clearValue('*[id="search_include"]').pause(2000)
.setValue('*[id="search_include"]', '**').sendKeys('*[id="search_include"]', browser.Keys.ENTER).pause(4000)
.elements('css selector', '.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 48)
Array.isArray(res.value) && browser.assert.equal(res.value.length, 61)
})
.setValue('*[id="search_exclude"]', ',contracts/**').sendKeys('*[id="search_exclude"]', browser.Keys.ENTER).pause(4000)
.elements('css selector', '.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 42)
Array.isArray(res.value) && browser.assert.equal(res.value.length, 55)
})
.clearValue('*[id="search_include"]').setValue('*[id="search_include"]', '*.sol, *.js, *.txt')
.clearValue('*[id="search_exclude"]').setValue('*[id="search_exclude"]', '.*/**/*')

@ -291,7 +291,7 @@ const deployWithEthersJs = `
// 'web3Provider' is a remix global variable object
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
let factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer);
let factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
let contract = await factory.deploy(...constructorArgs);
@ -320,7 +320,7 @@ describe("Storage with lib", function () {
// Make sure contract is compiled and artifacts are generated
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json'))
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer);
let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
let storage = await Storage.deploy();
console.log('storage contract Address: ' + storage.address);
await storage.deployed()
@ -330,7 +330,7 @@ describe("Storage with lib", function () {
it("test updating and retrieving updated value", async function () {
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json'))
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer);
let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
let storage = await Storage.deploy();
await storage.deployed()
const setValue = await storage.store(56);
@ -341,7 +341,7 @@ describe("Storage with lib", function () {
it("fail test updating and retrieving updated value", async function () {
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', 'contracts/artifacts/Storage.json'))
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer);
let Storage = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
let storage = await Storage.deploy();
await storage.deployed()
const setValue = await storage.store(56);

@ -68,14 +68,14 @@ module.exports = {
.click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.pause(2000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<any> => {`) !== -1,
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<any> => { `) !== -1,
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]')
@ -107,8 +107,7 @@ module.exports = {
const fileList = document.querySelector('*[data-id="treeViewUltreeViewMenu"]')
return fileList.getElementsByTagName('li').length;
}, [], function(result){
// check there are no files in FE except config file
browser.assert.equal(result.value, 1, 'Incorrect number of files');
browser.assert.equal(result.value, 0, 'Incorrect number of files');
});
},
@ -146,14 +145,14 @@ module.exports = {
.click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<any> => {`) !== -1,
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<any> => { `) !== -1,
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]')
@ -194,14 +193,14 @@ module.exports = {
.click('*[data-id="treeViewLitreeViewItemscripts/web3-lib.ts"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<any> => {`) !== -1,
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]')
.pause(100)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<any> => { `) !== -1,
browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {`) !== -1,
'Incorrect content')
})
.assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]')

@ -11,7 +11,7 @@ declare module 'nightwatch' {
testContracts(fileName: string, contractCode: NightwatchContractContent, compiledContractNames: string[]): NightwatchBrowser,
setEditorValue(value: string, callback?: () => void): NightwatchBrowser,
addFile(name: string, content: NightwatchContractContent): NightwatchBrowser,
verifyContracts(compiledContractNames: string[], opts?: { wait: number, version?: string }): NightwatchBrowser,
verifyContracts(compiledContractNames: string[], opts?: { wait: number, version?: string, runs?: string }): NightwatchBrowser,
selectAccount(account?: string): NightwatchBrowser,
clickFunction(fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser,
testFunction(txHash: string, expectedInput: NightwatchTestFunctionExpectedInput): NightwatchBrowser,

@ -5,8 +5,14 @@ const { globSource } = IpfsHttpClient
const folder = process.cwd() + '/dist/apps/remix-ide';
(async () => {
const host = 'ipfs.remixproject.org'
const ipfs = IpfsHttpClient({ host, port: 443, protocol: 'https' })
const host = 'ipfs.infura.io'
const projectId = process.argv[2]
const projectSecret = process.argv[3]
const auth = 'Basic ' + Buffer.from(projectId + ':' + projectSecret).toString('base64')
const ipfs = IpfsHttpClient({ port: 5001, host, protocol: 'https', headers: {
authorization: auth
} })
try {
let result = await ipfs.add(globSource(folder, { recursive: true}), { pin: false })
const hash = result.cid.toString()

@ -1,5 +1,6 @@
'use strict'
import React from 'react' // eslint-disable-line
import { resolve } from 'path'
import { EditorUI } from '@remix-ui/editor' // eslint-disable-line
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
@ -140,16 +141,62 @@ class Editor extends Plugin {
this.on('sidePanel', 'pluginDisabled', (name) => {
this.clearAllDecorationsFor(name)
})
this.on('fileManager', 'fileClosed', (name) => {
if (name === this.currentFile) {
this.currentFile = null
this.renderComponent()
}
})
this.on('theme', 'themeLoaded', (theme) => {
this.currentThemeType = theme.quality
this.renderComponent()
})
this.on('fileManager', 'currentFileChanged', async (name) => {
if (name.endsWith('.ts')) {
// extract the import, resolve their content
// and add the imported files to Monaco through the `addModel`
// so Monaco can provide auto completion
let content = await this.call('fileManager', 'readFile', name)
const paths = name.split('/')
paths.pop()
const fromPath = paths.join('/') // get current execution context path
for (const match of content.matchAll(/import\s+.*\s+from\s+(?:"(.*?)"|'(.*?)')/g)) {
let path = match[2]
if (path.startsWith('./') || path.startsWith('../')) path = resolve(fromPath, path)
if (path.startsWith('/')) path = path.substring(1)
if (!path.endsWith('.ts')) path = path + '.ts'
if (await this.call('fileManager', 'exists', path)) {
content = await this.call('fileManager', 'readFile', path)
this.emit('addModel', content, 'typescript', path, false)
}
}
}
})
this.on('fileManager', 'noFileSelected', async () => {
this.currentFile = null
this.renderComponent()
})
this.on('fileManager', 'currentFileChanged', async (name) => {
if (name.endsWith('.ts')) {
// extract the import, resolve their content
// and add the imported files to Monaco through the `addModel`
// so Monaco can provide auto completion
let content = await this.call('fileManager', 'readFile', name)
const paths = name.split('/')
paths.pop()
const fromPath = paths.join('/') // get current execution context path
for (const match of content.matchAll(/import\s+.*\s+from\s+(?:"(.*?)"|'(.*?)')/g)) {
let path = match[2]
if (path.startsWith('./') || path.startsWith('../')) path = resolve(fromPath, path)
if (path.startsWith('/')) path = path.substring(1)
if (!path.endsWith('.ts')) path = path + '.ts'
if (await this.call('fileManager', 'exists', path)) {
content = await this.call('fileManager', 'readFile', path)
this.emit('addModel', content, 'typescript', path, false)
}
}
}
})
this.on('fileManager', 'noFileSelected', async () => {
this.currentFile = null
this.renderComponent()
})
try {
this.currentThemeType = (await this.call('theme', 'currentTheme')).quality
} catch (e) {

@ -4,7 +4,7 @@ import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry'
import { EventEmitter } from 'events'
import { RemixAppManager } from '../../../../../libs/remix-ui/plugin-manager/src/types'
import { fileChangedToastMsg, storageFullMessage } from '@remix-ui/helper'
import { fileChangedToastMsg, recursivePasteToastMsg, storageFullMessage } from '@remix-ui/helper'
import helper from '../../lib/helper.js'
/*
@ -275,7 +275,7 @@ class FileManager extends Plugin {
const provider = this.fileProviderOf(src)
if (provider.isSubDirectory(src, dest)) {
this.call('notification', 'toast', 'File(s) to paste is an ancestor of the destination folder')
this.call('notification', 'toast', recursivePasteToastMsg())
} else {
await this.inDepthCopy(src, dest)
}

@ -57,9 +57,6 @@ export class Layout extends Plugin {
this.panels.main.active = false
this.event.emit('change', null)
})
this.on('tabs', 'tabCountChanged', async count => {
if (!count) await this.call('manager', 'activatePlugin', 'home')
})
this.on('manager', 'activate', (profile: Profile) => {
switch (profile.name) {
case 'filePanel':

@ -283,7 +283,12 @@ export class TabProxy extends Plugin {
delete this._handlers[name]
let previous = currentFileTab
this.loadedTabs = this.loadedTabs.filter((tab, index) => {
if (!previous && tab.name === name) previous = this.loadedTabs[index - 1]
if (!previous && tab.name === name) {
if(index - 1 >= 0 && this.loadedTabs[index - 1])
previous = this.loadedTabs[index - 1]
else if (index + 1 && this.loadedTabs[index + 1])
previous = this.loadedTabs[index + 1]
}
return tab.name !== name
})
this.renderComponent()

@ -135,23 +135,28 @@ export class RemixdHandle extends WebsocketPlugin {
function remixdDialog () {
const commandText = 'remixd'
const fullCommandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>'
return (<>
<div className=''>
<div className='mb-2 text-break'>
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>.<br/><br/>
Remixd needs to be running in the background to load the files in localhost workspace. For more info, please check the <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">Remixd documentation</a>.
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>
<div className='mb-2 text-break'>
If you are just looking for the remixd command, here it is:
<br></br>
Go to your working directory and then run:
<br></br><b>{commandText}</b>
<CopyToClipboard data-id='remixdCopyCommand' content={commandText}></CopyToClipboard>
Remixd <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">documentation</a>.
</div>
<div className='mb-2 text-break'>
When connected, a session will be started between <em>{window.location.origin}</em> and your local file system at <i>ws://127.0.0.1:65520</i>.
The shared folder will be in the "File Explorers" workspace named "localhost".
<br/>Read more about other <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">Remixd ports usage</a>
The remixd command is:
<br/><b>{commandText}</b>
</div>
<div className='mb-2 text-break'>
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
</div>
<div className='mb-2 text-break'>
Example command with flags: <br/>
<b>{fullCommandText}</b>
</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.

@ -56,6 +56,10 @@ class CompileTab extends CompilerApiMixin(ViewPlugin) { // implements ICompilerA
this.renderComponent()
}
onFileRemoved () {
this.renderComponent()
}
onNoFileSelected () {
this.renderComponent()
}

@ -19,6 +19,7 @@ export const CompilerApiMixin = (Base) => class extends Base {
onCurrentFileChanged: (fileName: string) => void
// onResetResults: () => void
onSetWorkspace: (isLocalhost: boolean, workspaceName: string) => void
onFileRemoved: (path: string) => void
onNoFileSelected: () => void
onCompilationFinished: (compilationDetails: { contractMap: { file: string } | Record<string, any>, contractsDetails: Record<string, any> }) => void
onSessionSwitched: () => void
@ -240,6 +241,10 @@ export const CompilerApiMixin = (Base) => class extends Base {
if (this.onSetWorkspace) this.onSetWorkspace(workspace.isLocalhost, workspace.name)
})
this.on('fileManager', 'fileRemoved', (path) => {
if (this.onFileRemoved) this.onFileRemoved(path)
})
this.on('remixd', 'rootFolderChanged', () => {
this.resetResults()
if (this.onSetWorkspace) this.onSetWorkspace(true, 'localhost')
@ -282,36 +287,34 @@ export const CompilerApiMixin = (Base) => class extends Base {
type: 'warning'
})
} else this.statusChanged({ key: 'succeed', title: 'compilation successful', type: 'success' })
// Store the contracts
this.compilationDetails.contractsDetails = {}
this.compiler.visitContracts((contract) => {
this.compilationDetails.contractsDetails[contract.name] = parseContracts(
contract.name,
contract.object,
this.compiler.getSource(contract.file)
)
})
} else {
const count = (data.errors ? data.errors.filter(error => error.severity === 'error').length : 0 + (data.error ? 1 : 0))
this.statusChanged({ key: count, title: `compilation failed with ${count} error${count > 1 ? 's' : ''}`, type: 'error' })
}
// Update contract Selection
this.compilationDetails.contractMap = {}
if (success) this.compiler.visitContracts((contract) => { this.compilationDetails.contractMap[contract.name] = contract })
this.compilationDetails.target = source.target
// Store the contracts and Update contract Selection
if (success) {
this.compilationDetails = await this.visitsContractApi(source, data)
} else {
this.compilationDetails = {
contractMap: {},
contractsDetails: {},
target: source.target
}
}
if (this.onCompilationFinished) this.onCompilationFinished(this.compilationDetails)
// set annotations
if (data.errors) {
for (const error of data.errors) {
let pos = helper.getPositionDetails(error.formattedMessage)
if (pos.errFile) {
const file = pos.errFile
if (file) {
pos = {
row: pos.errLine,
column: pos.errCol,
text: error.formattedMessage,
type: error.severity
}
await this.call('editor', 'addAnnotation', pos, pos.errFile)
await this.call('editor', 'addAnnotation', pos, file)
}
}
}
@ -332,11 +335,40 @@ export const CompilerApiMixin = (Base) => class extends Base {
// ctrl+s or command+s
if ((e.metaKey || e.ctrlKey) && !e.shiftKey && e.keyCode === 83 && this.currentFile !== '') {
e.preventDefault()
if(await this.getAppParameter('hardhat-compilation')) this.compileTabLogic.runCompiler('hardhat')
else if(await this.getAppParameter('truffle-compilation')) this.compileTabLogic.runCompiler('truffle')
else this.compileTabLogic.runCompiler(undefined)
if (this.currentFile && (this.currentFile.endsWith('.sol') || this.currentFile.endsWith('.yul'))) {
if(await this.getAppParameter('hardhat-compilation')) this.compileTabLogic.runCompiler('hardhat')
else if(await this.getAppParameter('truffle-compilation')) this.compileTabLogic.runCompiler('truffle')
else this.compileTabLogic.runCompiler(undefined)
}
}
}
window.document.addEventListener('keydown', this.data.eventHandlers.onKeyDown)
}
async visitsContractApi (source, data): Promise<{ contractMap: { file: string } | Record<string, any>, contractsDetails: Record<string, any>, target?: string }> {
return new Promise((resolve) => {
if (!data.contracts || (data.contracts && Object.keys(data.contracts).length === 0)) {
return resolve({
contractMap: {},
contractsDetails: {},
target: source.target
})
}
const contractMap = {}
const contractsDetails = {}
this.compiler.visitContracts((contract) => {
contractMap[contract.name] = contract
contractsDetails[contract.name] = parseContracts(
contract.name,
contract.object,
this.compiler.getSource(contract.file)
)
})
return resolve({
contractMap,
contractsDetails,
target: source.target
})
})
}
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-analyzer",
"version": "0.5.22",
"version": "0.5.23",
"description": "Tool to perform static analysis on Solidity smart contracts",
"main": "src/index.js",
"types": "src/index.d.ts",
@ -22,8 +22,8 @@
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-astwalker": "^0.0.43",
"@remix-project/remix-lib": "^0.5.13",
"@remix-project/remix-astwalker": "^0.0.44",
"@remix-project/remix-lib": "^0.5.14",
"async": "^2.6.2",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.4.2",
@ -52,5 +52,5 @@
"typescript": "^3.7.5"
},
"typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78"
"gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-astwalker",
"version": "0.0.43",
"version": "0.0.44",
"description": "Tool to walk through Solidity AST",
"main": "src/index.js",
"scripts": {
@ -37,7 +37,7 @@
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.13",
"@remix-project/remix-lib": "^0.5.14",
"@types/tape": "^4.2.33",
"async": "^2.6.2",
"ethereumjs-util": "^7.0.10",
@ -54,5 +54,5 @@
"tap-spec": "^5.0.0"
},
"typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78"
"gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-debug",
"version": "0.5.13",
"version": "0.5.14",
"description": "Tool to debug Ethereum transactions",
"contributors": [
{
@ -22,9 +22,9 @@
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-astwalker": "^0.0.43",
"@remix-project/remix-lib": "^0.5.13",
"@remix-project/remix-simulator": "^0.2.13",
"@remix-project/remix-astwalker": "^0.0.44",
"@remix-project/remix-lib": "^0.5.14",
"@remix-project/remix-simulator": "^0.2.14",
"ansi-gray": "^0.1.1",
"async": "^2.6.2",
"color-support": "^1.1.3",
@ -68,5 +68,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme",
"typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78"
"gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-lib",
"version": "0.5.13",
"version": "0.5.14",
"description": "Library to various Remix tools",
"contributors": [
{
@ -54,5 +54,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-lib#readme",
"typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78"
"gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
}

@ -25,6 +25,7 @@ export interface ICompilerApi {
onCurrentFileChanged: (fileName: string) => void
// onResetResults: () => void,
onSetWorkspace: (isLocalhost: boolean, workspaceName: string) => void
onFileRemoved: (path: string) => void
onNoFileSelected: () => void
onCompilationFinished: (contractsDetails: any, contractMap: any) => void
onSessionSwitched: () => void

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-simulator",
"version": "0.2.13",
"version": "0.2.14",
"description": "Ethereum IDE and tools for the web",
"contributors": [
{
@ -18,7 +18,7 @@
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.13",
"@remix-project/remix-lib": "^0.5.14",
"ansi-gray": "^0.1.1",
"async": "^3.1.0",
"body-parser": "^1.18.2",
@ -67,5 +67,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-simulator#readme",
"typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78"
"gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
}

@ -40,7 +40,6 @@ export class Blocks {
return cb(new Error('block not found'))
}
console.log(block.transactions)
const transactions = block.transactions.map((t) => {
const hash = '0x' + t.hash().toString('hex')
const tx = this.vmContext.txByHash[hash]
@ -95,7 +94,6 @@ export class Blocks {
eth_getBlockByHash (payload, cb) {
const block = this.vmContext.blocks[payload.params[0]]
console.log(block.transactions)
const transactions = block.transactions.map((t) => {
const hash = '0x' + t.hash().toString('hex')
const tx = this.vmContext.txByHash[hash]

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-solidity",
"version": "0.4.13",
"version": "0.5.0",
"description": "Tool to load and run Solidity compiler",
"main": "src/index.js",
"types": "src/index.d.ts",
@ -18,7 +18,7 @@
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.13",
"@remix-project/remix-lib": "^0.5.14",
"async": "^2.6.2",
"eslint-scope": "^5.0.0",
"ethereumjs-util": "^7.0.10",
@ -61,5 +61,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme",
"typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78"
"gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-tests",
"version": "0.2.13",
"version": "0.2.14",
"description": "Tool to test Solidity smart contracts",
"main": "src/index.js",
"types": "./src/index.d.ts",
@ -39,9 +39,9 @@
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.13",
"@remix-project/remix-simulator": "^0.2.13",
"@remix-project/remix-solidity": "^0.4.13",
"@remix-project/remix-lib": "^0.5.14",
"@remix-project/remix-simulator": "^0.2.14",
"@remix-project/remix-solidity": "^0.5.0",
"ansi-gray": "^0.1.1",
"async": "^2.6.0",
"axios": ">=0.21.1",
@ -79,5 +79,5 @@
"typescript": "^3.3.1"
},
"typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78"
"gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
}

@ -226,7 +226,7 @@ declare interface CondensedCompilationInput {
optimize: boolean
/** e.g: 0.6.8+commit.0bbfe453 */
version: string
evmVersion?: 'istanbul' | 'petersburg' | 'constantinople' | 'byzantium' | 'spuriousDragon' | 'tangerineWhistle' | 'homestead'
evmVersion?: 'berlin' | 'istanbul' | 'petersburg' | 'constantinople' | 'byzantium' | 'spuriousDragon' | 'tangerineWhistle' | 'homestead'
}
declare interface ContentImport {

@ -199,6 +199,10 @@ export const loadTypes = async (monaco) => {
const indexWeb3Personal = await import('raw-loader!web3-eth-personal/types/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Personal.default, `file:///node_modules/@types/web3-eth-personal/index.d.ts`)
// @ts-ignore
const indexWeb3Contract = await import('raw-loader!web3-eth-contract/types/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Contract.default, `file:///node_modules/@types/web3-eth-contract/index.d.ts`)
// @ts-ignore
const indexWeb3Net = await import('raw-loader!web3-net/types/index.d.ts')
monaco.languages.typescript.typescriptDefaults.addExtraLib(indexWeb3Net.default, `file:///node_modules/@types/web3-net/index.d.ts`)

@ -78,3 +78,9 @@ export const storageFullMessage = () => (
</span>
</div>
)
export const recursivePasteToastMsg = () => (
<div>
File(s) to paste is an ancestor of the destination folder
</div>
)

@ -0,0 +1,12 @@
.RSSFeed-item img {
width: 100%;
}
.RSSFeed-item .truncate {
max-height: 500px;
overflow: hidden;
}
.RSSFeed-item .more-button {
}

@ -0,0 +1,42 @@
import React, { useState, useEffect } from "react";
import Parser from "rss-parser";
import './rssFeed.css';
interface RSSFeedProps {
feedUrl: string,
maxItems: number,
}
export function RSSFeed({ feedUrl, maxItems }: RSSFeedProps) {
const [feed, setFeed] = useState(null);
useEffect(() => {
const fetchData = async () => {
const parser = new Parser()
const feed = await parser.parseURL(feedUrl);
for (const item of feed.items) {
item.content = item['content:encoded']
item.date = new Date(item.pubDate).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric'
})
}
setFeed(feed);
};
fetchData();
}, [feedUrl]);
return (<>
{feed && feed.items.slice(0, maxItems).map((item: any, index: any) => (
<div className='RSSFeed-item' key={index}>
<a target="_blank" href={item.link}><h3>{item.title}</h3></a>
<p>Author: {item.creator}</p>
<h4>{item.date}</h4>
<div className="truncate" dangerouslySetInnerHTML={{ __html: item.content }} />
<a className="more-button btn mb-3" target="_blank" href={item.link}>READ MORE</a>
<hr></hr>
</div>
))}
</>)
}

@ -5,6 +5,7 @@ import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import PluginButton from './components/pluginButton' // eslint-disable-line
import { ThemeContext, themes } from './themeContext'
import { RSSFeed } from './components/rssFeed'
declare global {
interface Window {
_paq: any
@ -107,14 +108,8 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
scriptTwitter.src = 'https://platform.twitter.com/widgets.js'
scriptTwitter.async = true
document.body.appendChild(scriptTwitter)
// to retrieve medium publications
const scriptMedium = document.createElement('script')
scriptMedium.src = 'https://www.twilik.com/assets/retainable/rss-embed/retainable-rss-embed.js'
scriptMedium.async = true
document.body.appendChild(scriptMedium)
return () => {
document.body.removeChild(scriptTwitter)
document.body.removeChild(scriptMedium)
}
}, [])
@ -340,17 +335,7 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
>
<div id="remixIDE_MediumBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" style={ { maxHeight: maxHeight } }>
<div id="medium-widget" className="px-3 remixui_home_media" hidden={state.showMediaPanel !== 'medium'} style={ { maxHeight: '10000px' } }>
<div
id="retainable-rss-embed"
data-rss="https://medium.com/feed/remix-ide"
data-maxcols="1"
data-layout="grid"
data-poststyle="external"
data-readmore="More..."
data-buttonclass="btn mb-3"
data-offset="-100"
>
</div>
<RSSFeed feedUrl='https://rss.remixproject.org/' maxItems={10} />
</div>
</div>
<div id="remixIDE_TwitterBlock" className="p-2 mx-1 mt-3 mb-0 remixui_home_remixHomeMedia" hidden={state.showMediaPanel !== 'twitter'} style={ { maxHeight: maxHeight, marginRight: '28px' } } >

@ -6,6 +6,7 @@ import { publishToSwarm } from './publishOnSwarm'
export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
const { api, storage, contract, resetStorage } = props
const [modalShown, setModalShown] = useState(false)
const [state, setState] = useState({
modal: {
title: '',
@ -28,9 +29,7 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
try {
const result = await publishToSwarm(contract, api)
modal(`Published ${contract.name}'s Metadata`, publishMessage(result.uploaded))
// triggered each time there's a new verified publish (means hash correspond)
api.writeFile('swarm/' + result.item.hash, result.item.content)
modal(`Published ${contract.name}'s Metadata and Sources`, publishMessage(result.uploaded))
} catch (err) {
let parseError = err
try {
@ -40,14 +39,32 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
modal('Swarm Publish Failed', publishMessageFailed(storage, parseError))
}
} else {
try {
const result = await publishToIPFS(contract, api)
modal(`Published ${contract.name}'s Metadata`, publishMessage(result.uploaded))
// triggered each time there's a new verified publish (means hash correspond)
api.writeFile('ipfs/' + result.item.hash, result.item.content)
} catch (err) {
modal('IPFS Publish Failed', publishMessageFailed(storage, err))
if (!api.config.get('settings/ipfs-url') && !modalShown) {
modal('IPFS Settings', <div>You have not set your own custom IPFS settings.<br></br>
<br></br>
We wont be providing a public endpoint anymore for publishing your contracts to IPFS.<br></br>Instead of that, 4 options are now available:<br></br>
<br></br>
<ul className='pl-3'>
<li>
DEFAULT OPTION:
Use the public INFURA node. This will not guarantee your data will persist.
</li>
<li>
Use your own INFURA IPFS node. This requires a subscription. <a href='https://infura.io/product/ipfs' target={'_blank'}>Learn more</a>
</li>
<li>
Use any external IPFS which doesnt require any authentification.
</li>
<li>
Use your own local ipfs node (which usually runs under http://localhost:5001)
</li>
</ul>
You can update your IPFS settings in the SETTINGS tab.
<br></br>
Now the default option will be used.
</div>, async () => await ipfs(contract, api))
} else {
await ipfs(contract, api)
}
}
}
@ -58,18 +75,29 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
}
}, [storage])
const ipfs = async (contract, api) => {
try {
const result = await publishToIPFS(contract, api)
modal(`Published ${contract.name}'s Metadata and Sources`, publishMessage(result.uploaded))
} catch (err) {
modal('IPFS Publish Failed', publishMessageFailed(storage, err))
}
setModalShown(true)
}
const publishMessage = (uploaded) => (
<span> Metadata of "{contract.name.toLowerCase()}" was published successfully. <br />
<span> Metadata and sources of "{contract.name.toLowerCase()}" were published successfully. <br />
<pre>
<div>
{ uploaded.map((value, index) => <div key={index}><b>{ value.filename }</b> : <pre>{ value.output.url }</pre></div>) }
{uploaded.map((value, index) => <div key={index}><b>{value.filename}</b> : <pre>{value.output.url}</pre></div>)}
</div>
</pre>
</span>
)
const publishMessageFailed = (storage, err) => (
<span>Failed to publish metadata file to { storage }, please check the { storage } gateways is available. <br />
<span>Failed to publish metadata file and sources to {storage}, please check the {storage} gateways is available. <br />
{err}
</span>
)
@ -81,7 +109,7 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
resetStorage()
}
const modal = async (title: string, message: string | JSX.Element) => {
const modal = async (title: string, message: string | JSX.Element, okFn: any = () => { }) => {
await setState(prevState => {
return {
...prevState,
@ -89,7 +117,8 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
...prevState.modal,
hide: false,
message,
title
title,
okFn
}
}
})
@ -98,13 +127,13 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
return (
<ModalDialog
id={props.id || 'publishToStorage'}
title={ state.modal.title }
message={ state.modal.message }
hide={ state.modal.hide }
title={state.modal.title}
message={state.modal.message}
hide={state.modal.hide}
okLabel='OK'
okFn={() => {}}
handleHide={ handleHideModal }>
{ (typeof state.modal.message !== 'string') && state.modal.message }
okFn={state.modal.okFn}
handleHide={handleHideModal}>
{(typeof state.modal.message !== 'string') && state.modal.message}
</ModalDialog>
)
}

@ -79,7 +79,7 @@ export const publishToSwarm = async (contract, api) => {
// publish the list of sources in order, fail if any failed
await Promise.all(sources.map(async (item) => {
try {
const result = await swarmVerifiedPublish(beeNodes, postageStampId, item.content, item.hash)
const result = await swarmVerifiedPublish(beeNodes, postageStampId, item.content, item.hash, api)
try {
item.hash = result.url.match('bzz-raw://(.+)')[1]
@ -96,9 +96,9 @@ export const publishToSwarm = async (contract, api) => {
}
}))
const metadataContent = JSON.stringify(metadata)
const metadataContent = JSON.stringify(metadata, null, '\t')
try {
const result = await swarmVerifiedPublish(beeNodes, postageStampId, metadataContent, '')
const result = await swarmVerifiedPublish(beeNodes, postageStampId, metadataContent, '', api)
try {
contract.metadataHash = result.url.match('bzz-raw://(.+)')[1]
@ -121,7 +121,7 @@ export const publishToSwarm = async (contract, api) => {
return { uploaded, item }
}
const swarmVerifiedPublish = async (beeNodes: Bee[], postageStampId: string, content, expectedHash): Promise<Record<string, any>> => {
const swarmVerifiedPublish = async (beeNodes: Bee[], postageStampId: string, content, expectedHash, api): Promise<Record<string, any>> => {
try {
const results = await uploadToBeeNodes(beeNodes, postageStampId, content)
const hash = hashFromResults(results)
@ -129,6 +129,7 @@ const swarmVerifiedPublish = async (beeNodes: Bee[], postageStampId: string, con
if (expectedHash && hash !== expectedHash) {
return { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'bzz-raw://' + hash, hash }
} else {
api.writeFile('swarm/' + hash, content)
return { message: 'ok', url: 'bzz-raw://' + hash, hash }
}
} catch (error) {

@ -1,12 +1,26 @@
import IpfsClient from 'ipfs-mini'
import IpfsHttpClient from 'ipfs-http-client'
const ipfsNodes = [
new IpfsClient({ host: 'ipfs.remixproject.org', port: 443, protocol: 'https' }),
new IpfsClient({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' }),
new IpfsClient({ host: '127.0.0.1', port: 5001, protocol: 'http' })
]
let ipfsNodes = []
export const publishToIPFS = async (contract, api) => {
ipfsNodes = [
IpfsHttpClient({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' })
]
if (api.config.get('settings/ipfs-url')) {
const auth = api.config.get('settings/ipfs-project-id') ? 'Basic ' + Buffer.from(api.config.get('settings/ipfs-project-id') + ':' + api.config.get('settings/ipfs-project-secret')).toString('base64') : null
const ipfs = IpfsHttpClient({
host: api.config.get('settings/ipfs-url'),
port: api.config.get('settings/ipfs-port'),
protocol: api.config.get('settings/ipfs-protocol'),
headers: {
Authorization: auth
}
})
ipfsNodes.push(ipfs)
}
// gather list of files to publish
const sources = []
let metadata
@ -63,8 +77,7 @@ export const publishToIPFS = async (contract, api) => {
// publish the list of sources in order, fail if any failed
await Promise.all(sources.map(async (item) => {
try {
const result = await ipfsVerifiedPublish(item.content, item.hash)
const result = await ipfsVerifiedPublish(item.content, item.hash, api)
try {
item.hash = result.url.match('dweb:/ipfs/(.+)')[1]
} catch (e) {
@ -76,10 +89,10 @@ export const publishToIPFS = async (contract, api) => {
throw new Error(error)
}
}))
const metadataContent = JSON.stringify(metadata)
const metadataContent = JSON.stringify(metadata, null, '\t')
try {
const result = await ipfsVerifiedPublish(metadataContent, '')
const result = await ipfsVerifiedPublish(metadataContent, '', api)
try {
contract.metadataHash = result.url.match('dweb:/ipfs/(.+)')[1]
@ -101,14 +114,15 @@ export const publishToIPFS = async (contract, api) => {
return { uploaded, item }
}
const ipfsVerifiedPublish = async (content, expectedHash) => {
const ipfsVerifiedPublish = async (content, expectedHash, api) => {
try {
const results = await severalGatewaysPush(content)
if (expectedHash && results !== expectedHash) {
return { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'dweb:/ipfs/' + results, hash: results }
const hash: any = (results as any).path
if (expectedHash && hash !== expectedHash) {
return { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'dweb:/ipfs/' + hash, hash }
} else {
return { message: 'ok', url: 'dweb:/ipfs/' + results, hash: results }
api.writeFile('ipfs/' + hash, content)
return { message: 'ok', url: 'dweb:/ipfs/' + hash, hash }
}
} catch (error) {
throw new Error(error)

@ -19,7 +19,7 @@ export function EnvironmentUI (props: EnvironmentProps) {
Environment
</label>
<div className="udapp_environment">
<select id="selectExEnvOptions" data-id="settingsSelectEnvOptions" className="form-control udapp_select custom-select" value={props.selectedEnv} onChange={(e) => { handleChangeExEnv(e.target.value) }}>
<select id="selectExEnvOptions" data-id="settingsSelectEnvOptions" className="form-control udapp_select custom-select" value={props.selectedEnv || ''} onChange={(e) => { handleChangeExEnv(e.target.value) }}>
{
props.providers.providerList.map((provider, index) =>
<option id={provider.id} key={index} data-id={provider.dataId}

@ -344,8 +344,10 @@ export const SearchProvider = ({
console.log(e)
}
}
setTimeout(async () => {
await fetchWorkspace()
}, 500)
fetchWorkspace()
return () => {
plugin.off('fileManager', 'fileChanged')

@ -17,6 +17,7 @@ export const enablePersonalModeText = ' Enable Personal Mode for web3 provider.
export const matomoAnalytics = 'Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the site’s UX & UI. See more about '
export const swarmSettingsTitle = 'Swarm Settings'
export const swarmSettingsText = 'Swarm Settings'
export const ipfsSettingsText = 'IPFS Settings'
export const labels = {
'gist': {
'link': gitAccessTokenLink,

@ -1,10 +1,10 @@
import React, { useState, useReducer, useEffect, useCallback } from 'react' // eslint-disable-line
import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line
import { enablePersonalModeText, ethereunVMText, labels, generateContractMetadataText, matomoAnalytics, textDark, textSecondary, warnText, wordWrapText, swarmSettingsTitle } from './constants'
import { enablePersonalModeText, ethereunVMText, labels, generateContractMetadataText, matomoAnalytics, textDark, textSecondary, warnText, wordWrapText, swarmSettingsTitle, ipfsSettingsText } from './constants'
import './remix-ui-settings.css'
import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast, saveSwarmSettingsToast } from './settingsAction'
import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast, saveSwarmSettingsToast, saveIpfsSettingsToast } from './settingsAction'
import { initialState, toastInitialState, toastReducer, settingReducer } from './settingsReducer'
import { Toaster } from '@remix-ui/toaster'// eslint-disable-line
import { RemixUiThemeModule, ThemeModule} from '@remix-ui/theme-module'
@ -26,6 +26,12 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const [privateBeeAddress, setPrivateBeeAddress] = useState('')
const [postageStampId, setPostageStampId] = useState('')
const [resetState, refresh] = useState(0)
const [ipfsUrl, setipfsUrl] = useState('')
const [ipfsPort, setipfsPort] = useState('')
const [ipfsProtocol, setipfsProtocol] = useState('')
const [ipfsProjectId, setipfsProjectId] = useState('')
const [ipfsProjectSecret, setipfsProjectSecret] = useState('')
const initValue = () => {
const metadataConfig = props.config.get('settings/generate-contract-metadata')
@ -59,6 +65,29 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
if (configPostageStampId) {
setPostageStampId(configPostageStampId)
}
const configipfsUrl = props.config.get('settings/ipfs-url')
if (configipfsUrl) {
setipfsUrl(configipfsUrl)
}
const configipfsPort = props.config.get('settings/ipfs-port')
if (configipfsPort) {
setipfsPort(configipfsPort)
}
const configipfsProtocol = props.config.get('settings/ipfs-protocol')
if (configipfsProtocol) {
setipfsProtocol(configipfsProtocol)
}
const configipfsProjectId = props.config.get('settings/ipfs-project-id')
if (configipfsProjectId) {
setipfsProjectId(configipfsProjectId)
}
const configipfsProjectSecret = props.config.get('settings/ipfs-project-secret')
if (configipfsProjectSecret) {
setipfsProjectSecret(configipfsProjectSecret)
}
}, [themeName, state.message])
useEffect(() => {
@ -237,6 +266,83 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
</div>
)
// ipfs settings
const handleSaveIpfsProjectId = useCallback(
(event) => {
setipfsProjectId(event.target.value)
}
, [ipfsProjectId]
)
const handleSaveIpfsSecret = useCallback(
(event) => {
setipfsProjectSecret(event.target.value)
}
, [ipfsProjectSecret]
)
const handleSaveIpfsUrl = useCallback(
(event) => {
setipfsUrl(event.target.value)
}
, [ipfsUrl]
)
const handleSaveIpfsPort = useCallback(
(event) => {
setipfsPort(event.target.value)
}
, [ipfsPort]
)
const handleSaveIpfsProtocol = useCallback(
(event) => {
setipfsProtocol(event.target.value)
}
, [ipfsProtocol]
)
const saveIpfsSettings = () => {
saveIpfsSettingsToast(props.config, dispatchToast, ipfsUrl, ipfsProtocol, ipfsPort, ipfsProjectId, ipfsProjectSecret)
}
const ipfsSettings = () => (
<div className="border-top">
<div className="card-body pt-3 pb-2">
<h6 className="card-title">{ ipfsSettingsText }</h6>
<div className="pt-2 mb-1"><label>IPFS HOST:</label>
<div className="text-secondary mb-0 h6">
<input placeholder='e.g. ipfs.infura.io' id="settingsIpfsUrl" data-id="settingsIpfsUrl" className="form-control" onChange={handleSaveIpfsUrl} value={ ipfsUrl } />
</div>
</div>
<div className=""><label>IPFS PROTOCOL:</label>
<div className="text-secondary mb-0 h6">
<input placeholder='e.g. https' id="settingsIpfsProtocol" data-id="settingsIpfsProtocol" className="form-control" onChange={handleSaveIpfsProtocol} value={ ipfsProtocol } />
</div>
</div>
<div className=""><label>IPFS PORT:</label>
<div className="text-secondary mb-0 h6">
<input placeholder='e.g. 5001' id="settingsIpfsPort" data-id="settingsIpfsPort" className="form-control" onChange={handleSaveIpfsPort} value={ ipfsPort } />
</div>
</div>
<div className=""><label>IPFS PROJECT ID [ INFURA ]:</label>
<div className="text-secondary mb-0 h6">
<input id="settingsIpfsProjectId" data-id="settingsIpfsProjectId" className="form-control" onChange={handleSaveIpfsProjectId} value={ ipfsProjectId } />
</div>
</div>
<div className=""><label>IPFS PROJECT SECRET [ INFURA ]:</label>
<div className="text-secondary mb-0 h6">
<input id="settingsIpfsProjectSecret" data-id="settingsIpfsProjectSecret" className="form-control" type="password" onChange={handleSaveIpfsSecret} value={ ipfsProjectSecret } />
</div>
</div>
<div className="d-flex justify-content-end pt-2">
<input className="btn btn-sm btn-primary ml-2" id="saveIpfssettings" data-id="settingsTabSaveIpfsSettings" onClick={() => saveIpfsSettings()} value="Save" type="button"></input>
</div>
</div>
</div>)
return (
<div>
{state.message ? <Toaster message= {state.message}/> : null}
@ -244,6 +350,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
{token('gist')}
{token('etherscan')}
{swarmSettings()}
{ipfsSettings()}
<RemixUiThemeModule themeModule={props._deps.themeModule} />
</div>
)

@ -56,3 +56,12 @@ export const saveSwarmSettingsToast = (config, dispatch, privateBeeAddress, post
config.set('settings/swarm-postage-stamp-id', postageStampId)
dispatch({ type: 'save', payload: { message: 'Swarm settings have been saved' } })
}
export const saveIpfsSettingsToast = (config, dispatch, ipfsURL, ipfsProtocol, ipfsPort, ipfsProjectId, ipfsProjectSecret) => {
config.set('settings/ipfs-url', ipfsURL)
config.set('settings/ipfs-protocol', ipfsProtocol)
config.set('settings/ipfs-port', ipfsPort)
config.set('settings/ipfs-project-id', ipfsProjectId)
config.set('settings/ipfs-project-secret', ipfsProjectSecret)
dispatch({ type: 'save', payload: { message: 'IPFS settings have been saved' } })
}

@ -12,6 +12,7 @@ import { CopyToClipboard } from '@remix-ui/clipboard'
import { configFileContent } from './compilerConfiguration'
import './css/style.css'
const defaultPath = "compiler_config.json"
declare global {
interface Window {
@ -22,11 +23,23 @@ declare global {
const _paq = window._paq = window._paq || [] //eslint-disable-line
export const CompilerContainer = (props: CompilerContainerProps) => {
const { api, compileTabLogic, tooltip, modal, compiledFileName, updateCurrentVersion, configurationSettings, isHardhatProject, isTruffleProject, workspaceName } = props // eslint-disable-line
const {
api,
compileTabLogic,
tooltip,
modal,
compiledFileName,
updateCurrentVersion,
configurationSettings,
isHardhatProject,
isTruffleProject,
workspaceName,
configFilePath,
setConfigFilePath,
} = props // eslint-disable-line
const [state, setState] = useState({
hideWarnings: false,
autoCompile: false,
configFilePath: "compiler_config.json",
useFileConfiguration: false,
matomoAutocompileOnce: true,
optimize: false,
@ -40,7 +53,8 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
compiledFileName: '',
includeNightlies: false,
language: 'Solidity',
evmVersion: ''
evmVersion: '',
createFileOnce: true
})
const [showFilePathInput, setShowFilePathInput] = useState<boolean>(false)
const [toggleExpander, setToggleExpander] = useState<boolean>(false)
@ -53,17 +67,27 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
const [compilerContainer, dispatch] = useReducer(compilerReducer, compilerInitialState)
useEffect(() => {
api.setAppParameter('configFilePath', "/compiler_config.json")
api.fileExists("/compiler_config.json").then((exists) => {
if (!exists) createNewConfigFile()
else {
// what to do? discuss
if (workspaceName) {
api.setAppParameter('configFilePath', defaultPath)
if (state.useFileConfiguration) {
api.fileExists(defaultPath).then((exists) => {
if (!exists && state.useFileConfiguration) createNewConfigFile()
})
}
})
api.setAppParameter('configFilePath', "/compiler_config.json")
setShowFilePathInput(false)
setShowFilePathInput(false)
}
}, [workspaceName])
useEffect(() => {
if (state.useFileConfiguration) {
api.fileExists(defaultPath).then((exists) => {
if (!exists) createNewConfigFile()
})
setToggleExpander(true)
}
}, [state.useFileConfiguration])
useEffect(() => {
const listener = (event) => {
if (configFilePathInput.current !== event.target) {
@ -106,8 +130,10 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
const hideWarnings = await api.getAppParameter('hideWarnings') as boolean || false
const includeNightlies = await api.getAppParameter('includeNightlies') as boolean || false
const useFileConfiguration = await api.getAppParameter('useFileConfiguration') as boolean || false
let configFilePath = await api.getAppParameter('configFilePath')
if (!configFilePath || configFilePath == '') configFilePath = "/compiler_config.json"
let configFilePathSaved = await api.getAppParameter('configFilePath')
if (!configFilePathSaved || configFilePathSaved == '') configFilePathSaved = defaultPath
setConfigFilePath(configFilePathSaved)
setState(prevState => {
const params = api.getCompilerParameters()
@ -122,7 +148,6 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
autoCompile: autocompile,
includeNightlies: includeNightlies,
useFileConfiguration: useFileConfiguration,
configFilePath: configFilePath,
optimize: optimize,
runs: runs,
evmVersion: (evmVersion !== null) && (evmVersion !== 'null') && (evmVersion !== undefined) && (evmVersion !== 'undefined') ? evmVersion : 'default',
@ -181,7 +206,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
useEffect(() => {
compileTabLogic.setUseFileConfiguration(state.useFileConfiguration)
if (state.useFileConfiguration) compileTabLogic.setConfigFilePath(state.configFilePath)
if (state.useFileConfiguration) compileTabLogic.setConfigFilePath(configFilePath)
}, [state.useFileConfiguration])
useEffect(() => {
@ -191,6 +216,16 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
}, [configurationSettings])
const toggleConfigType = () => {
if (state.useFileConfiguration)
if (state.createFileOnce) {
api.fileExists(defaultPath).then((exists) => {
if (!exists || state.useFileConfiguration ) createNewConfigFile()
})
setState(prevState => {
return { ...prevState, createFileOnce: false }
})
}
setState(prevState => {
api.setAppParameter('useFileConfiguration', !state.useFileConfiguration)
return { ...prevState, useFileConfiguration: !state.useFileConfiguration }
@ -198,18 +233,17 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
}
const openFile = async () => {
api.open(state.configFilePath)
api.open(configFilePath)
}
const createNewConfigFile = async () => {
let filePath = configFilePathInput.current && configFilePathInput.current.value !== '' ? configFilePathInput.current.value : state.configFilePath
let filePath = configFilePathInput.current && configFilePathInput.current.value !== '' ? configFilePathInput.current.value : configFilePath
if (filePath === '') filePath = defaultPath
if (!filePath.endsWith('.json')) filePath = filePath + '.json'
await api.writeFile(filePath, configFileContent)
api.setAppParameter('configFilePath', filePath)
setState(prevState => {
return { ...prevState, configFilePath: filePath }
})
setConfigFilePath(filePath)
compileTabLogic.setConfigFilePath(filePath)
setShowFilePathInput(false)
}
@ -220,9 +254,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
if (await api.fileExists(configFilePathInput.current.value)) {
api.setAppParameter('configFilePath', configFilePathInput.current.value)
setState(prevState => {
return { ...prevState, configFilePath: configFilePathInput.current.value }
})
setConfigFilePath(configFilePathInput.current.value)
compileTabLogic.setConfigFilePath(configFilePathInput.current.value)
setShowFilePathInput(false)
@ -394,6 +426,9 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
compileIcon.current.classList.remove('remixui_spinningIcon')
compileIcon.current.classList.remove('remixui_bouncingIcon')
if (!state.autoCompile || (state.autoCompile && state.matomoAutocompileOnce)) {
if (state.useFileConfiguration)
_paq.push(['trackEvent', 'compiler', 'compiled_with_config_file'])
_paq.push(['trackEvent', 'compiler', 'compiled_with_version', _retrieveVersion()])
if (state.autoCompile && state.matomoAutocompileOnce) {
setState(prevState => {
@ -750,36 +785,38 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
</div>
<div className="d-flex pb-1 remixui_compilerConfig custom-control custom-radio">
<input className="custom-control-input" type="radio" name="configradio" value="file" onChange={toggleConfigType} checked={state.useFileConfiguration} id="scFileConfig" />
<label className="form-check-label custom-control-label" htmlFor="scFileConfig">Use configuration file</label>
<label className="form-check-label custom-control-label" htmlFor="scFileConfig" data-id="scFileConfiguration">Use configuration file</label>
</div>
<div className={`pt-2 ml-4 ml-2 align-items-start justify-content-between d-flex`}>
{ (!showFilePathInput && state.useFileConfiguration) && <span
title="Click to open the config file."
onClick={openFile}
className="py-2 text-primary remixui_compilerConfigPath"
>{state.configFilePath}</span> }
{ (!showFilePathInput&& !state.useFileConfiguration) && <span className="py-2 text-secondary">{state.configFilePath}</span> }
onClick={configFilePath === '' ? () => {} : openFile}
className="py-2 remixui_compilerConfigPath"
>{configFilePath === '' ? 'No file selected.' : configFilePath}</span> }
{ (!showFilePathInput && !state.useFileConfiguration) && <span className="py-2 text-secondary">{configFilePath}</span> }
<input
ref={configFilePathInput}
className={`py-0 my-0 form-control ${showFilePathInput ? "d-flex" : "d-none"}`}
placeholder={"Enter the new path"}
title="If the file you entered does not exist you will be able to create one in the next step."
disabled={!state.useFileConfiguration}
data-id="scConfigFilePathInput"
onKeyPress={event => {
if (event.key === 'Enter') {
handleConfigPathChange()
}
}}
/>
{ !showFilePathInput && <button disabled={!state.useFileConfiguration} className="btn-secondary" onClick={() => {setShowFilePathInput(true)}}>Change</button> }
{ !showFilePathInput && <button disabled={!state.useFileConfiguration} data-id="scConfigChangeFilePath" className="btn-secondary" onClick={() => {setShowFilePathInput(true)}}>Change</button> }
</div>
</div>
<div className="px-4">
<button id="compileBtn" data-id="compilerContainerCompileBtn" className="btn btn-primary btn-block d-block w-100 text-break remixui_disabled mb-1 mt-3" onClick={compile} disabled={disableCompileButton}>
<button id="compileBtn" data-id="compilerContainerCompileBtn" className="btn btn-primary btn-block d-block w-100 text-break remixui_disabled mb-1 mt-3" onClick={compile} disabled={(configFilePath === '' && state.useFileConfiguration) || disableCompileButton}>
<OverlayTrigger overlay={
<Tooltip id="overlay-tooltip-compile">
<div className="text-left">
<div><b>Ctrl+S</b> for compiling</div>
{ !(configFilePath === '' && state.useFileConfiguration) && <div><b>Ctrl+S</b> for compiling</div> }
{ (configFilePath === '' && state.useFileConfiguration) && <div> No config file selected</div> }
</div>
</Tooltip>
}>
@ -790,11 +827,12 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
</OverlayTrigger>
</button>
<div className='d-flex align-items-center'>
<button id="compileAndRunBtn" data-id="compilerContainerCompileAndRunBtn" className="btn btn-secondary btn-block d-block w-100 text-break remixui_solidityCompileAndRunButton d-inline-block remixui_disabled mb-1 mt-3" onClick={compileAndRun} disabled={disableCompileButton}>
<button id="compileAndRunBtn" data-id="compilerContainerCompileAndRunBtn" className="btn btn-secondary btn-block d-block w-100 text-break remixui_solidityCompileAndRunButton d-inline-block remixui_disabled mb-1 mt-3" onClick={compileAndRun} disabled={(configFilePath === '' && state.useFileConfiguration) || disableCompileButton}>
<OverlayTrigger overlay={
<Tooltip id="overlay-tooltip-compile-run">
<div className="text-left">
<div><b>Ctrl+Shift+S</b> for compiling and script execution</div>
{ !(configFilePath === '' && state.useFileConfiguration) && <div><b>Ctrl+Shift+S</b> for compiling and script execution</div> }
{ (configFilePath === '' && state.useFileConfiguration) && <div> No config file selected</div> }
</div>
</Tooltip>
}>
@ -806,18 +844,18 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
<OverlayTrigger overlay={
<Tooltip id="overlay-tooltip-compile-run-doc">
<div className="text-left p-2">
<div>Choose the script to execute right after compilation by adding the `dev-run-script` natspec tag, as in:</div>
<pre>
<code>
/**<br />
* @title ContractName<br />
* @dev ContractDescription<br />
* @custom:dev-run-script file_path<br />
*/<br />
contract ContractName {'{}'}<br />
</code>
</pre>
Click to know more
<div>Choose the script to execute right after compilation by adding the `dev-run-script` natspec tag, as in:</div>
<pre>
<code>
/**<br />
* @title ContractName<br />
* @dev ContractDescription<br />
* @custom:dev-run-script file_path<br />
*/<br />
contract ContractName {'{}'}<br />
</code>
</pre>
Click to know more
</div>
</Tooltip>
}>

@ -24,7 +24,7 @@ export class CompileTabLogic {
constructor (public api: ICompilerApi, public contentImport) {
this.event = new EventEmitter()
this.compiler = new Compiler((url, cb) => api.resolveContentAndSave(url).then((result) => cb(null, result)).catch((error) => cb(error.message)))
this.evmVersions = ['default', 'london', 'istanbul', 'petersburg', 'constantinople', 'byzantium', 'spuriousDragon', 'tangerineWhistle', 'homestead']
this.evmVersions = ['default', 'berlin', 'london', 'istanbul', 'petersburg', 'constantinople', 'byzantium', 'spuriousDragon', 'tangerineWhistle', 'homestead']
}
init () {
@ -106,9 +106,11 @@ export class CompileTabLogic {
const sources = { [target]: { content } }
this.event.emit('removeAnnotations')
this.event.emit('startingCompilation')
this.api.readFile(this.configFilePath).then( contentConfig => {
this.compiler.set('configFileContent', contentConfig)
})
if (this.configFilePath) {
this.api.readFile(this.configFilePath).then( contentConfig => {
this.compiler.set('configFileContent', contentConfig)
})
}
// setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation')
setTimeout(() => { this.compiler.compile(sources, target); resolve(true) }, 100)
}).catch((error) => {

@ -15,6 +15,7 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
isTruffleProject: false,
workspaceName: '',
currentFile,
configFilePath: 'compiler_config.json',
loading: false,
compileTabLogic: null,
compiler: null,
@ -72,6 +73,13 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
})
}
api.onFileRemoved = (path: string) => {
if (path === state.configFilePath)
setState(prevState => {
return { ...prevState, configFilePath: '' }
})
}
api.onNoFileSelected = () => {
setState(prevState => {
return { ...prevState, currentFile: '' }
@ -103,6 +111,13 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
setBadgeStatus({ ...badgeStatus, [currentFile]: data })
}
const setConfigFilePath = (path: string) => {
setState(prevState => {
return { ...prevState, configFilePath: path }
})
}
const toast = (message: string) => {
setState(prevState => {
return { ...prevState, toasterMsg: message }
@ -162,6 +177,8 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
compiledFileName={currentFile}
updateCurrentVersion={updateCurrentVersion}
configurationSettings={configurationSettings}
configFilePath={state.configFilePath}
setConfigFilePath={setConfigFilePath}
/>
{ contractsFile[currentFile] && contractsFile[currentFile].contractsDetails && <ContractSelection api={api} contractsDetails={contractsFile[currentFile].contractsDetails} contractList={contractsFile[currentFile].contractList} modal={modal} /> }
{ compileErrors[currentFile] &&

@ -16,7 +16,9 @@ export interface CompilerContainerProps {
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void,
compiledFileName: string,
updateCurrentVersion: any,
configurationSettings: ConfigurationSettings
configurationSettings: ConfigurationSettings,
configFilePath: string,
setConfigFilePath: (path: string) => void
}
export interface ContractSelectionProps {
api: ICompilerApi,

@ -13,6 +13,7 @@
padding-left : 6px;
padding-right : 6px;
padding-top : 6px;
overflow-y : auto;
}
.remixui_fileExplorerTree {
cursor : default;

@ -228,8 +228,8 @@ export function Workspace () {
</div>
</header>
</div>
<div className='remixui_fileExplorerTree'>
<div>
<div className='h-100 remixui_fileExplorerTree'>
<div className='h-100'>
<div className='pl-2 remixui_treeview' data-id='filePanelFileExplorerTree'>
{ (global.fs.mode === 'browser') && (currentWorkspace !== NO_WORKSPACE) &&
<FileExplorer

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-url-resolver",
"version": "0.0.34",
"version": "0.0.35",
"description": "Solidity import url resolver engine",
"main": "src/index.js",
"types": "src/index.d.ts",
@ -41,5 +41,5 @@
"typescript": "^3.1.6"
},
"typings": "src/index.d.ts",
"gitHead": "2be5108c51f2ac9226e4598c512ec6d3c63f5c78"
"gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
}

@ -1,23 +1,29 @@
{
"name": "@remix-project/remix-ws-templates",
"version": "1.0.0",
"description": "Create a Remix IDE workspace using different templates",
"main": "src/index.js",
"types": "src/index.d.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ethereum/remix-project.git"
},
"author": "Aniket-Engg",
"license": "MIT",
"bugs": {
"url": "https://github.com/ethereum/remix-project/issues"
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-ws-templates#readme"
}
"name": "@remix-project/remix-ws-templates",
"version": "1.0.1",
"description": "Create a Remix IDE workspace using different templates",
"main": "src/index.js",
"types": "src/index.d.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ethereum/remix-project.git"
},
"author": "Aniket-Engg",
"license": "MIT",
"bugs": {
"url": "https://github.com/ethereum/remix-project/issues"
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-ws-templates#readme",
"typings": "./src/index.d.ts",
"dependencies": {
"ethers": "^5.4.2",
"web3": "^1.5.1"
},
"gitHead": "3f311aaf25f5796f70711006bb783ee4087ffc71"
}

@ -1,24 +1,27 @@
import { ethers } from 'ethers'
export const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<any> => {
/**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {Number} accountIndex account index from the exposed account
* @return {Contract} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {
console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated
const artifactsPath = `browser/contracts/artifacts/${contractName}.json`
const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
// 'web3Provider' is a remix global variable object
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer);
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex)
let contract
if (from) {
contract = await factory.connect(from).deploy(...args);
} else {
contract = await factory.deploy(...args);
}
const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
const contract = await factory.deploy(...args)
// The contract is NOT deployed yet; we must wait until it is mined
await contract.deployed()

@ -1,8 +1,17 @@
import Web3 from 'web3'
import { Contract, ContractSendMethod, Options } from 'web3-eth-contract'
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<any> => {
/**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {string} from account used to send the transaction
* @param {number} gas gas limit
* @return {Options} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {
const web3 = new Web3(window.web3Provider)
const web3 = new Web3(web3Provider)
console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated
@ -12,14 +21,14 @@ export const deploy = async (contractName: string, args: Array<any>, from?: stri
const accounts = await web3.eth.getAccounts()
let contract = new web3.eth.Contract(metadata.abi)
const contract: Contract = new web3.eth.Contract(metadata.abi)
contract = contract.deploy({
const contractSend: ContractSendMethod = contract.deploy({
data: metadata.data.bytecode.object,
arguments: args
})
const newContractInstance = await contract.send({
const newContractInstance = await contractSend.send({
from: from || accounts[0],
gas: gas || 1500000
})

@ -1,24 +1,27 @@
import { ethers } from 'ethers'
export const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<any> => {
/**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {Number} accountIndex account index from the exposed account
* @return {Contract} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {
console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated
const artifactsPath = `browser/contracts/artifacts/${contractName}.json`
const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
// 'web3Provider' is a remix global variable object
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer);
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex)
let contract
if (from) {
contract = await factory.connect(from).deploy(...args);
} else {
contract = await factory.deploy(...args);
}
const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
const contract = await factory.deploy(...args)
// The contract is NOT deployed yet; we must wait until it is mined
await contract.deployed()

@ -1,8 +1,17 @@
import Web3 from 'web3'
import { Contract, ContractSendMethod, Options } from 'web3-eth-contract'
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<any> => {
/**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {string} from account used to send the transaction
* @param {number} gas gas limit
* @return {Options} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {
const web3 = new Web3(window.web3Provider)
const web3 = new Web3(web3Provider)
console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated
@ -12,14 +21,14 @@ export const deploy = async (contractName: string, args: Array<any>, from?: stri
const accounts = await web3.eth.getAccounts()
let contract = new web3.eth.Contract(metadata.abi)
const contract: Contract = new web3.eth.Contract(metadata.abi)
contract = contract.deploy({
const contractSend: ContractSendMethod = contract.deploy({
data: metadata.data.bytecode.object,
arguments: args
})
const newContractInstance = await contract.send({
const newContractInstance = await contractSend.send({
from: from || accounts[0],
gas: gas || 1500000
})

@ -1,6 +1,13 @@
import { ethers } from 'ethers'
export const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<any> => {
/**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {Number} accountIndex account index from the exposed account
* @return {Contract} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {
console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact.
@ -9,16 +16,12 @@ export const deploy = async (contractName: string, args: Array<any>, from?: stri
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
// 'web3Provider' is a remix global variable object
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer);
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex)
let contract
if (from) {
contract = await factory.connect(from).deploy(...args);
} else {
contract = await factory.deploy(...args);
}
const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
const contract = await factory.deploy(...args)
// The contract is NOT deployed yet; we must wait until it is mined
await contract.deployed()

@ -1,25 +1,34 @@
import Web3 from 'web3'
import { Contract, ContractSendMethod, Options } from 'web3-eth-contract'
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<any> => {
/**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {string} from account used to send the transaction
* @param {number} gas gas limit
* @return {Options} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {
const web3 = new Web3(window.web3Provider)
const web3 = new Web3(web3Provider)
console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated
const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path
const artifactsPath = `browser/contracts/artifacts/${contractName}.json`
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
const accounts = await web3.eth.getAccounts()
let contract = new web3.eth.Contract(metadata.abi)
const contract: Contract = new web3.eth.Contract(metadata.abi)
contract = contract.deploy({
const contractSend: ContractSendMethod = contract.deploy({
data: metadata.data.bytecode.object,
arguments: args
})
const newContractInstance = await contract.send({
const newContractInstance = await contractSend.send({
from: from || accounts[0],
gas: gas || 1500000
})

@ -1,24 +1,27 @@
import { ethers } from 'ethers'
export const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<any> => {
/**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {Number} accountIndex account index from the exposed account
* @return {Contract} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {
console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated
const artifactsPath = `browser/contracts/artifacts/${contractName}.json`
const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
// 'web3Provider' is a remix global variable object
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner()
const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer);
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex)
let contract
if (from) {
contract = await factory.connect(from).deploy(...args);
} else {
contract = await factory.deploy(...args);
}
const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
const contract = await factory.deploy(...args)
// The contract is NOT deployed yet; we must wait until it is mined
await contract.deployed()

@ -1,8 +1,17 @@
import Web3 from 'web3'
import { Contract, ContractSendMethod, Options } from 'web3-eth-contract'
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<any> => {
/**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {string} from account used to send the transaction
* @param {number} gas gas limit
* @return {Options} deployed contract
*/
export const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Options> => {
const web3 = new Web3(window.web3Provider)
const web3 = new Web3(web3Provider)
console.log(`deploying ${contractName}`)
// Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated
@ -12,14 +21,14 @@ export const deploy = async (contractName: string, args: Array<any>, from?: stri
const accounts = await web3.eth.getAccounts()
let contract = new web3.eth.Contract(metadata.abi)
const contract: Contract = new web3.eth.Contract(metadata.abi)
contract = contract.deploy({
const contractSend: ContractSendMethod = contract.deploy({
data: metadata.data.bytecode.object,
arguments: args
})
const newContractInstance = await contract.send({
const newContractInstance = await contractSend.send({
from: from || accounts[0],
gas: gas || 1500000
})

@ -1,6 +1,6 @@
{
"name": "@remix-project/remixd",
"version": "0.6.1",
"version": "0.6.2",
"description": "remix server: allow accessing file system from remix.ethereum.org and start a dev environment (see help section)",
"main": "index.js",
"types": "./index.d.ts",

@ -1,6 +1,6 @@
{
"name": "remix-project",
"version": "0.24.0-dev",
"version": "0.25.0-dev",
"license": "MIT",
"description": "Ethereum Remix Monorepo",
"keywords": [
@ -60,7 +60,7 @@
"minify": "uglifyjs --in-source-map inline --source-map-inline -c warnings=false",
"build:production": "NODE_ENV=production nx build remix-ide --skip-nx-cache",
"serve:production": "npx http-server ./dist/apps/remix-ide",
"select_test": "sh apps/remix-ide-e2e/src/select_tests.sh",
"select_test": "bash apps/remix-ide-e2e/src/select_tests.sh",
"group_test": "yarn run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/${npm_config_test}_group${npm_config_group}.test.js --env=${npm_config_env}",
"nightwatch_parallel": "yarn run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js --env=chrome,firefox",
"nightwatch_local_firefox": "yarn run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js --env=firefox",
@ -198,6 +198,7 @@
"react-draggable": "^4.4.4",
"react-tabs": "^3.2.2",
"regenerator-runtime": "0.13.7",
"rss-parser": "^3.12.0",
"selenium": "^2.20.0",
"signale": "^1.4.0",
"string-similarity": "^4.0.4",

Loading…
Cancel
Save