Merge branch 'master' into pastedCodeSafety

pull/5344/head
STetsing 2 months ago committed by GitHub
commit b3d0a02c09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 14
      .circleci/config.yml
  2. 4
      .github/workflows/pr-reminder.yml
  3. 4
      .gitignore
  4. 5
      apps/circuit-compiler/src/app/actions/index.ts
  5. 29
      apps/circuit-compiler/src/app/app.tsx
  6. 2
      apps/circuit-compiler/src/app/components/configurations.tsx
  7. 2
      apps/circuit-compiler/src/app/components/container.tsx
  8. 2
      apps/circuit-compiler/src/app/components/generateProof.tsx
  9. 112
      apps/circuit-compiler/src/app/components/versions.tsx
  10. 23
      apps/circuit-compiler/src/app/reducers/state.ts
  11. 260
      apps/circuit-compiler/src/app/services/circomPluginClient.ts
  12. 3
      apps/circuit-compiler/src/app/types/index.ts
  13. 15
      apps/circuit-compiler/src/css/app.css
  14. 57
      apps/contract-verification/src/app/ContractVerificationPluginClient.ts
  15. 18
      apps/contract-verification/src/app/views/LookupView.tsx
  16. 2
      apps/remix-dapp/src/locales/en/terminal.json
  17. 18
      apps/remix-ide-e2e/src/tests/circom.test.ts
  18. 7
      apps/remix-ide-e2e/src/tests/metamask.test.ts
  19. 5
      apps/remix-ide-e2e/src/tests/quickDapp.test.ts
  20. 4
      apps/remix-ide-e2e/src/tests/runAndDeploy_injected.test.ts
  21. 6
      apps/remix-ide/src/app.js
  22. 12
      apps/remix-ide/src/app/plugins/electron/circomElectronPlugin.ts
  23. 15
      apps/remix-ide/src/app/providers/injected-provider.tsx
  24. 2
      apps/remix-ide/src/app/tabs/locale-module.js
  25. 2
      apps/remix-ide/src/remixAppManager.js
  26. 1
      apps/remix-ide/src/remixEngine.js
  27. 10
      apps/remix-ide/team-best-practices.md
  28. 3
      apps/remixdesktop/src/engine.ts
  29. 96
      apps/remixdesktop/src/plugins/circomElectronBasePlugin.ts
  30. 2
      apps/remixdesktop/src/preload.ts
  31. 136
      apps/remixdesktop/src/tools/circom.ts
  32. 86
      apps/remixdesktop/test/tests/app/circom-compiler.test.ts
  33. 67
      apps/remixdesktop/test/tests/app/circom-script.test.ts
  34. 2
      libs/remix-ws-templates/src/templates/hashchecker/scripts/groth16/groth16_zkproof.ts
  35. 2
      libs/remix-ws-templates/src/templates/hashchecker/scripts/plonk/plonk_zkproof.ts
  36. 2
      libs/remix-ws-templates/src/templates/rln/scripts/groth16/groth16_zkproof.ts
  37. 2
      libs/remix-ws-templates/src/templates/rln/scripts/plonk/plonk_zkproof.ts
  38. 4
      libs/remix-ws-templates/src/templates/semaphore/scripts/groth16/groth16_zkproof.ts
  39. 4
      libs/remix-ws-templates/src/templates/semaphore/scripts/plonk/plonk_zkproof.ts

@ -7,6 +7,7 @@ parameters:
orbs:
browser-tools: circleci/browser-tools@1.4.4
win: circleci/windows@5.0
node: circleci/node@7.0.0
jobs:
build:
docker:
@ -107,23 +108,25 @@ jobs:
test-remixdesktop-linux:
machine:
image: ubuntu-2004:current
image: ubuntu-2204:current
resource_class:
xlarge
working_directory: ~/remix-project
parallelism: 15
steps:
- run: ldd --version
- checkout
- attach_workspace:
at: .
- run: unzip ./persist/desktopbuild.zip
- node/install:
install-yarn: true
node-version: '20.2'
- run:
command: |
nvm install 20.2
nvm use 20.2
node -v
npm install --global yarn node-gyp
yarn global add node-gyp@10.2.0
python -m pip install --upgrade pip
pip install setuptools
mkdir apps/remixdesktop/build
@ -136,13 +139,11 @@ jobs:
- run:
name: "Run tests"
command: |
nvm use 20.2
cd apps/remixdesktop/
./run_ci_test.sh
- run:
name: "Run isogit tests"
command: |
nvm use 20.2
cd apps/remixdesktop/
./run_git_ui_isogit_tests.sh
- store_test_results:
@ -521,6 +522,7 @@ jobs:
ls -la dist/apps/remix-ide
nvm install 20.2
nvm use 20.2
/usr/sbin/softwareupdate --install-rosetta --agree-to-license
- restore_cache:
keys:
- remixdesktop-deps-mac-{{ checksum "apps/remixdesktop/yarn.lock" }}

@ -3,7 +3,7 @@ name: PRs reviews reminder
on:
schedule:
- cron: "0 8 * * 1-5"
- cron: '58 9 * * 1-5'
- cron: '55 9 * * 1-5'
workflow_dispatch:
jobs:
@ -19,7 +19,7 @@ jobs:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
freeze-date: '2024-12-23T18:00:00Z'
- name: Reminder for standup
if: github.event.schedule == '58 9 * * 1-5'
if: github.event.schedule == '55 9 * * 1-5'
uses: Aniket-Engg/pr-reviews-reminder-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

4
.gitignore vendored

@ -68,4 +68,8 @@ apps/remix-ide/src/assets/esbuild.wasm
apps/remixdesktop/build*
apps/remixdesktop/reports
apps/remixdesktop/logs/
apps/remixdesktop/bin/
apps/remixdesktop/circom-download
apps/remixdesktop/log_input_signals.txt
apps/remixdesktop/log_input_signals_new.txt
logs

@ -3,6 +3,7 @@ import type { CircomPluginClient } from "../services/circomPluginClient"
import { ActionPayloadTypes, AppState, ICircuitAppContext } from "../types"
import { GROTH16_VERIFIER, PLONK_VERIFIER } from './constant'
import { extractNameFromKey, extractParentFromKey } from '@remix-ui/helper'
import isElectron from 'is-electron'
export const compileCircuit = async (plugin: CircomPluginClient, appState: AppState) => {
try {
@ -28,7 +29,7 @@ export const computeWitness = async (plugin: CircomPluginClient, appState: AppSt
const wtns = await snarkjs.wtns.exportJson(witness)
const wtnsJson = wtns.map(wtn => wtn.toString())
const fileName = extractNameFromKey(appState.filePath)
const writePath = extractParentFromKey(appState.filePath) + `/.bin/${fileName.replace('.circom', '.wtn.json')}`
const writePath = extractParentFromKey(appState.filePath) + `/.bin/${fileName.replace('.circom', '_js')}/${fileName.replace('.circom', '.wtn.json')}`
await plugin.call('fileManager', 'writeFile', writePath, JSON.stringify(wtnsJson, null, 2))
plugin._paq.push(['trackEvent', 'circuit-compiler', 'computeWitness', 'wtns.exportJson', writePath])
@ -116,7 +117,7 @@ export const generateProof = async (plugin: CircomPluginClient, appState: AppSta
const r1csBuffer = await plugin.call('fileManager', 'readFile', r1csPath, { encoding: null })
// @ts-ignore
const r1cs = new Uint8Array(r1csBuffer)
const wtnsPath = r1csPath.replace('.r1cs', '.wtn')
const wtnsPath = isElectron() ? extractParentFromKey(appState.filePath) + "/.bin/" + fileName.replace('.circom', '_js') + "/" + fileName.replace('.circom', '.wtn') : r1csPath.replace('.r1cs', '.wtn')
// @ts-ignore
const wtnsBuffer = await plugin.call('fileManager', 'readFile', wtnsPath, { encoding: null })
// @ts-ignore

@ -21,6 +21,11 @@ function App() {
useEffect(() => {
plugin.internalEvents.on('circom_activated', () => {
(async () => {
const downloadList = await plugin.getCompilerDownloadList()
dispatch({ type: 'SET_VERSION_DOWNLOAD_LIST', payload: downloadList })
})();
// @ts-ignore
plugin.on('locale', 'localeChanged', (locale: any) => {
setLocale(locale)
@ -47,6 +52,7 @@ function App() {
signalInputs = (signalInputs || []).filter(input => input)
dispatch({ type: 'SET_SIGNAL_INPUTS', payload: signalInputs })
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: null })
})
plugin.internalEvents.on('circuit_compiling_errored', compilerErrored)
@ -56,7 +62,16 @@ function App() {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' })
dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: null })
})
plugin.internalEvents.on('circuit_computing_witness_errored', compilerErrored)
plugin.internalEvents.on('circuit_computing_witness_errored', (err) => {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' })
try {
const report = JSON.parse(err.message)
dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: report })
} catch (e) {
dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: err.message })
}
})
// parsing events
plugin.internalEvents.on('circuit_parsing_done', (_, filePathToId) => {
@ -73,6 +88,13 @@ function App() {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'warning' })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: report })
})
plugin.internalEvents.on('download_success', (version) => {
dispatch({ type: 'REMOVE_VERSION_FROM_DOWNLOAD_LIST', payload: version })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: null })
})
plugin.internalEvents.on('download_failed', (error) => {
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: 'Download failed! Please check your internet connection. ' + error.message })
})
}, [])
useEffect(() => {
@ -120,9 +142,10 @@ function App() {
try {
const report = JSON.parse(err.message)
dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: report })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: report })
} catch (e) {
dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: err.message })
if (process.platform === 'win32' && err.message.includes('3221225781')) return dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: 'The compiler failed to start because of some missing dependencies. Please install or repair the Microsoft Visual C++ Redistributable package to resolve this issue.' })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: err.message })
}
}

