Skip to main content

Compiler

The SolidCash compiler is called solidc and is used to compile SolidCash .scash contract files into .json (or .ts) artifact files. These artifact files can be used to instantiate a SolidCash contract with the help of the SolidCash SDK. For more information on this artifact format refer to Artifacts.

info

Because of the separation of the compiler and the SDK, SolidCash contracts can be integrated into other programming languages in the future.

Command Line Interface​

The solidc command line interface is used to compile SolidCash .scash files into .json (or .ts) artifact files.

Installation​

You can use npm to install the solidc command line tool globally.

npm install -g solidc

CLI Usage​

The solidc CLI tool can be used to compile .scash files to JSON (or .ts) artifact files.

Usage: solidc [options] <source_file>

Options:
-V, --version Output the version number.
-o, --output <path> Specify a file to output the generated artifact.
-h, --hex Compile the contract to hex format rather than a full artifact.
-A, --asm Compile the contract to ASM format rather than a full artifact.
-c, --opcount Display the number of opcodes in the compiled bytecode.
-s, --size Display the size in bytes of the compiled bytecode.
--split [mode] Split mode: off, size, or func. A bare --split is refused.
--suppress-warnings Suppress non-fatal compiler warnings.
--no-source-map Skip bytecode source maps (faster compile; weaker debug attribution).
--no-message-bus Disable inferred atomic message-bus composition (fails if the contract requires it).
-S, --skip-enforce-function-parameter-types Do not enforce function parameter types.
-L, --skip-enforce-locktime-guard Do not inject a tx.time guard when tx.locktime is used.
--allow-unverified-paths Allow spending paths with no verification or state covenant (unsafe).
--allow-unsafe-operations Allow detected caller-controlled delegatecall/selfdestruct authority (unsafe).
-f, --format <format> Specify the format of the output. (choices: "json", "ts", default: "json")
-?, --help Display help

-h prints hex, not help: use -? or --help for help. The CLI takes exactly one source file and has no Solidity-mode flag; compile .sol sources through the JavaScript API with evmCompat: true, or through a SolidHat project.

FlagEffect
-o, --output <path>Write the artifact to a file, creating missing directories. Without it, or with -o -, the artifact goes to stdout.
-f, --format json|tsjson (default) or a TypeScript module (export default {...} as const) whose literal types drive the SDK's typed calls.
-h, --hex / -A, --asmPrint only the script, as hex or as ASM. They cannot be combined with each other, with --output, or with --split size|func.
-c, --opcount / -s, --sizePrint the opcode count and/or the byte size instead of the artifact. With a split mode, one line per shard.
--split off|size|funcoff compiles a normal artifact. size always splits into a root plus facets (it sets forceSplit), see Large Contracts. func compiles one framed master per entry point and selects the framed convention (experimental).
--suppress-warningsHide non-fatal warnings, including the over-10,000-byte size warning. Errors still fail the compile.
--no-source-mapSets sourceMap: false: no source maps on the artifact. Faster, but failures are attributed less precisely.
--no-message-busSets messageBus: false: compile direct-only. Fails with MESSAGE_BUS_REQUIRED when the contract needs a bus route.
-SSets enforceFunctionParameterTypes: false.
-LSets enforceLocktimeGuard: false.
--allow-unverified-pathsSets enforceFinalRequire: false: a spend path with no verification and no state change becomes a warning instead of an error. Unsafe.
--allow-unsafe-operationsSets allowUnsafeOperations: true. Unsafe.

When the compiled script is over 10,000 bytes, solidc prints a warning that the BCH network will not accept it.

tip

To have the best TypeScript integration, we recommend generating the artifact in the .ts format and importing it into your TypeScript project from that .ts file.

Example​

solidc ./Contract.scash --output ./artifact.ts --format ts
solidc ./Contract.scash --size --opcount

solidc ./Large.scash --split size --output ./Large.split.json
info

The size outputs of the solidc compiler are based on the bytecode without constructor arguments. This means they are always an underestimate, as the contract hasn't been initialized with contract arguments.

JavaScript Compilation​

Generally SolidCash contracts are compiled to an Artifact JSON file using the CLI compiler. They can also be compiled programmatically with the solidc package.

npm install solidc

compileFile()​

compileFile(sourceFile: PathLike, options?: CompileOptions): Artifact

Compiles a SolidCash contract from a source file. This compile method is handy when using Node.js with the contract source file available but you are doing quick compilations (for example for contract size comparisons) and you don't need the contract artifact file to be generated.

note

compileFile() only works from a Node.js context because it uses the file-system so it's not available in browser setting.

Example​

const P2PKH = compileFile(new URL('p2pkh.scash', import.meta.url));

