CLI tool
argotorg/solc-js avatar
argotorg/solc-js

solc-js runs the Solidity compiler in JavaScript, and solcjs is not solc

Javascript bindings for the Solidity compiler

1,510 stars349 forksTypeScriptMIT

At a glance

What is it?
solc-js wraps the Emscripten build of the Solidity compiler so contracts can be compiled from Node.js without installing the native binary. The README is unusually candid about the gap between the two: the bundled command line tool cannot be used with an Ethereum client and offers fewer features than the real binary.
Who is it for?
Adopt solc-js when your build or test pipeline needs to compile Solidity inside Node.js, for example a test suite or a web tool, and pin the exact version you need since each package version matches a compiler release. Skip it for reproducible production builds on a server, where the native solc binary is the documented route.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The compiler is Emscripten, which changes what you can do

solc-js is JavaScript bindings for the Solidity compiler, and the mechanism underneath is specific: it uses the Emscripten compiled Solidity found in the solc-bin repository. In other words, the compiler itself is compiled to WebAssembly and shipped inside the npm package.

That single architectural fact explains both the appeal and the limits. It means no system compiler, no download step and no native binary, so a Node.js test suite, a browser-based tool or a CI job on an unusual architecture can compile Solidity by adding one dependency.

It also means this is a large package. The compiler is not a thin wrapper around a subprocess, and the `bindings/` directory at the repository root holds the generated JavaScript that drives the WebAssembly module. The published `files` list in package.json includes `bindings/*.js`, `common/*.js` and the top level `*.js` and `*.d.ts`, which is how the compiled compiler reaches consumers.

The sources are TypeScript. Alongside `index.ts` and `wrapper.ts` there are `abi.ts`, `linker.ts`, `translate.ts`, `formatters.ts`, and two files that only matter when publishing a new compiler version, `downloadCurrentVersion.ts` and `verifyVersion.ts`.

The package metadata declares Node 16 or newer as the engine floor, and the runtime dependencies include commander for the command line front end, command-exists to detect local solvers, follow-redirects, and js-sha3 for hashing.

solcjs cannot be used with an Ethereum client

The command line tool in this package is named solcjs, and the README is direct about how it differs from the real thing.

It is not compatible with `solc` from the Solidity compiler package, and therefore cannot be used together with an Ethereum client through the `eth.compile.solidity()` RPC method. The command line interface also provides fewer features than the binary release.

That rules out a whole category of use. If you are looking for a compiler to put on a build machine and point a node at, this is not it. The README refers you to the Solidity compiler documentation for instructions on installing the native `solc`.

What the command line is good for is quick checks. Installed globally with `npm install -g solc`, you get `solcjs`, and the supported features are listed with:

bash
solcjs --help

Compiling a contract that imports others through relative paths needs the path options to describe the layout of your project:

bash
solcjs --bin --include-path node_modules/ --base-path . MainContract.sol

`--base-path` is the root of your own source tree and `--include-path` names extra locations holding external code such as package manager libraries.

Then comes the reproducibility warning, which is the most useful sentence in the section. Files specified outside the base path or an include path are referenced by absolute path, and absolute paths in contract metadata mean the bytecode is reproducible only when placed at those exact locations. Two builds of the same contract in different directories can produce different metadata, which is exactly what you do not want in a release pipeline.

The high-level API is one method and a JSON string

There are two ways to use the package, and the README distinguishes them clearly.

The high-level API gives a uniform interface across all compiler versions, and it consists of a single method, `compile`. It expects the Compiler Standard Input and Output JSON, so the shape of your input and output is defined by the Solidity documentation rather than by this library. It takes a string and returns a string, which is why every example in the README wraps the result in `JSON.parse`.

The example without imports is the shortest useful one:

javascript
var solc = require('solc');

var input = {
  language: 'Solidity',
  sources: {
    'test.sol': {
      content: 'contract C { function f() public { } }'
    }
  },
  settings: {
    outputSelection: {
      '*': {
        '*': ['*']
      }
    }
  }
};

var output = JSON.parse(solc.compile(JSON.stringify(input)));

The output selection is a two-level wildcard, `*` for every source and `*` for every contract, which asks the compiler for everything. That is convenient for exploration and wasteful for a build, since a real project should request the specific artifacts it deploys.

The uniformity across versions is the point of this API. Compiler input and output formats have changed over the years, and the high-level method hides that so a tool written against one version keeps working against another.

The import callback has to be synchronous, and the README admits it

The optional callbacks are where the interesting constraints are, and the README is unusually forthcoming about the limitation.

The `import` callback resolves unmet dependencies. It receives a path and must synchronously return either an error or the dependency content as a string. It cannot be used together with callback-based, asynchronous filesystem access.