@ -52,7 +52,7 @@ export function Configurations ({primeValue, setPrimeValue, versionValue}: Confi
<option value="vesta">vesta</option>
</>
</RenderIf>
<RenderIf condition={versionValue === '2.1.8'}>
<RenderIf condition={versionValue === '2.1.8' || versionValue === 'latest'}>
<>
<option value="bn128">bn128</option>
<option value="bls12381">bls12381</option>

@ -118,7 +118,7 @@ export function Container () {
>
<span className="far fa-file-certificate border-0 p-0 ml-2" onClick={() => showCompilerLicense()}></span>
</CustomTooltip>
<VersionList setVersion={handleVersionSelect} versionList={circuitApp.appState.versionList} currentVersion={circuitApp.appState.version} />
<VersionList setVersion={handleVersionSelect} versionList={circuitApp.appState.versionList} currentVersion={circuitApp.appState.version} downloadList={circuitApp.appState.versionDownloadList} />
<CompileOptions setCircuitAutoCompile={handleCircuitAutoCompile} setCircuitHideWarnings={handleCircuitHideWarnings} autoCompile={circuitApp.appState.autoCompile} hideWarnings={circuitApp.appState.hideWarnings} />
<Toggler title='circuit.advancedConfigurations' dataId=''>
<Configurations setPrimeValue={handlePrimeChange} primeValue={circuitApp.appState.primeValue} versionValue={circuitApp.appState.version} />

@ -27,7 +27,7 @@ export function GenerateProof () {
className="btn btn-secondary btn-block d-block w-100 text-break mb-1 mt-1"
onClick={() => generateProof(circuitApp.plugin, circuitApp.appState, circuitApp.dispatch)}
disabled={(status === "compiling") || (status === "computing") || (status === "proving") || (status === "exporting")}
data-id="compute_witness_btn"
data-id="generateProofBtn"
>
<RenderIf condition={status === 'proving'}>
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i>

@ -1,26 +1,96 @@
import { RenderIf } from "@remix-ui/helper";
import { AppState } from "../types";
import { AppState } from "../types"
import { Dropdown } from "react-bootstrap"
import React, { Ref } from "react"
import isElectron from 'is-electron'
export function VersionList ({ currentVersion, versionList, setVersion }: { versionList: AppState['versionList'], currentVersion: string, setVersion: (version: string) => void }) {
export function VersionList ({ currentVersion, versionList, downloadList, setVersion }: { versionList: AppState['versionList'], currentVersion: string, setVersion: (version: string) => void , downloadList: string[]}) {
const versionListKeys = Object.keys(versionList)
return (
<select
value={currentVersion}
onChange={(e) => setVersion(e.target.value)}
className="custom-select"
>
<RenderIf condition={versionListKeys.length > 0}>
<>
{
versionListKeys.map((version, index) => (
<option value={version} key={index}>
{ versionList[version].name }
</option>
))
}
</>
</RenderIf>
</select>
<Dropdown>
<Dropdown.Toggle as={CircomVersionMenuToggle} id="circomVersionList" className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control">
<div style={{ flexGrow: 1, overflow: 'hidden', display:'flex', justifyContent:'left' }}>
{ versionList[currentVersion].name }
</div>
</Dropdown.Toggle>
<Dropdown.Menu as={CircomVersionMenu} className="w-100 custom-dropdown-items overflow-hidden">
{
versionListKeys.reverse().map((version, index) => (
<Dropdown.Item key={index} onClick={() => {
setVersion(version)
}}>
<div className='d-flex w-100 justify-content-between'>
<div>
<span className={`fas fa-check text-success mr-2 ${currentVersion === version ? 'visible' : 'invisible'}`}></span>
<span>
{ isElectron() ? versionList[version].name.replace('wasm', '') : versionList[version].name }
</span>
</div>
{ isElectron() ? downloadList.includes(version) ? <div className='far fa-arrow-circle-down'></div> : <div className='fas fa-arrow-circle-down text-success ml-auto'></div> : null }
</div>
</Dropdown.Item>
))
}
</Dropdown.Menu>
</Dropdown>
)
}
const CircomVersionMenuToggle = React.forwardRef(
(
{
children,
onClick,
className = ''
}: {
children: React.ReactNode
onClick: (e) => void
className: string
},
ref: Ref<HTMLButtonElement>
) => (
<button
ref={ref}
onClick={(e) => {
e.preventDefault()
onClick(e)
}}
className={className.replace('dropdown-toggle', '')}
>
<div className="d-flex">
{children}
<div>
<i className="fad fa-sort-circle"></i>
</div>
</div>
</button>
)
)
const CircomVersionMenu = React.forwardRef(
(
{
children,
style,
'data-id': dataId,
className,
'aria-labelledby': labeledBy
}: {
'children': React.ReactNode
'style'?: React.CSSProperties
'data-id'?: string
'className': string
'aria-labelledby'?: string
},
ref: Ref<HTMLDivElement>
) => {
const height = window.innerHeight * 0.6
return (
<div ref={ref} style={style} className={className} aria-labelledby={labeledBy} data-id={dataId}>
<ul className="list-unstyled mb-0" style={{ maxHeight: height + 'px',overflowY:'auto' }}>
{children}
</ul>
</div>
)
}
)

@ -1,10 +1,21 @@
import { PTAU_LIST } from '../actions/constant'
import { Actions, AppState } from '../types'
import { compiler_list } from 'circom_wasm'
import isElectron from 'is-electron'
const VersionList = isElectron() ? { ...compiler_list.wasm_builds,
"latest": {
"name": "latest",
"version": "latest",
"repo": "",
"build_source": ""
}
} : compiler_list.wasm_builds
export const appInitialState: AppState = {
version: compiler_list.latest,
versionList: compiler_list.wasm_builds,
versionDownloadList: [],
filePath: "",
filePathToId: {},
status: "idle",
@ -157,6 +168,18 @@ export const appReducer = (state = appInitialState, action: Actions): AppState =
zKey: action.payload
}
case 'SET_VERSION_DOWNLOAD_LIST':
return {
...state,
versionDownloadList: action.payload
}
case 'REMOVE_VERSION_FROM_DOWNLOAD_LIST':
return {
...state,
versionDownloadList: state.versionDownloadList.filter(version => version !== action.payload)
}
default:
throw new Error()
}

@ -8,12 +8,13 @@ import * as compilerV217 from 'circom_wasm/v2.1.7'
import * as compilerV216 from 'circom_wasm/v2.1.6'
import * as compilerV215 from 'circom_wasm/v2.1.5'
import { extractNameFromKey, extractParentFromKey } from '@remix-ui/helper'
import { CompilationConfig, CompilerReport, PrimeValue, ResolverOutput } from '../types'
import { CompilationConfig, CompilerReport, PrimeValue } from '../types'
import isElectron from 'is-electron'
export class CircomPluginClient extends PluginClient {
public internalEvents: EventManager
private _compilationConfig: CompilationConfig = {
version: "2.1.8",
version: compiler_list.latest,
prime: "bn128"
}
private lastCompiledCircuitPath: string = ''
@ -43,13 +44,23 @@ export class CircomPluginClient extends PluginClient {
}
set compilerVersion (version: string) {
if (!compiler_list.versions.includes(version)) throw new Error("Unsupported compiler version")
if (!compiler_list.versions.includes(version) && version !== "latest") throw new Error("Unsupported compiler version")
this._compilationConfig.version = version
if (version === '2.1.5') this.compiler = compilerV215
else if (version === '2.1.6') this.compiler = compilerV216
else if (version === '2.1.7') this.compiler = compilerV217
else if (version === '2.1.8') this.compiler = compilerV218
else this.compiler = null
if (isElectron()) {
const versionToInstall = version === 'latest' ? 'latest' : `v${version}`
// @ts-ignore
this.call('circom', 'install', versionToInstall).then(() => {
this.internalEvents.emit('download_success', version)
}).catch((e) => {
this.internalEvents.emit('download_failed', e)
})
} else {
if (version === '2.1.5') this.compiler = compilerV215
else if (version === '2.1.6') this.compiler = compilerV216
else if (version === '2.1.7') this.compiler = compilerV217
else if (version === '2.1.8') this.compiler = compilerV218
else this.compiler = null
}
}
set compilerPrime (prime: PrimeValue) {
@ -135,6 +146,75 @@ export class CircomPluginClient extends PluginClient {
}
async compile(path: string, compilationConfig?: CompilationConfig): Promise<void> {
if (isElectron()) {
await this.compileElectron(path)
} else {
this.internalEvents.emit('circuit_compiling_start')
this.emit('statusChanged', { key: 'loading', title: 'Compiling...', type: 'info' })
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: 'Compiling ' + path })
const [parseErrors, filePathToId] = await this.parse(path)
if (parseErrors && (parseErrors.length > 0)) {
if (parseErrors[0].type === 'Error') {
this.internalEvents.emit('circuit_parsing_errored', parseErrors, filePathToId)
this.logCompilerReport(parseErrors)
return
}
}
if (compilationConfig) {
const { prime, version } = compilationConfig
this.compilerVersion = version
this.compilerPrime = prime
}
const circuitApi = this.compiler ? this.compiler.compile(path, this.lastParsedFiles, { prime: this._compilationConfig.prime }) : compile(path, this.lastParsedFiles, { prime: this._compilationConfig.prime })
const circuitProgram = circuitApi.program()
if (circuitProgram.length < 1) {
const circuitErrors = circuitApi.report()
this.logCompilerReport(circuitErrors)
this._paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation failed'])
throw new Error(circuitErrors)
} else {
this.lastCompiledFile = path
const fileName = extractNameFromKey(path)
this.lastCompiledCircuitPath = extractParentFromKey(path) + "/.bin/" + fileName.replace('.circom', '_js') + '/' + fileName.replace('.circom', '.wasm')
// @ts-ignore
await this.call('fileManager', 'writeFile', this.lastCompiledCircuitPath, circuitProgram, { encoding: null })
const fileContent = this.lastParsedFiles[path]
const searchComponentName = fileContent.match(/component\s+main\s*(?:{[^{}]*})?\s*=\s*([A-Za-z_]\w*)\s*\(.*\)/)
if (searchComponentName) {
const componentName = searchComponentName[1]
const signals = circuitApi.input_signals(componentName)
this.internalEvents.emit('circuit_compiling_done', signals)
} else {
this.internalEvents.emit('circuit_compiling_done', [])
}
if (parseErrors && (parseErrors.length > 0)) {
if (parseErrors[0].type === 'Warning') {
this.internalEvents.emit('circuit_parsing_warning', parseErrors, filePathToId)
this.logCompilerReport(parseErrors)
}
} else {
this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId)
this.emit('statusChanged', { key: 'succeed', title: 'circuit compiled successfully', type: 'success' })
}
this._paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation successful'])
circuitApi.log().map(log => {
log && this.call('terminal', 'log', { type: 'log', value: log })
})
// @ts-ignore
this.call('terminal', 'log', { type: 'typewritersuccess', value: 'Everything went okay' })
}
}
}
async compileElectron (path: string): Promise<void> {
this.internalEvents.emit('circuit_compiling_start')
this.emit('statusChanged', { key: 'loading', title: 'Compiling...', type: 'info' })
// @ts-ignore
@ -146,7 +226,30 @@ export class CircomPluginClient extends PluginClient {
this.internalEvents.emit('circuit_parsing_errored', parseErrors, filePathToId)
this.logCompilerReport(parseErrors)
return
} else if (parseErrors[0].type === 'Warning') {
}
}
const { version, prime } = this._compilationConfig
const versionToInstall = version === 'latest' ? 'latest' : `v${version}`
try {
// @ts-ignore
const { stdout, stderr } = await this.call('circom', 'run', path, versionToInstall, { prime: prime, wasm: "", inputs: "" })
const fileName = extractNameFromKey(path)
this.lastCompiledCircuitPath = pathModule.normalize(extractParentFromKey(path) + "/.bin/" + fileName.replace('.circom', '_js') + "/" + fileName.replace('.circom', '.wasm'))
if (stderr) this.call('terminal', 'log', { type: 'error', value: stderr })
if (stdout) this.call('terminal', 'log', { type: 'log', value: stdout })
} catch (error) {
this.call('terminal', 'log', { type: 'error', value: error.message })
this.internalEvents.emit('circuit_compiling_errored', error)
this.emit('statusChanged', { key: 'errored', title: 'Compilation failed', type: 'error' })
return
}
// @ts-ignore
const signals = await this.call('circom', 'getInputs')
this.internalEvents.emit('circuit_compiling_done', signals)
if (parseErrors && (parseErrors.length > 0)) {
if (parseErrors[0].type === 'Warning') {
this.internalEvents.emit('circuit_parsing_warning', parseErrors, filePathToId)
this.logCompilerReport(parseErrors)
}
@ -154,49 +257,58 @@ export class CircomPluginClient extends PluginClient {
this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId)
this.emit('statusChanged', { key: 'succeed', title: 'circuit compiled successfully', type: 'success' })
}
if (compilationConfig) {
const { prime, version } = compilationConfig
this.compilerVersion = version
this.compilerPrime = prime
}
const circuitApi = this.compiler ? this.compiler.compile(path, this.lastParsedFiles, { prime: this._compilationConfig.prime }) : compile(path, this.lastParsedFiles, { prime: this._compilationConfig.prime })
const circuitProgram = circuitApi.program()
if (circuitProgram.length < 1) {
const circuitErrors = circuitApi.report()
}
this.logCompilerReport(circuitErrors)
this._paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation failed'])
throw new Error(circuitErrors)
async generateR1cs (path: string, compilationConfig?: CompilationConfig): Promise<void> {
if (isElectron()) {
await this.generateR1csElectron(path)
} else {
this.lastCompiledFile = path
const fileName = extractNameFromKey(path)
const [parseErrors, filePathToId] = await this.parse(path)
if (parseErrors && (parseErrors.length > 0)) {
if (parseErrors[0].type === 'Error') {
this.logCompilerReport(parseErrors)
return
} else if (parseErrors[0].type === 'Warning') {
this.logCompilerReport(parseErrors)
}
}
if (compilationConfig) {
const { prime, version } = compilationConfig
this.lastCompiledCircuitPath = extractParentFromKey(path) + "/.bin/" + fileName.replace('circom', 'wasm')
// @ts-ignore
await this.call('fileManager', 'writeFile', this.lastCompiledCircuitPath, circuitProgram, { encoding: null })
const fileContent = this.lastParsedFiles[path]
const searchComponentName = fileContent.match(/component\s+main\s*(?:{[^{}]*})?\s*=\s*([A-Za-z_]\w*)\s*\(.*\)/)
this.compilerVersion = version
this.compilerPrime = prime
}
const r1csApi = this.compiler ? this.compiler.generate_r1cs(path, this.lastParsedFiles, { prime: this._compilationConfig.prime }) : generate_r1cs(path, this.lastParsedFiles, { prime: this._compilationConfig.prime })
const r1csProgram = r1csApi.program()
if (searchComponentName) {
const componentName = searchComponentName[1]
const signals = circuitApi.input_signals(componentName)
if (r1csProgram.length < 1) {
const r1csErrors = r1csApi.report()
this.internalEvents.emit('circuit_compiling_done', signals)
this.logCompilerReport(r1csErrors)
this._paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation failed'])
throw new Error(r1csErrors)
} else {
this.internalEvents.emit('circuit_compiling_done', [])
const fileName = extractNameFromKey(path)
const writePath = extractParentFromKey(path) + "/.bin/" + fileName.replace('.circom', '.r1cs')
// @ts-ignore
await this.call('fileManager', 'writeFile', writePath, r1csProgram, true)
this._paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation successful'])
r1csApi.log().map(log => {
log && this.call('terminal', 'log', { type: 'log', value: log })
})
// @ts-ignore
this.call('terminal', 'log', { type: 'typewritersuccess', value: 'Everything went okay' })
}
this._paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation successful'])
circuitApi.log().map(log => {
log && this.call('terminal', 'log', { type: 'log', value: log })
})
// @ts-ignore
this.call('terminal', 'log', { type: 'typewritersuccess', value: 'Everything went okay' })
}
}
async generateR1cs (path: string, compilationConfig?: CompilationConfig): Promise<void> {
async generateR1csElectron (path: string): Promise<void> {
this.internalEvents.emit('circuit_generating_r1cs_start')
this.emit('statusChanged', { key: 'loading', title: 'Generating...', type: 'info' })
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: 'Generating R1CS for ' + path })
const [parseErrors, filePathToId] = await this.parse(path)
if (parseErrors && (parseErrors.length > 0)) {
@ -207,34 +319,15 @@ export class CircomPluginClient extends PluginClient {
this.logCompilerReport(parseErrors)
}
}
if (compilationConfig) {
const { prime, version } = compilationConfig
this.compilerVersion = version
this.compilerPrime = prime
}
const r1csApi = this.compiler ? this.compiler.generate_r1cs(path, this.lastParsedFiles, { prime: this._compilationConfig.prime }) : generate_r1cs(path, this.lastParsedFiles, { prime: this._compilationConfig.prime })
const r1csProgram = r1csApi.program()
if (r1csProgram.length < 1) {
const r1csErrors = r1csApi.report()
this.logCompilerReport(r1csErrors)
this._paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation failed'])
throw new Error(r1csErrors)
} else {
const fileName = extractNameFromKey(path)
const writePath = extractParentFromKey(path) + "/.bin/" + fileName.replace('circom', 'r1cs')
// @ts-ignore
await this.call('fileManager', 'writeFile', writePath, r1csProgram, true)
this._paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation successful'])
r1csApi.log().map(log => {
log && this.call('terminal', 'log', { type: 'log', value: log })
})
// @ts-ignore
this.call('terminal', 'log', { type: 'typewritersuccess', value: 'Everything went okay' })
}
const { version, prime } = this._compilationConfig
const versionToInstall = version === 'latest' ? 'latest' : `v${version}`
// @ts-ignore
await this.call('circom', 'run', path, versionToInstall, {
prime: prime,
r1cs: ""
})
this.internalEvents.emit('circuit_generating_r1cs_done')
this.emit('statusChanged', { key: 'succeed', title: 'r1cs generated successfully', type: 'success' })
}
async computeWitness (input: string): Promise<Uint8Array> {
@ -248,7 +341,7 @@ export class CircomPluginClient extends PluginClient {
const dataRead = new Uint8Array(buffer)
const witness = this.compiler ? await this.compiler.generate_witness(dataRead, input) : await generate_witness(dataRead, input)
// @ts-ignore
await this.call('fileManager', 'writeFile', wasmPath.replace('.wasm', '.wtn'), witness, true)
await this.call('fileManager', 'writeFile', wasmPath.replace('.wasm', '.wtn'), witness, { encoding: null })
this._paq.push(['trackEvent', 'circuit-compiler', 'computeWitness', 'compiler.generate_witness', wasmPath.replace('.wasm', '.wtn')])
this.internalEvents.emit('circuit_computing_witness_done')
this.emit('statusChanged', { key: 'succeed', title: 'witness computed successfully', type: 'success' })
@ -298,7 +391,7 @@ export class CircomPluginClient extends PluginClient {
path = `.deps/https/raw.githubusercontent.com/iden3/circomlib/${version[0]}/${splitInclude.slice(2).join('/')}`
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
} else {
path = `.deps/https/raw.githubusercontent.com/iden3/circomlib/master/${splitInclude.slice(1).join('/')}`
path = `.deps/https/raw.githubusercontent.com/iden3/circomlib/${splitInclude.slice(1).join('/')}`
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
}
} catch (e) {
@ -308,7 +401,15 @@ export class CircomPluginClient extends PluginClient {
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
} else {
path = `https://raw.githubusercontent.com/iden3/circomlib/master/${splitInclude.slice(1).join('/')}`
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
const { content } = await this.call('contentImport', 'resolve', path)
if (content) {
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
const savePath = `.deps/https/raw.githubusercontent.com/iden3/${include}`
// @ts-ignore
await this.call('fileManager', 'writeFile', savePath, dependencyContent)
}
}
}
} else {
@ -420,4 +521,17 @@ export class CircomPluginClient extends PluginClient {
this.emit('statusChanged', { key: report.length, title: `You have ${report.length} problem${report.length === 1 ? '' : 's'}`, type: 'warning' })
}
}
async getCompilerDownloadList () {
if (!isElectron()) return []
else {
return await Promise.all(compiler_list.versions.map(async (version) => {
const versionToInstall = version === 'latest' ? 'latest' : `v${version}`
// @ts-ignore
const exists = await this.call('circom', 'isVersionInstalled', versionToInstall)
if (!exists) return version
}))
}
}
}

@ -23,6 +23,8 @@ export interface ICircuitAppContext {
export interface ActionPayloadTypes {
SET_COMPILER_VERSION: string,
SET_VERSION_DOWNLOAD_LIST: string[],
REMOVE_VERSION_FROM_DOWNLOAD_LIST: string,
SET_FILE_PATH: string,
SET_COMPILER_STATUS: CompilerStatus,
SET_PRIME_VALUE: PrimeValue,
@ -54,6 +56,7 @@ export type Actions = {[A in keyof ActionPayloadTypes]: Action<A>}[keyof ActionP
export interface AppState {
version: string,
versionList: typeof compiler_list.wasm_builds,
versionDownloadList: string[],
filePath: string,
filePathToId: Record<string, string>,
status: CompilerStatus,

@ -84,3 +84,18 @@ body {
bottom: 0;
right: 0;
}
.custom-dropdown-items {
padding: 0.25rem 0.25rem;
border-radius: .25rem;
background: var(--custom-select);
}
.custom-dropdown-items a {
border-radius: .25rem;
text-transform: none;
text-decoration: none;
font-weight: normal;
font-size: 0.875rem;
padding: 0.25rem 0.25rem;
width: auto;
color: var(--text);
}

@ -1,12 +1,16 @@
import { PluginClient } from '@remixproject/plugin'
import { createClient } from '@remixproject/plugin-webview'
import EventManager from 'events'
import { VERIFIERS, type ChainSettings, type ContractVerificationSettings, type LookupResponse, type VerifierIdentifier } from './types'
import { mergeChainSettingsWithDefaults, validConfiguration } from './utils'
import { getVerifier } from './Verifiers'
export class ContractVerificationPluginClient extends PluginClient {
public internalEvents: EventManager
constructor() {
super()
this.methods = ['lookupAndSave']
this.internalEvents = new EventManager()
createClient(this)
this.onload()
@ -15,4 +19,57 @@ export class ContractVerificationPluginClient extends PluginClient {
onActivation(): void {
this.internalEvents.emit('verification_activated')
}
async lookupAndSave(verifierId: string, chainId: string, contractAddress: string): Promise<LookupResponse> {
const canonicalVerifierId = VERIFIERS.find((id) => id.toLowerCase() === verifierId.toLowerCase())
if (!canonicalVerifierId) {
console.error(`lookupAndSave failed: Unknown verifier: ${verifierId}`)
return
}
const userSettings = this.getUserSettingsFromLocalStorage()
const chainSettings = mergeChainSettingsWithDefaults(chainId, userSettings)
try {
const lookupResult = await this.lookup(canonicalVerifierId, chainSettings, chainId, contractAddress)
await this.saveToRemix(lookupResult)
return lookupResult
} catch (err) {
console.error(`lookupAndSave failed: ${err}`)
}
}
async lookup(verifierId: VerifierIdentifier, chainSettings: ChainSettings, chainId: string, contractAddress: string): Promise<LookupResponse> {
if (!validConfiguration(chainSettings, verifierId)) {
throw new Error(`Error during lookup: Invalid configuration given for verifier ${verifierId}`)
}
const verifier = getVerifier(verifierId, chainSettings.verifiers[verifierId])
return await verifier.lookup(contractAddress, chainId)
}
async saveToRemix(lookupResponse: LookupResponse): Promise<void> {
for (const source of lookupResponse.sourceFiles ?? []) {
try {
await this.call('fileManager', 'setFile', source.path, source.content)
} catch (err) {
throw new Error(`Error while creating file ${source.path}: ${err.message}`)
}
}
try {
await this.call('fileManager', 'open', lookupResponse.targetFilePath)
} catch (err) {
throw new Error(`Error focusing file ${lookupResponse.targetFilePath}: ${err.message}`)
}
}
private getUserSettingsFromLocalStorage(): ContractVerificationSettings {
const fallbackSettings = { chains: {} };
try {
const settings = window.localStorage.getItem("contract-verification:settings")
return settings ? JSON.parse(settings) : fallbackSettings
} catch (error) {
console.error(error)
return fallbackSettings
}
}
}

@ -5,7 +5,6 @@ import type { LookupResponse, VerifierIdentifier } from '../types'
import { VERIFIERS } from '../types'
import { AppContext } from '../AppContext'
import { CustomTooltip } from '@remix-ui/helper'
import { getVerifier } from '../Verifiers'
import { useNavigate } from 'react-router-dom'
import { VerifyFormContext } from '../VerifyFormContext'
import { useSourcifySupported } from '../hooks/useSourcifySupported'
@ -25,7 +24,7 @@ export const LookupView = () => {
const sourcifySupported = useSourcifySupported(selectedChain, chainSettings)
const noVerifierEnabled = VERIFIERS.every((verifierId) => !validConfiguration(chainSettings, verifierId) || (verifierId === 'Sourcify' && !sourcifySupported))
const submitDisabled = !!contractAddressError || !contractAddress || !selectedChain || noVerifierEnabled
const submitDisabled = !!contractAddressError || !contractAddress || !selectedChain || noVerifierEnabled || Object.values(loadingVerifiers).some((loading) => loading)
// Reset results when chain or contract changes
useEffect(() => {
@ -47,9 +46,7 @@ export const LookupView = () => {
}
setLoadingVerifiers((prev) => ({ ...prev, [verifierId]: true }))
const verifier = getVerifier(verifierId, chainSettings.verifiers[verifierId])
verifier
.lookup(contractAddress, selectedChain.chainId.toString())
clientInstance.lookup(verifierId, chainSettings, selectedChain.chainId.toString(), contractAddress)
.then((result) => setLookupResult((prev) => ({ ...prev, [verifierId]: result })))
.catch((err) =>
setLookupResult((prev) => {
@ -66,18 +63,11 @@ export const LookupView = () => {
}
const handleOpenInRemix = async (lookupResponse: LookupResponse) => {
for (const source of lookupResponse.sourceFiles ?? []) {
try {
await clientInstance.call('fileManager', 'setFile', source.path, source.content)
} catch (err) {
console.error(`Error while creating file ${source.path}: ${err.message}`)
}
}
try {
await clientInstance.call('fileManager', 'open', lookupResponse.targetFilePath)
await clientInstance.saveToRemix(lookupResponse)
await sendToMatomo('lookup', 'openInRemix On: ' + selectedChain)
} catch (err) {
console.error(`Error focusing file ${lookupResponse.targetFilePath}: ${err.message}`)
console.error(`Error while trying to open in Remix: ${err.message}`)
}
}

@ -15,7 +15,7 @@
"terminal.welcomeText8": "Right-click on a JavaScript file in the file explorer and then click `Run`",
"terminal.welcomeText9": "The following libraries are accessible",
"terminal.welcomeText10": "Type the library name to see available commands",
"terminal.text1": "This type of command has been deprecated and is not functionning anymore. Please run remix.help() to list available commands.",
"terminal.text1": "This type of command has been deprecated and is not functioning anymore. Please run remix.help() to list available commands.",
"terminal.hideTerminal": "Hide Terminal",
"terminal.showTerminal": "Show Terminal",
"terminal.clearConsole": "Clear console",

@ -38,8 +38,8 @@ module.exports = {
.waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.click('[data-id="play-editor"]')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]')
},
'Should compute a witness for a simple circuit #group1': function (browser: NightwatchBrowser) {
browser
@ -55,8 +55,8 @@ module.exports = {
.click('[data-id="compute_witness_btn"]')
.frameParent()
.clickLaunchIcon('filePanel')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wtn"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wtn"]')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wtn"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wtn"]')
},
'Should compile a simple circuit using compile button in circom plugin #group2': function (browser: NightwatchBrowser) {
browser
@ -70,8 +70,8 @@ module.exports = {
.click('button[data-id="compile_circuit_btn"]')
.frameParent()
.clickLaunchIcon('filePanel')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]')
},
'Should run Groth16 setup and export for a simple circuit using the GUI #group2': function (browser: NightwatchBrowser) {
browser
@ -116,8 +116,8 @@ module.exports = {
return actions.keyDown(this.Keys.CONTROL).sendKeys('s')
})
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]')
},
'Should display warnings for compiled circuit without pragma version #group4': function (browser: NightwatchBrowser) {
browser
@ -164,7 +164,7 @@ module.exports = {
.waitForElementNotPresent('[data-id="circuit_feedback"]')
.frameParent()
.clickLaunchIcon('filePanel')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]')
},
'Should create a new workspace using hash checker template #group5 #group6': function (browser: NightwatchBrowser) {
browser

@ -46,14 +46,13 @@ const tests = {
'Should connect to Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.setupMetamask(passphrase, password)
.useCss().switchBrowserTab(0)
.useCss()
.switchBrowserTab(0)
.refreshPage()
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.click('*[data-id="landingPageStartSolidity"]')
.clickLaunchIcon('udapp')
.switchEnvironment('injected-MetaMask')
.waitForElementPresent('*[data-id="settingsNetworkEnv"]')
.assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network')
.pause(5000)
.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
@ -65,6 +64,8 @@ const tests = {
.click('*[data-testid="page-container-footer-next"]')
})
.switchBrowserTab(0) // back to remix
.waitForElementPresent('*[data-id="settingsNetworkEnv"]')
.assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network')
},
'Should add a contract file #group1': function (browser: NightwatchBrowser) {

@ -40,8 +40,7 @@ const tests = {
.click('*[data-id="landingPageStartSolidity"]')
.clickLaunchIcon('udapp')
.switchEnvironment('injected-MetaMask')
.waitForElementPresent('*[data-id="settingsNetworkEnv"]')
.assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network')
.pause(5000)
.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
@ -54,6 +53,8 @@ const tests = {
// .click('*[data-testid="popover-close"]')
})
.switchBrowserTab(0) // back to remix
.waitForElementPresent('*[data-id="settingsNetworkEnv"]')
.assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network')
},
'Should load quick-dapp plugin #group1': function (browser: NightwatchBrowser) {

@ -45,8 +45,6 @@ const tests = {
.click('*[data-id="landingPageStartSolidity"]')
.clickLaunchIcon('udapp')
.switchEnvironment('injected-MetaMask')
.waitForElementPresent('*[data-id="settingsNetworkEnv"]')
.assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network')
.pause(5000)
.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
@ -60,6 +58,8 @@ const tests = {
// .click('*[data-testid="popover-close"]')
})
.switchBrowserTab(0) // back to remix
.waitForElementPresent('*[data-id="settingsNetworkEnv"]')
.assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network')
},
'Should add a contract file #group1': function (browser: NightwatchBrowser) {

@ -66,7 +66,7 @@ import { FoundryHandle } from './app/files/foundry-handle'
import { FoundryHandleDesktop } from './app/plugins/electron/foundryPlugin'
import { HardhatHandle } from './app/files/hardhat-handle'
import { HardhatHandleDesktop } from './app/plugins/electron/hardhatPlugin'
import { circomPlugin } from './app/plugins/electron/circomElectronPlugin'
import { GitPlugin } from './app/plugins/git'
import { Matomo } from './app/plugins/matomo'
@ -419,6 +419,8 @@ class AppComponent {
this.engine.register([xterm])
const ripgrep = new ripgrepPlugin()
this.engine.register([ripgrep])
const circom = new circomPlugin()
this.engine.register([circom])
const appUpdater = new appUpdaterPlugin()
this.engine.register([appUpdater])
const remixAIDesktop = new remixAIDesktopPlugin()
@ -565,7 +567,7 @@ class AppComponent {
await this.appManager.activatePlugin(['solidity-script', 'remix-templates'])
if (isElectron()) {
await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep', 'appUpdater', 'slither', 'foundry', 'hardhat']) // 'remixAID'
await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep', 'appUpdater', 'slither', 'foundry', 'hardhat', 'circom']) // 'remixAID'
}
this.appManager.on(

@ -0,0 +1,12 @@
import { ElectronPlugin } from '@remixproject/engine-electron'
export class circomPlugin extends ElectronPlugin {
constructor() {
super({
displayName: 'circom',
name: 'circom',
description: 'circom language compiler',
})
this.methods = []
}
}

@ -42,10 +42,14 @@ export abstract class InjectedProvider extends Plugin implements IProvider {
}
}
askPermission(throwIfNoInjectedProvider) {
async askPermission(throwIfNoInjectedProvider) {
const web3Provider = this.getInjectedProvider()
if (typeof web3Provider !== 'undefined' && typeof web3Provider.request === 'function') {
web3Provider.request({ method: 'eth_requestAccounts' })
try {
await web3Provider.request({ method: 'eth_requestAccounts' })
} catch (error) {
throw new Error(this.notFound())
}
} else if (throwIfNoInjectedProvider) {
throw new Error(this.notFound())
}
@ -61,7 +65,12 @@ export abstract class InjectedProvider extends Plugin implements IProvider {
this.call('notification', 'toast', this.notFound())
throw new Error(this.notFound())
} else {
this.askPermission(true)
try {
await this.askPermission(true)
} catch (error) {
this.call('notification', 'toast', 'Please make sure your Injected Provider is connected to Remix.')
throw new Error(this.notFound())
}
}
return {}
}

@ -38,7 +38,7 @@ export class LocaleModule extends Plugin {
config: Registry.getInstance().get('config') && Registry.getInstance().get('config').api
}
this.locales = {}
locales.map((locale) => {
locales.forEach((locale) => {
this.locales[locale.code.toLocaleLowerCase()] = locale
})
this._paq = _paq

@ -175,7 +175,7 @@ export class RemixAppManager extends PluginManager {
this.pluginsDirectory = 'https://raw.githubusercontent.com/ethereum/remix-plugins-directory/master/build/metadata.json'
this.pluginLoader = new PluginLoader()
if (Registry.getInstance().get('platform').api.isDesktop()) {
requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep', 'slither', 'remixAID']
requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep', 'slither', 'remixAID', 'circom']
}
}

@ -30,6 +30,7 @@ export class RemixEngine extends Engine {
if (name === 'remixAI') return { queueTimeout: 60000 * 20 }
if (name === 'cookbookdev') return { queueTimeout: 60000 * 3 }
if (name === 'contentImport') return { queueTimeout: 60000 * 3 }
if (name === 'circom') return { queueTimeout: 60000 * 4 }
return { queueTimeout: 10000 }
}

@ -58,7 +58,7 @@ Related links:
# Prerequisites:
Before starting coding, we should ensure all devs / contributors are aware of:
Before starting to coding, we should ensure all devs / contributors are aware of:
- Where the codebase is.
- How to setup and get started (always up to date).
- How to run tests.
@ -87,7 +87,7 @@ Before starting coding, we should ensure all devs / contributors are aware of:
- Mark unfinished pull requests with the `Work in Progress` label
- Large pull requests (above 200-400 lines of code changed) cannot be effectively reviewed and should be split into smaller pieces.
- Code should comply to the `JavaScript standard style` - https://www.npmjs.com/package/standard
- You should not expect complete review on a pull request which is not passing CI.
- You should not expect a complete review on a pull request which is not passing CI.
- You can obviously ask for feedback on your approach.
- You should assign a reviewer(s).
- Pull requests should be used as a reference to update coding best practices whenever it is needed.
@ -109,7 +109,7 @@ Before starting coding, we should ensure all devs / contributors are aware of:
- You should take the responsibility of the PR you are reviewing.
- You should make sure the app is viable after the PR is being merged.
- You should make sure the PR is correctly tested (e2e tests, unit tests)
- Ideally You should have enough knowledge to be able to fix related bugs.
- Ideally you should have enough knowledge to be able to fix related bugs.
### 3) Merge:
@ -143,7 +143,7 @@ Before starting coding, we should ensure all devs / contributors are aware of:
- We release an `m.x.0` whenever there is a new feature.
- We release an `x.0.0` after each milestone we consider being an important progress.
- We release an `x.0.0` if there's an API breaking change in one of our libraries.
- After a new release we should stay in alert for possible regression and better not release Friday at 5pm :)
- After a new release we should stay in alert for possible regression and better not release on Friday at 5pm :)
### 2) Community:
- Before the official release, we should select a group of power users and invite them to test and give feedbacks.
@ -197,7 +197,7 @@ Before starting coding, we should ensure all devs / contributors are aware of:
- We release an `x.0.0` if there's a fundamental change in our UX design, which means users will need to readapt the way they use the app
- after a week finishes, we publish/release a new version as **remix-beta.ethereum.org** and inform users so early adopters can test. after another week, when the next finished work is released as **remix-beta.ethereum.org**, the previous one becomes **remix.ethereum.org** and all users can start using it
- a bot to automatically notify users about upcoming features on all channels whenever **remix-beta.ethereum.org** is updated
- in case it's a major version increase - this announcement should be specially marked so ppl can check early instead of being confronted with drastic changes when **remix.ethereum.org** updates
- in case it's a major version increase - this announcement should be specially marked so people can check early instead of being confronted with drastic changes when **remix.ethereum.org** updates
### maintenance:
- Setting up a "bug" time where we each take a bug for which:
- We feel comfortable to deal with

@ -14,6 +14,7 @@ import { AppUpdaterPlugin } from './plugins/appUpdater';
import { RemixAIDesktopPlugin } from './plugins/remixAIDektop';
import { FoundryPlugin } from './plugins/foundryPlugin';
import { HardhatPlugin } from './plugins/hardhatPlugin';
import { CircomElectronPlugin } from './plugins/circomElectronBasePlugin';
import { isE2E } from './main';
const engine = new Engine()
@ -30,6 +31,7 @@ const appUpdaterPlugin = new AppUpdaterPlugin()
const foundryPlugin = new FoundryPlugin()
const hardhatPlugin = new HardhatPlugin()
const remixAIDesktopPlugin = new RemixAIDesktopPlugin()
const circomPlugin = new CircomElectronPlugin()
engine.register(appManager)
engine.register(fsPlugin)
@ -44,6 +46,7 @@ engine.register(foundryPlugin)
engine.register(appUpdaterPlugin)
engine.register(hardhatPlugin)
engine.register(remixAIDesktopPlugin)
engine.register(circomPlugin)
appManager.activatePlugin('electronconfig')
appManager.activatePlugin('fs')

@ -0,0 +1,96 @@
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron"
import { Profile } from "@remixproject/plugin-utils"
import { getInstallationPath, circomCli, extractParentFromKey, getInstallationUrl, getLogInputSignalsPath, extractNameFromKey } from "../tools/circom"
import path from "path"
import { existsSync, readFileSync } from "fs"
const profile: Profile = {
displayName: 'circom',
name: 'circom',
description: 'Circom language compiler'
}
export class CircomElectronPlugin extends ElectronBasePlugin {
clients: CircomElectronPluginClient[] = []
constructor() {
super(profile, clientProfile, CircomElectronPluginClient)
this.methods = [...super.methods]
}
}
const clientProfile: Profile = {
name: 'circom',
displayName: 'circom',
description: 'Circom Language Compiler',
methods: ['install', 'run', 'getInputs', 'isVersionInstalled']
}
class CircomElectronPluginClient extends ElectronBasePluginClient {
isCircomInstalled: boolean = false
constructor(webContentsId: number, profile: Profile) {
super(webContentsId, profile)
this.onload()
}
async install(version = 'latest') {
this.call('terminal' as any, 'logHtml', `Checking if circom compiler (${version}) is installed in ${getInstallationPath(version)}`)
this.isCircomInstalled = await circomCli.isCircomInstalled(version)
if (!this.isCircomInstalled) {
this.call('terminal' as any, 'logHtml', 'Downloading circom compiler from ' + getInstallationUrl(version))
await circomCli.installCircom(version)
this.isCircomInstalled = true
this.call('terminal' as any, 'logHtml', `Circom compiler (${version}) downloaded from ${getInstallationUrl(version)} to ${getInstallationPath(version)}`)
}
}
async run(filePath: string, version = 'latest', options: Record<string, string>) {
if (!this.isCircomInstalled) await this.install(version)
// @ts-ignore
const wd = await this.call('fs', 'getWorkingDir')
const binDir = path.join(wd, path.join(extractParentFromKey(filePath), '.bin'))
// @ts-ignore
const outputDirExists = await this.call('fs', 'exists', path.join(extractParentFromKey(filePath), '.bin'))
// @ts-ignore
if (!outputDirExists) await this.call('fs', 'mkdir', path.join(extractParentFromKey(filePath), '.bin'))
else {
// @ts-ignore
if (process.platform === 'win32' && 'wasm' in options) {
try{
// @ts-ignore
await this.call('fs', 'rmdir', path.join(extractParentFromKey(filePath), '.bin', extractNameFromKey(filePath).replace('.circom', '_js')))
} catch (e) {
}
}
}
filePath = path.join(wd, filePath)
const depPath = path.join(wd, '.deps/https/raw.githubusercontent.com/iden3/')
const outputDir = process.platform !== 'win32' ? path.normalize(extractParentFromKey(filePath) + '/' + '.bin') : binDir
this.call('terminal' as any, 'logHtml', `Compiling ${filePath} with circom compiler (${version})`)
return await circomCli.run(`${filePath} -l ${depPath} -o ${outputDir}`, version, options)
}
getInputs() {
const inputsFile = getLogInputSignalsPath()
const inputsFileExists = existsSync(inputsFile)
const signals: string[] = []
if (inputsFileExists) {
const inputsContent = readFileSync(inputsFile, 'utf-8')
const regexPattern = /main\.(\w+)/g
let match
while ((match = regexPattern.exec(inputsContent)) !== null) {
signals.push(match[1])
}
return signals
}
}
async isVersionInstalled(version: string) {
return await circomCli.isCircomInstalled(version)
}
}

@ -6,7 +6,7 @@ console.log('preload.ts', new Date().toLocaleTimeString())
/* preload script needs statically defined API for each plugin */
const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates', 'ripgrep', 'compilerloader', 'appUpdater', 'slither', 'foundry', 'hardhat', 'remixAID']
const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates', 'ripgrep', 'compilerloader', 'appUpdater', 'slither', 'foundry', 'hardhat', 'remixAID', 'circom']
let webContentsId: number | undefined

@ -0,0 +1,136 @@
import { app } from 'electron'
import os from 'os'
import { exec } from 'child_process'
import path from 'path'
import fs, { existsSync } from 'fs'
import axios from 'axios'
async function downloadFile(url: string, dest: string) {
const writer = fs.createWriteStream(dest)
const response = await axios({
url,
method: 'GET',
responseType: 'stream'
})
response.data.pipe(writer)
return new Promise((resolve, reject) => {
writer.on('finish', () => {
if (process.platform !== 'win32') {
// Sets permission to make the file executable
fs.chmod(dest, 0o775, (err) => {
if (err) {
reject(`Error making file executable: ${err}`)
} else {
resolve(dest)
}
})
}
resolve(true)
})
writer.on('error', reject)
})
}
export function getInstallationPath(version) {
const fileNames = {
win32: 'circom-windows-amd64.exe',
darwin: 'circom-macos-amd64',
linux: 'circom-linux-amd64'
};
return path.join(os.tmpdir(), 'circom-download', version, fileNames[process.platform]);
}
export function getInstallationUrl(version) {
switch (process.platform) {
case 'win32':
return version === 'latest' ? 'https://github.com/iden3/circom/releases/latest/download/circom-windows-amd64.exe' : `https://github.com/iden3/circom/releases/download/${version}/circom-windows-amd64.exe`
case 'darwin':
return version === 'latest' ? 'https://github.com/iden3/circom/releases/latest/download/circom-macos-amd64' : `https://github.com/iden3/circom/releases/download/${version}/circom-macos-amd64`
case 'linux':
return version === 'latest' ? 'https://github.com/iden3/circom/releases/latest/download/circom-linux-amd64' : `https://github.com/iden3/circom/releases/download/${version}/circom-linux-amd64`
}
}
export function getLogInputSignalsPath() {
const tempFilePath = path.join(os.tmpdir(), 'log_input_signals.txt');
return tempFilePath;
}
export const circomCli = {
async installCircom (version) {
const installationPath = getInstallationPath(version)
const installationUrl = getInstallationUrl(version)
if (!existsSync(path.dirname(installationPath))) fs.mkdirSync(path.dirname(installationPath), { recursive: true })
try {
await downloadFile(installationUrl, installationPath)
} catch (e) {
fs.rmSync(installationPath)
throw new Error(e.message)
}
},
async isCircomInstalled (version) {
try {
const installationPath = getInstallationPath(version)
return existsSync(installationPath)
} catch (e) {
return false
}
},
async run (filePath: string, version: string, options?: Record<string, string>) {
const installationPath = getInstallationPath(version)
const cmd = `${installationPath} ${filePath} ${Object.keys(options || {}).map((key) => options[key] ? `--${key} ${options[key]}` : `--${key}`).join(' ')}`
console.log(cmd)
if(process.platform === 'darwin') {
const rosettaInstalled = await checkRosettaInstalled();
if(rosettaInstalled === 'Rosetta is not installed') {
throw new Error('Rosetta is not installed. Please install Rosetta to run this command.');
}
}
return new Promise((resolve, reject) => {
exec(cmd, { cwd: os.tmpdir() }, (error, stdout, stderr) => {
if (error) {
reject(`${error.message} with error code ${error.code}`)
} else {
resolve({ stdout, stderr })
}
})
})
}
}
export const extractParentFromKey = (key: string):string => {
if (!key) return
const keyPath = key.split('/')
keyPath.pop()
return keyPath.join('/')
}
export const extractNameFromKey = (key: string): string => {
if (!key) return
const keyPath = key.split('/')
return keyPath[keyPath.length - 1]
}
async function checkRosettaInstalled(): Promise<string> {
return new Promise((resolve, reject) => {
exec("/usr/bin/pgrep oahd > /dev/null && echo 'Rosetta is installed' || echo 'Rosetta is not installed'", (error, stdout, stderr) => {
if (error) {
reject(`Error: ${stderr}`);
} else {
resolve(stdout.trim());
}
});
});
}

@ -0,0 +1,86 @@
import { NightwatchBrowser } from "nightwatch"
const tests = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
browser.hideToolTips()
done()
},
'Should create semaphore workspace': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="homeTabGetStartedsemaphore"]', 20000)
.click('*[data-id="homeTabGetStartedsemaphore"]')
.pause(3000)
.windowHandles(function (result) {
console.log(result.value)
browser.switchWindow(result.value[1])
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]')
.click('*[data-id="treeViewLitreeViewItemcircuits"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/semaphore.circom"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')
.click('*[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16"]')
.click('*[data-id="treeViewLitreeViewItemscripts/groth16"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/groth16_trusted_setup.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/groth16_zkproof.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk"]')
.click('*[data-id="treeViewLitreeViewItemscripts/plonk"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/plonk_trusted_setup.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/plonk_zkproof.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates"]')
.click('*[data-id="treeViewLitreeViewItemtemplates"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates/groth16_verifier.sol.ejs"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates/plonk_verifier.sol.ejs"]')
})
},
'Should compile a simple circuit using editor play button': function (browser: NightwatchBrowser) {
browser
.click('[data-id="treeViewLitreeViewItemcircuits/simple.circom"]')
.waitForElementVisible('[data-id="play-editor"]')
.click('[data-id="play-editor"]')
.pause(3000)
.click('[data-id="play-editor"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin"]')
.click('[data-id="treeViewLitreeViewItemcircuits/.bin"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js"]')
.click('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]')
},
'Should run setup script for simple circuit': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('circuit-compiler')
.frame(0)
.waitForElementVisible('[data-id="runSetupBtn"]')
.click('[data-id="runSetupBtn"]')
},
'Should compute a witness for a simple circuit': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="compute_witness_btn"]', 60000)
.waitForElementVisible('[data-id="circuit_input_a"]')
.waitForElementVisible('[data-id="circuit_input_b"]')
.setValue('[data-id="circuit_input_a"]', '1')
.setValue('[data-id="circuit_input_b"]', '2')
.click('[data-id="compute_witness_btn"]')
.frameParent()
.clickLaunchIcon('filePanel')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wtn"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wtn"]')
},
'Should generate proof for a simple circuit': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('circuit-compiler')
.frame(0)
.waitForElementVisible('[data-id="generateProofBtn"]')
.click('[data-id="generateProofBtn"]')
.frameParent()
.waitForElementVisible({
locateStrategy: 'xpath',
selector: "//span[@class='text-log' and contains(., 'zk proof validity true')]",
timeout: 60000
})
}
}
module.exports = tests

@ -0,0 +1,67 @@
import { NightwatchBrowser } from "nightwatch"
const tests = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
browser.hideToolTips()
done()
},
'Should create semaphore workspace': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="homeTabGetStartedsemaphore"]', 20000)
.click('*[data-id="homeTabGetStartedsemaphore"]')
.pause(3000)
.windowHandles(function (result) {
console.log(result.value)
browser.switchWindow(result.value[1])
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]')
.click('*[data-id="treeViewLitreeViewItemcircuits"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/semaphore.circom"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')
.click('*[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16"]')
.click('*[data-id="treeViewLitreeViewItemscripts/groth16"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/groth16_trusted_setup.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/groth16_zkproof.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk"]')
.click('*[data-id="treeViewLitreeViewItemscripts/plonk"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/plonk_trusted_setup.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/plonk_zkproof.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates"]')
.click('*[data-id="treeViewLitreeViewItemtemplates"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates/groth16_verifier.sol.ejs"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates/plonk_verifier.sol.ejs"]')
})
},
'Should run plonk trusted setup script for hash checker #group6': function (browser: NightwatchBrowser) {
browser
.click('[data-id="treeViewLitreeViewItemscripts/plonk/plonk_trusted_setup.ts"]')
.pause(2000)
.click('[data-id="play-editor"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible({
locateStrategy: 'xpath',
selector: "//span[@class='text-log' and contains(., 'setup done.')]",
timeout: 60000
})
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk"]')
.click('*[data-id="treeViewLitreeViewItemscripts/plonk/zk"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/keys"]')
.click('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/keys"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/keys/verification_key.json"]')
},
'Should run plonk zkproof script for hash checker #group6': function (browser: NightwatchBrowser) {
browser
.click('[data-id="treeViewLitreeViewItemscripts/plonk/plonk_zkproof.ts"]')
.pause(2000)
.click('[data-id="play-editor"]')
.waitForElementVisible({
locateStrategy: 'xpath',
selector: "//span[@class='text-log' and contains(., 'proof done')]",
timeout: 60000
})
}
}
module.exports = tests