compileString()​

compileString(sourceCode: string, options?: CompileStringOptions): Artifact

Compiles a SolidCash contract from a source code string. This compile method is handy in a browser, where test contracts can be compiled quickly and discarded. The method is also useful if no source file is locally available (e.g. the source code is retrieved with a REST API).

const baseUrl = 'https://raw.githubusercontent.com/solidcash/solidcash'
const result = await fetch(`${baseUrl}/master/examples/p2pkh.scash`);
const source = await result.text();

const P2PKH = compileString(source);

Multiple declarations​

compileFile() and compileString() keep the single-artifact API and select the source's primary contract (the last concrete top-level contract unless primaryContractName is provided). To compile every top-level declaration, use the additive multi APIs:

import { compileFileMulti, compileStringMulti } from 'solidc';

const artifacts = compileFileMulti('Protocol.scash');
const tokenArtifact = artifacts.Token;
const factoryArtifact = artifacts.Factory;

The return value is an ArtifactMap, keyed by declaration/contract name in source order. Concrete contracts and libraries receive normal artifacts; declaration-only units receive minimal artifacts; abstract contracts are not deployable entries. Duplicate top-level names are a compile error.

For a deploy-aware wrapper which carries the complete artifact graph into the SDK, use ContractBundle:

import { ContractBundle } from 'solidcash/stateful';

const bundle = ContractBundle.from(artifacts, { provider, signer });
const { contract: factory } = await bundle.deploy('Factory', [owner], deployOptions);

This lets typed child deployments and linked library dependencies resolve artifacts without calling registerChild() or registerArtifacts() manually.

Split bundles​

import { compileFileSplit, compileStringSplit } from 'solidc';

const split = compileFileSplit('Large.scash', {
maxShardBytes: 9_500,
forceSplit: false,
});

The return type follows the split option:

splitReturns
omitted or 'size'a SplitArtifactBundle (root plus facets). A contract that already fits has zero facets unless forceSplit is set.
'off'a plain Artifact, as compileFile() returns
'func'a FunctionSplitBundle: one framed master per ABI entry (experimental; requires composition: 'framed')

compileFile() and compileString() refuse split: 'size' and split: 'func'. See Large Contracts & Split Bundles for planning, deployment, routing, and v1 limits.

Compiler options​

Every compile function takes an options object. DEFAULT_COMPILER_OPTIONS (exported by solidc) holds the defaults: enforceFunctionParameterTypes: true, enforceLocktimeGuard: true, enforceFinalRequire: true, allowUnsafeOperations: false, commitmentLimit: 128, chunkedArrays: true, chainId: 0, optimise: true, messageBusKernel: true and composition: 'direct'. The artifact records the options that change how it is read (evmCompat, the enforcement flags, commitmentLimit, messageBus and a few more) under compiler.options.

Safety and validation​

OptionDefaultPurpose
enforceFinalRequiretrueA public or external spend path with no verification (require, assert, revert, a time check) and no state change is a compile error. false turns it into a warning; such a path is spendable by anyone.
enforceFunctionParameterTypestrueCheck entry arguments: bounded integers, fixed-point values, enums and bool are range-checked, bytesN length-checked.
enforceLocktimeGuardtrueInject the guard required when tx.locktime participates in a spend.
allowUnsafeOperationsfalseAdmit compiler-detected caller-controlled delegatecall/selfdestruct authority. A migration and testing escape hatch only.
maximumInputBytes10000The per-input byte ceiling the compiler plans against (the BCH limit). Raise it only for a private or mock network.
commitmentLimit128NFT commitment budget for the state layout. BCH allows 128 bytes.

Solidity compatibility​

OptionDefaultPurpose
evmCompatfalseSolidity mode: accept pragma solidity and .sol imports, apply Solidity type and literal rules, use EVM ABI words and keccak256 selectors. See Solidity Import Compatibility.
addressFormfollows the mode'hash' (the 20-byte identity) or 'bytecode' (the whole locking bytecode): the form an address takes where it becomes bytes. Omitted means 'hash' under evmCompat and 'bytecode' otherwise.
ecrecover'evm' under evmCompat, else unsetMap bare Solidity ecrecover(hash, v, r, s) to the 'p2pkh' or 'evm' address projection. Natively, ecrecover is undefined unless this is set.
chainId0The value of block.chainid, baked as one byte (chainId & 0xff). Convention: mainnet 0, chipnet 1, testnet4 2, regtest -1.

Imports, selection and diagnostics​