That is a real limitation in a Node.js application, where reading a file is naturally asynchronous. The README gives the workaround: collect the names of dependencies, return an error, and keep re-running the compiler until all of them are resolved. That is an iterative loop driven by error responses rather than by a resolver, and it works, but it is not what you would write if the API allowed promises.

The second callback, `smtSolver`, exists since version 0.5.1 and solves the SMT queries generated by Solidity's SMTChecker. If you have a solver installed locally, the callback must synchronously return either an error or the result.

A default implementation ships with the package. The `smtchecker` module exports `smtCallback`, which takes a function that runs queries and a solver configuration object. The `smtsolver` module holds predefined solver configurations and relies on Z3, Eldarica or cvc5 being installed locally, exporting the list of solvers it found and a function to invoke one.

The example sets a model checker engine of `chc` with the `smtlib2` solver, compiles a contract whose assertion is false, and expects an assertion failure warning with a counterexample. It also notes the example only works via Node and not in the browser, because the solvers are external processes.

The README labels the SMT callback API experimental and says it can change at any time, with the last change in version 0.8.11.

The low-level API is four entry points with different version floors

The low-level API gives access to the compiler interfaces as they actually are, and the interfaces depend on the compiler version. That is the trade: fidelity instead of uniformity.

Four methods are listed.

`solc.lowlevel.compileSingle` is the original entry point and supports only a single file. `solc.lowlevel.compileMulti` supports multiple files and was introduced in 0.1.6. `solc.lowlevel.compileCallback` supports callbacks and arrived in 0.2.1. `solc.lowlevel.compileStandard` behaves like `compile` above but is only present in compilers at 0.4.1 and later.

Those introduction versions are the useful information here. If your tool supports pinning an old compiler for a legacy contract, `compileMulti` will not exist before 0.1.6 and `compileStandard` will not exist before 0.4.1, so a version-switching tool has to branch on the compiler version rather than call one method.

Starting from 0.6.0 the high-level API accepts an object in place of a bare callback for supplying callbacks. The import callback example notes the new callback syntax was supported from 0.5.12 and mandatory from 0.6.0, which is the same kind of boundary.

For most tooling the high-level method is the right choice and the low-level one exists for the cases where it is not.

Package version, compiler version and a safety check on publish

The package version is 0.8.37, which matches the Solidity compiler release it wraps rather than a version of this library. That is deliberate: you install a specific compiler, not a version of the bindings, and the whole point of the package is to pin a compiler.

It also means an `npm install solc` in a project gives you whatever the `latest` tag points at, which will move as new compilers are released. For a reproducible build you want an exact version, and for a contract that was audited against a specific compiler you want exactly that one.

The publish pipeline in package.json shows how tightly the two are coupled. `updateBinary` cleans the build, runs `downloadCurrentVersion.ts` and then `verifyVersion.ts`, which is how the WebAssembly compiler binary is fetched and checked before a build. `build:tarball` runs that update, compiles, and packs from `dist` with a safety check bypassed by an environment variable, and `publish:tarball` publishes the resulting file.

Two smaller scripts are worth knowing. `pretest` lints, builds and copies test resources into the distribution, and `test` runs tape inside `dist`, so the tests exercise the published output rather than the source tree. Coverage goes through nyc and then Coveralls.

The repository has no GitHub releases, so the npm registry is the distribution channel, and the last push was on 2026-10-07. The licence is MIT.

Editorial conclusion

Adopt solc-js when your build or test pipeline needs to compile Solidity inside Node.js, for example a test suite or a web tool, and pin the exact version you need since each package version matches a compiler release. Skip it for reproducible production builds on a server, where the native solc binary is the documented route. Verify first that your imports resolve under `--base-path` and `--include-path`, because absolute paths outside them break reproducible bytecode.

Frequently asked questions

How do I install the Solidity compiler for Node.js?

Install the package with `npm install solc`, which gives you the latest stable version of the compiler compiled to WebAssembly. A global install with `npm install -g solc` also provides the `solcjs` command line tool.

Can solcjs be used with an Ethereum client?

No. The README states the command line interface is not compatible with `solc` from the Solidity compiler package and cannot be used with an Ethereum client through `eth.compile.solidity()`, and that it provides fewer features than the binary release.

How does the import callback work for resolving dependencies?

The `import` callback receives a path and must synchronously return either an error or the dependency content as a string, and it cannot be combined with asynchronous filesystem access. The documented workaround is to collect dependency names, return an error, and re-run the compiler until all imports resolve.

Official sources

  1. argotorg/solc-js on GitHub
  2. Issues
  3. License: MIT
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/argotorg-solc-js.svg)](https://hysenlabs.com/projects/argotorg-solc-js)

Was this useful?