@ -18,7 +18,7 @@ const logger = {
// @ts-ignore
await remix.call('circuit-compiler', 'compile', 'circuits/calculate_hash.circom');
// @ts-ignore
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/calculate_hash.wasm', { encoding: null });
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/calculate_hash_js/calculate_hash.wasm', { encoding: null });
// @ts-ignore
const wasm = new Uint8Array(wasmBuffer);

@ -15,7 +15,7 @@ const logger = {
// @ts-ignore
await remix.call('circuit-compiler', 'compile', 'circuits/calculate_hash.circom');
// @ts-ignore
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/calculate_hash.wasm', { encoding: null });
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/calculate_hash_js/calculate_hash.wasm', { encoding: null });
// @ts-ignore
const wasm = new Uint8Array(wasmBuffer);
const zkey_final = {

@ -79,7 +79,7 @@ async function prove (signals, wasm, wtns, r1cs, zkey_final, vKey) {
// @ts-ignore
await remix.call('circuit-compiler', 'compile', 'circuits/rln.circom');
// @ts-ignore
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/rln.wasm', { encoding: null });
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/rln_js/rln.wasm', { encoding: null });
// @ts-ignore
const wasm = new Uint8Array(wasmBuffer);

@ -102,7 +102,7 @@ async function prove (signals, wasm, wtns, r1cs, zkey_final, vKey) {
// @ts-ignore
await remix.call('circuit-compiler', 'compile', 'circuits/rln.circom');
// @ts-ignore
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/rln.wasm', { encoding: null });
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/rln_js/rln.wasm', { encoding: null });
// @ts-ignore
const wasm = new Uint8Array(wasmBuffer);

@ -25,13 +25,13 @@ function hash(message: any): bigint {
(async () => {
try {
// @ts-ignore
const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', true);
const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', { encoding: null });
// @ts-ignore
const r1cs = new Uint8Array(r1csBuffer);
// @ts-ignore
await remix.call('circuit-compiler', 'compile', 'circuits/semaphore.circom');
// @ts-ignore
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.wasm', true);
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore_js/semaphore.wasm', { encoding: null });
// @ts-ignore
const wasm = new Uint8Array(wasmBuffer);

@ -25,13 +25,13 @@ function hash(message: any): bigint {
(async () => {
try {
// @ts-ignore
const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', true);
const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', { encoding: null });
// @ts-ignore
const r1cs = new Uint8Array(r1csBuffer);
// @ts-ignore
await remix.call('circuit-compiler', 'compile', 'circuits/semaphore.circom');
// @ts-ignore
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.wasm', true);
const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore_js/semaphore.wasm', { encoding: null });
// @ts-ignore
const wasm = new Uint8Array(wasmBuffer);

Loading…
Cancel
Save