OptionPurpose
primaryContractNameSelect the contract compiled into the single artifact instead of the last concrete one.
compileDeclarationsFully compile only these contract names in a multi compile; other imported concrete contracts are left out (interfaces still get ABI-only artifacts).
importResolver, rootFileIdResolve imports without a filesystem (browser and tooling hook). compileFile* supplies a Node resolver automatically, including node_modules and .sol resolution under evmCompat.
filesString entry points only: an in-memory map of import sources keyed by path, shorthand for importResolver: createMapResolver(files). Pass files or importResolver, not both.
errorListenerReceive syntax errors through a custom SolidCashErrorListener.
warningListenerReceive each warning as a CompilerWarning instead of the default console print. Compose with defaultWarningListener to print and collect.
suppressWarningsDiscard non-fatal warnings. Errors still throw.
sourceMapDefault true. false skips source maps; require and log line numbers are still recorded.
optimiseDefault true. false emits the unoptimised script so a VM failure points at the exact source line. Debugging only: the script is larger, so never deploy it.
import { compileString, defaultWarningListener, type CompilerWarning } from 'solidc';

const warnings: CompilerWarning[] = [];
const artifact = compileString('import "./math.scash";\ncontract Calculator() { ... }', {
files: { 'math.scash': 'function double(int x) pure returns (int) { return x * 2; }' },
warningListener: (warning) => { warnings.push(warning); defaultWarningListener(warning); },
});

State and code generation​

OptionDefaultPurpose
chunkedArraystrueStore an eligible unbounded fixed-width T[] state array in the dense chunk backend. false selects the sparse entry-NFT collection.
forceStatefulfalseGive a contract with no state a master NFT and an empty state layout, so it can be called through the stateful SDK.
tagSingletonDataNftsfalseEncode an NFT-index tag even when fixed state fits one data NFT. A wire-layout choice.
inlineLibrariesautomaticPer built-in library name (for example keccak256): true always inlines it, false always uses a side-car.
disableAutoInlinefalseDisable the heuristic that inlines small, single-use libraries. Explicit inlineLibraries entries still apply.
userLibrariesnoneRegister custom script libraries: { name, code, abi: { inputs, outputs } }, with code as hex.
brandLibraryfalseBrand a delegate library to its consumer's category, so it is spendable only beside that consumer's master.
delegateArtifactContractsallOnly these contract names receive a delegateArtifact (the proxy-context build).
reuseEntryLocalForWritebacktrueSize optimisation. false exists for size measurements only; do not deploy it.
autoDelegateunsetInternal: the compiler sets it to produce delegateArtifact. Do not set it.

Composition (experimental)​

Message-bus kernel composition and the framed convention are experimental profiles. Leave these options at their defaults unless you are following the message bus guide or the framed composition guide.

OptionDefaultPurpose
messageBusautomaticOmitted infers a route from the source's atomic call graph. true also requests a route for a graph-free contract and falls back to direct-only when none can be built. false compiles direct-only and fails with MESSAGE_BUS_REQUIRED when the source needs a bus.
messageBusKerneltrueCompile routes as the kernel row loop. false reproduces the legacy unrolled route, for compatibility tests only.
messageBusHandlersall eligibleClone only these entry points (by name, or { name, selector } for one overload) into the route.
messageBusGraphContextfalseCompile a graph-free participant with the atomic graph envelope.
messageBusEntryPoolCapacity8 / 2Entry lanes for graph-context one-key pools (default 8) and fixed-key tuples (default 2). An integer from 1 to 99.
messageBusDirectParticipantfalseAllow atomic manifests with a co-spent direct-only participant and no verifier NFT.
messageBusRootEnvelopefalseCarry the canonical envelope on the root route.
messageBusProxyForwardEnvelopefalseCarry the canonical envelope in the root proxy's forward payload.
optimizeForCompilationSpeedfalseSkip speculative route admission attempts and compile the conservative route directly.
ownerAltAnchor, handlerBandModeloffOpt-in route code generation features; omitted reads the SOLIDC_OWNER_ALT_ANCHOR / SOLIDC_HANDLER_BAND_MODEL environment flags.
composition'direct''framed' compiles each entry point as a master program that recreates itself, so a contract's state settles once per transaction. The result is published on artifact.composition.
upgradesFromnoneFramed proxies: per contract name, the state layout of the implementation it replaces. The compile refuses a layout that would read those records differently.
splitsee above'off', 'size' or 'func', for the split entry points.

Split compiles also take maxShardBytes (the per-shard redeem-script ceiling; omitted, the planner budgets the BCH input limit including constructor and call arguments), forceSplit (split even when the contract fits) and rootFunctions (entry points to keep on the root). See Large Contracts.

Unsafe and relaxed options exist for migration, testing, and private networks. Publishing an artifact requires the limits and security settings of the network which will validate it.