Enclave is now The Interfold.
CRISP
Setup

Set up CRISP

This page shows how to build CRISP and run it on a local chain. It is for developers who want to run the reference application or change it. At the end, the full CRISP stack runs on your computer and the client is at http://localhost:3000.

CRISP lives in its own repository, theinterfold/CRISP (opens in a new tab). It builds against one released version of the Interfold, which interfold-release.json names. You do not need a clone of the Interfold repository. Run all pnpm commands on this page from the root of the CRISP repository.

Prerequisites

ToolVersionUsed by
GitAnyClone
Rust (opens in a new tab)1.91.1 (rust-toolchain.toml)Server, CLI, program, and runner. Also the interfold CLI, on a platform with no release binary.
Node.js (opens in a new tab)22.x (CI uses NODE_VERSION: 22)Client, SDK, contracts, and scripts
pnpm (opens in a new tab)10.7.1 (packageManager in package.json)All JavaScript packages
Foundry (opens in a new tab)Anyanvil, the local chain that scripts/dev.sh starts
yq (opens in a new tab)Anyscripts/dev_cipher.sh reads node addresses from the config
nargo (opens in a new tab)v1.0.0-beta.26 (NOIR_TOOLCHAIN in CI)Circuit builds in pnpm dev:setup and pnpm compile:circuits
bb (opens in a new tab)5.2.0 (crates/zk/prover/versions.json of the Interfold release)Verification keys and generated verifiers
Python 3Anyscripts/compile_circuits.sh patches the generated verifiers
A browser wallet, for example MetaMask (opens in a new tab)AnyThe client
cargo-openvm v2.0.2v2.0.2Only for real OpenVM proofs (CRISP_REAL_PROOFS=1)

Install nargo with noirup -v v1.0.0-beta.26. Other nargo versions can fail to compile the circuits. OpenVM is not necessary for local development, because the local scripts start the program server in dev mode unless CRISP_REAL_PROOFS=1.

Build and start CRISP

Clone the repository

git clone https://github.com/theinterfold/CRISP.git
cd CRISP

Choose a local profile

This step is optional. pnpm dev:setup copies the example file when crisp.dev.env does not exist.

cp crisp.dev.env.example crisp.dev.env

The default profile uses the insecure-64 BFV preset and skips DKG proof aggregation. See Local profile for the two settings.

Build everything

pnpm dev:setup

scripts/setup.sh does these tasks in sequence:

  1. Installs the dependencies (pnpm install --frozen-lockfile).
  2. Builds the CRISP SDK (pnpm build:sdk). This step takes the circuits of the pinned Interfold release, compiles the CRISP circuits, and stages the circuit presets.
  3. Compiles the CRISP contracts (pnpm compile:contracts).
  4. Copies server/.env.example to server/.env when server/.env does not exist. Then it builds the cli and server binaries.
  5. Writes the E3_PARAM_SET that matches the profile into server/.env.
  6. Copies client/.env.example to client/.env when client/.env does not exist.
  7. Installs the interfold CLI of the pinned release into ~/.cargo/bin (see Interfold release).
  8. Runs interfold program compile, only when CRISP_REAL_PROOFS=1.

At the end, the script prints the active profile under the heading CRISP dev profile.

Check the interfold binary

which interfold

Make sure that the path is ~/.cargo/bin/interfold (or the bin folder of your CARGO_HOME). The CRISP scripts run the first interfold on your PATH.

Start all services

pnpm dev:up

scripts/dev.sh starts the services in this order. The ciphernodes, the program server, the CRISP server, and the client start after the contract deployment is complete.

ServiceStarted byPort
Anvil (chain ID 31337)scripts/dev.sh8545
Local randomness helperscripts/anvil-randomness.mjsNone
Contract deploymentscripts/crisp_deploy.shNone
Ciphernodes cn1 to cn5scripts/dev_cipher.sh (interfold nodes up)QUIC 9201 to 9205. cn1 dashboard 8080
Program serverscripts/dev_program.sh (interfold program start)13151
CRISP serverscripts/dev_server.sh (cargo run --bin server)4000
Clientscripts/dev_client.sh (pnpm dev-static)3000

When dev_cipher.sh has registered the ciphernodes, it prints CIPHERNODES HAVE BEEN ADDED. and writes the file .interfold/ready. The client starts after port 4000 is open and that file exists. The client is then available at http://localhost:3000.

⚠️

Each pnpm dev:up starts from an empty state. Anvil starts a new chain, dev_cipher.sh deletes .interfold/data and .interfold/config, and dev_server.sh deletes server/database. When you press Ctrl+C, the cleanup in dev.sh stops every process whose command line contains anvil or interfold start. This includes processes that CRISP did not start.

The program server listens on 0.0.0.0:13151 and does not authenticate callers. It is a development service. It must not be reachable from production or untrusted networks.

What the deployment does

scripts/crisp_deploy.sh sets PRIVATE_KEY to the first Anvil account, USE_MOCKS=true, and DEPLOY_INTERFOLD=true. Then it runs pnpm deploy:contracts --network localhost. The deployment includes these contracts:

  • The Interfold contracts, with mock BFV verifiers for the DKG. With CRISP_SKIP_PROOF_AGGREGATION=false, the script sets ENABLE_ZK_VERIFICATION=true, and the deployment uses the real BFV verifiers. For these verifiers, the script extracts the circuit archive of the pinned Interfold release and sets INTERFOLD_CIRCUITS_DIR (see below).
  • CRISPProgram, the two generated Noir ballot verifiers, and SelfRegistry.
  • MockOpenVmReceiptVerifier (with CRISP_UNPROVED_TEST=1, which the local scripts set unless CRISP_REAL_PROOFS=1), MockVotingToken, and MockCrispDataAvailabilityVerifier. With CRISP_REAL_PROOFS=1, the deployment instead deploys the OpenVM receipt verifier for the guest that interfold program compile built. On Sepolia, CRISP_UNPROVED_TEST=1 deploys the mock verifier only with ALLOW_SEPOLIA_UNPROVED_COMPUTE=true. On any chain other than the local chain (chain ID 31337) and Sepolia, the deployment refuses CRISP_UNPROVED_TEST=1.

After the deployment, the script writes the new contract addresses into three files. You do not copy addresses by hand:

  • interfold.config.yaml: the contracts of the localhost chain.
  • server/.env: INTERFOLD_ADDRESS, E3_PROGRAM_ADDRESS, CIPHERNODE_REGISTRY_ADDRESS, FEE_TOKEN_ADDRESS, CRISP_VOTING_TOKEN, and the mock contract references.
  • client/.env: VITE_CRISP_TOKEN.

The deployment records are in packages/crisp-contracts/deployed_contracts.json. For the addresses of the public networks, see Contract addresses.

CRISP deploys the Interfold contracts with the scripts of the published @interfold/contracts package, not with a clone of the Interfold repository. Outside the Interfold monorepo, the real BFV verifiers (ENABLE_ZK_VERIFICATION=true) read the verification key hashes of the BFV aggregation circuits from the directory that INTERFOLD_CIRCUITS_DIR names. This directory must hold the circuits of the same release as the package, in the release layout (<preset>/<committee>/default/...). scripts/crisp_deploy.sh sets the variable to circuits/interfold/release, which node scripts/interfold-release.mjs circuits writes from the release archive. When you run pnpm deploy:contracts yourself with ENABLE_ZK_VERIFICATION=true, set INTERFOLD_CIRCUITS_DIR to that directory. The deployment stops with an error when the variable is not set.

Configuration

FileCreated byPurpose
crisp.dev.envCopied from crisp.dev.env.example by the scriptsLocal profile: BFV preset and proof aggregation mode
interfold.config.yamlTracked in Git. The deployment updates addresses.Ciphernode chains, contracts, program mode, and nodes
server/.envCopied from server/.env.example by dev:setupSettings for the CRISP server and the CLI
client/.envCopied from client/.env.example by dev:setupSettings for the client
.envCopied by hand from .env.exampleKeys for scripts/setup_testnet.sh only
interfold-release.jsonTracked in GitThe Interfold release that CRISP builds against

Interfold release

interfold-release.json names the Interfold version and the SHA-256 digest of each release asset that CRISP downloads: the interfold CLI and the circuit archive. scripts/interfold-release.mjs refuses an asset when its digest does not match. The other dependencies name the same release in their own files:

  • Cargo.toml and guest/Cargo.toml: the Rust crates (e3-*), as Cargo git dependencies on the release tag.
  • circuits/lib/Nargo.toml and circuits/bin/*/Nargo.toml: the Noir library interfold_lib, as a Nargo git dependency on the release tag.
  • package.json and packages/crisp-contracts/package.json: the npm packages @interfold/config and @interfold/contracts.

To move to another release, follow the steps in the Interfold dependency section (opens in a new tab) of the CRISP Readme.

Local profile

scripts/lib/dev_config.sh reads crisp.dev.env:

VariableDefaultValuesEffect
CRISP_BFV_PRESETinsecure-64insecure-64, secure-8192BFV preset. Sets E3_PARAM_SET in server/.env to 0 or 2.
CRISP_SKIP_PROOF_AGGREGATIONtruetrue, falsetrue: mock verifiers and no recursive DKG proofs. false: real verifiers, recursive circuits, and full proof aggregation.

After you change crisp.dev.env, run pnpm dev:setup again, then pnpm dev:up. The ciphernodes refuse the skip setting unless the interfold binary has the test-only-skip-proof-aggregation Cargo feature. pnpm dev:setup selects that feature from the profile. With CRISP_SKIP_PROOF_AGGREGATION=false, DKG takes minutes for each committee, because the ciphernodes make recursive proofs.

⚠️

~/.cargo/bin/interfold is shared. A pnpm dev:setup in another project, for example templates/default, replaces the binary that this profile installed. When the ciphernodes then exit at startup, dev_cipher.sh stops and prints the node status table.

interfold.config.yaml

This file configures the local ciphernodes and the program server. The excerpt shows the local chain and the first two nodes. The deployment updates the contract addresses.

interfold.config.yaml (excerpt)
chains:
  - name: localhost
    enabled: true
    rpc_url: ws://localhost:8545
    ingestion_confirmations: 0
    rpc_poll_interval_ms: 250
    data_availability:
      mode: mock_http
      rpc_url: http://127.0.0.1:4000/availability
    contracts:
      # e3_program, interfold, ciphernode_registry, bonding_registry,
      # slashing_manager, and fee_token: updated by the deployment
program:
  dev: false
nodes:
  cn1:
    quic_port: 9201
    ctrl_port: 50501
    autonetkey: true
    autopassword: true
    dashboard_port: 8080
  cn2:
    quic_port: 9202
    ctrl_port: 50502
    autonetkey: true
    autopassword: true
    peers:
      - /ip4/127.0.0.1/udp/9201/quic-v1
  • The file also has mainnet and sepolia chains with enabled: false. The data-availability mode of mainnet is avail. On sepolia it is mock_http, because Avail Turing is down.
  • Nodes cn3 to cn5 use QUIC ports 9203 to 9205 and control ports 50503 to 50505. Each node peers with cn1.
  • scripts/lib/dev_config.sh sets E3_PROGRAM__DEV=true unless CRISP_REAL_PROOFS=1, so pnpm dev:up runs the development runner by default although the file sets program.dev: false.

For all node configuration keys, see Configuration.

Server settings

The CRISP server and the CLI read server/.env. Each value can also come from the process environment (server/src/config.rs).

VariableLocal value or defaultPurpose
PRIVATE_KEYFirst Anvil accountServer key. The CLI uses it to request rounds. The server uses it to relay inputs, post census roots, and sign availability promises.
INTERFOLD_SERVER_URLhttp://127.0.0.1:4000URL of this server. Remote cron targets must use HTTPS.
HTTP_RPC_URL, WS_RPC_URL, CHAIN_IDhttp://127.0.0.1:8545, ws://127.0.0.1:8545, 31337Chain access. At startup, the server stops if CHAIN_ID is not the chain of HTTP_RPC_URL.
PROGRAM_SERVER_URLhttp://127.0.0.1:13151Program server that runs the computation
INTERFOLD_ADDRESS, E3_PROGRAM_ADDRESS, CIPHERNODE_REGISTRY_ADDRESS, FEE_TOKEN_ADDRESS, CRISP_VOTING_TOKENWritten by the deploymentContract addresses
E3_DURATION300 in the example fileLength of the input window in seconds. On Avail networks, it includes the finalization tail.
VOTING_START_BUFFER_SECONDS20 in the example file. Default 120.Time for the request transaction to be mined before the input window starts
E3_PARAM_SET00 is insecure-64, 2 is secure-8192. Ethereum mainnet requires 2.
E3_COMMITTEE_SIZE00 is minimum (N = 3, T = 1), 1 is micro (N = 9, T = 4), 2 is small (N = 19, T = 9)
E3_COMPUTE_PROVIDER_NAME, E3_COMPUTE_PROVIDER_PARALLEL, E3_COMPUTE_PROVIDER_BATCH_SIZEOpenVM, false, 4Compute provider parameters sent with the request. The batch size must be a power of 2.
DATA_AVAILABILITY_MODEmockmock or avail. Without a value, the mode is mock for chain IDs 31337 and 1337 and avail for other chains. mock is allowed only on those two chains and on Sepolia with ALLOW_SEPOLIA_MOCK_DATA_AVAILABILITY=true.
AVAIL_RPC_URL, AVAIL_BRIDGE_API_URL, AVAIL_APP_ID, AVAIL_SEEDEmptyAvail access and the funded Avail account that pays for submit_data
AVAIL_PROOF_LEAD_SECONDS10800Minimum time before the input deadline when an Avail publication starts. In Avail mode, it must equal CRISPProgram.availabilityFinalizationWindow().
DATA_AVAILABILITY_MAX_PENDING_BYTES1073741824 (1 GiB)Limit for bytes held by unfinished availability jobs
CRON_API_KEYEmptySecret for POST /rounds/request and the cron client. When it is empty, the endpoint is disabled.
MAINNET_RELAY, RELAY_MAX_INPUTS_PER_SLOT, RELAY_MAX_INPUTS_PER_ROUNDfalse, 3, not setInput relay within per-slot and per-round limits. Mainnet relays only when MAINNET_RELAY is true, which also needs RELAY_MAX_INPUTS_PER_ROUND and RELAY_MIN_BALANCE_ETH.
RELAY_MIN_BALANCE_ETHNot setStop relaying below this ETH amount, which keeps funds for finalizeInput. Required outside mainnet and local chains (0 for no floor). Mainnet relay needs more than 0.
ETHERSCAN_API_KEYEmptyToken holder lookup on non-local chains
INDEX_START_BLOCK, INDEX_CHUNK_SIZE, INDEX_CONTRACTS, INDEX_LOG_CONTRACTSNot setIndexer backfill and the contracts that the read-only /chain/* routes serve. An empty INDEX_CONTRACTS denies all /chain/* reads.

The relay limits in the table apply to one server instance. All server instances use the same server key, but each instance counts only its own relays. Thus, when more than one instance relays, a slot or a round can receive the limit from each instance. To keep one limit for the server key, let one instance relay and set RELAY_MAX_INPUTS_PER_SLOT to 0 on all other instances. Move the relay to another instance only when no round is open. A ballot that a non-relaying instance receives uses the voter's wallet. Also, only the instance that receives a ballot holds its job. Thus, send all client requests to the relaying instance, for example with VITE_INTERFOLD_API. When a limit is reached, the voter's wallet sends the input.

The server records its relays in database/server in its working directory, and its limits count only these records. The server also records the time when this relay record starts. It writes that time when it starts on a database without that time, for example a new database. A round that is already open at that time uses the voter's wallet for all of its inputs, because the server cannot count the relays that occurred before. Keep this directory when you replace or move the server.

⚠️

The local PRIVATE_KEY is a public Anvil test key. Never use it on a live network. Funds sent to its address on a live network can be lost.

Client settings

Vite reads client/.env when the client starts or builds.

VariableLocal valuePurpose
VITE_INTERFOLD_APIhttp://127.0.0.1:4000CRISP server URL
VITE_CHAIN_ID31337Chain that the client asks the wallet to use
VITE_RPC_URLhttp://127.0.0.1:8545Chain RPC
VITE_WALLETCONNECT_PROJECT_IDEmptyWalletConnect. Without it, only injected wallets work.
VITE_CRISP_TOKENWritten by the deploymentVoting eligibility token (MockVotingToken locally)
VITE_E3_REQUESTERSFirst two Anvil accountsComma-separated requester addresses. The client shows rounds from these requesters.
VITE_ENABLE_TEST_TOKEN_MINTtrueShows the + Mint test tokens button on test networks. It is always off on mainnet.

Real proofs with OpenVM

By default, the local scripts run the development runner, which makes no proof. To prove rounds with OpenVM on your machine, build the workers and set the program section of interfold.config.yaml (see Compute Provider):

interfold.config.yaml
program:
  dev: false
  openvm:
    prover_bin: /opt/openvm/interfold-openvm-prover
    prover_bin_cuda: /opt/openvm/interfold-openvm-prover-cuda # optional
    backend: auto # a working GPU when there is one, the CPU otherwise

Then set CRISP_REAL_PROOFS=1 in crisp.dev.env and run pnpm dev:setup again. With it, interfold program compile builds guest/ and its keys, the deployment deploys the receipt verifier for that guest, and interfold program start serves real proofs. A changed program or policy has a new receipt identity, so compile and deploy again after you change either.

Run single components

pnpm dev:up runs all services together. To run them one at a time, open a terminal for each command and run the commands in this order from the root of the CRISP repository:

# 1. Local chain
anvil --host 0.0.0.0 --chain-id 31337 --block-time 1 --mnemonic 'test test test test test test test test test test test junk'
 
# 2. Fulfill randomness requests of the mock provider in a later block
node ./scripts/anvil-randomness.mjs
 
# 3. Deploy the contracts and sync the addresses
./scripts/crisp_deploy.sh
 
# 4. Start and register the ciphernodes. The argument is the ready file.
./scripts/dev_cipher.sh ./.interfold/ready
 
# 5. Program server (port 13151)
./scripts/dev_program.sh
 
# 6. CRISP server (port 4000)
./scripts/dev_server.sh
 
# 7. Client (port 3000)
./scripts/dev_client.sh

Without step 2, the committee is not selected, because the mock randomness provider does not fulfill its own requests.

Scripts

These scripts are defined in package.json of the CRISP repository.

ScriptWhat it does
pnpm dev:setupRuns scripts/setup.sh: installs, builds, and creates the .env files
pnpm dev:upRuns scripts/dev.sh: starts the full local stack
pnpm cliRuns target/debug/cli with the arguments that you give
pnpm compile:circuitsCompiles the Noir circuits and writes the verifiers to packages/crisp-contracts/contracts/verifiers
pnpm compute:vk-hash <preset>Prints the key hash that each fold circuit binds for one BFV preset. Run it after you change a ballot circuit or move to another Interfold release.
pnpm build:runner-imageBuilds the e3-support program runner image from runner/Dockerfile. With --push, it also pushes the image.
pnpm openvm <task>Runs a task of scripts/openvm.sh, for example service-build, service-start, fixture, or guest-parity
pnpm compile:contractsCompiles the CRISP contracts with Hardhat
pnpm build:sdk, pnpm build:presetsBuilds the SDK, or compiles and stages the circuit presets
pnpm deploy:contracts, pnpm deploy:contracts:mock, pnpm deploy:contracts:full, pnpm deploy:contracts:full:mockDeploys the CRISP contracts. mock sets USE_MOCKS=true, which mocks the token and data availability, not compute. full adds the Interfold contracts.
pnpm ciphernode:add, pnpm ciphernode:add:self, pnpm ciphernode:mint:tokensHardhat tasks that register ciphernodes or mint tokens for them
pnpm setup:testnetStarts cn1 to cn5, mints tokens for them, and registers them on Sepolia with the keys in .env. Then it starts the program server and the CRISP server.
pnpm publish:packagesPublishes the CRISP packages to npm (--channel prod or --channel testing)
pnpm lintRuns ESLint

Run the tests

CommandWhat it tests
pnpm test:e2e (also pnpm test)The full stack in a browser. See End-to-end test.
pnpm test:contractsAll CRISP contract tests (Hardhat and Mocha)
pnpm test:contracts:unitCensus modes, journal, input availability, input leaf, Interfold binding, SelfRegistry, and tally decoding
pnpm test:contracts:input-tree, pnpm test:contracts:input-tree:poisoned-parent, pnpm test:contracts:input-tree:canonicalThe end-to-end input tree test, in full or in parts
pnpm test:contracts:ballots:program, pnpm test:contracts:ballots:censusBallots against CRISPProgram, and ballots in an on-chain census round
pnpm test:circuitsnargo test in circuits/lib
pnpm test:sdkSDK tests with Vitest. The script first stages the insecure-64 preset.

End-to-end test

pnpm test:e2e runs scripts/test_e2e.sh. The script starts scripts/dev.sh, waits for port 3000 and the .interfold/ready file, and then runs Synpress and Playwright. The test does these steps:

  1. Requests a round with target/debug/cli init.
  2. Connects MetaMask to the client and waits for the committee key.
  3. Casts a vote and waits until CRISPProgram records it.
  4. Moves the Anvil time past the input deadline.
  5. Waits until the E3 stage is Complete, then checks the result on the All Polls page.

Set CRISP_E2E_DURATION_SECS to change the input window of the test (default 300 seconds). Add --ui (pnpm test:e2e --ui) to run the Synpress wallet setup without --headless. On Linux, the script runs Playwright with xvfb-run. Run pnpm report to open the Playwright report.

Compact the server database

The CRISP server keeps its database in database/server below its working directory. In the crisp-server image, the working directory is /, so the database is /database/server. sled keeps old copies of changed pages on disk until it cleans its log. To recover that space, copy the database while the server is stopped. The copy leaves out the old copies. It can still be up to about three times the size of the current data, because sled writes pages again while it builds the copy.

  1. Stop the CRISP server.

  2. In the server's working directory, run the server binary with compact-database. A source build puts the binary in target/release or target/debug below the root of the CRISP repository:

    server compact-database database/server database/server-compact

    With the image, give the same arguments to the image and mount the volume that holds /database:

    docker run --rm -v <volume>:/database <crisp-server image> compact-database /database/server /database/server-compact
  3. Move database/server to a backup location.

  4. Rename database/server-compact to database/server.

  5. Start the CRISP server.

  6. When the server works correctly, run sync on the host.

  7. Delete the backup.

The command builds the copy in the target path with .partial added, for example database/server-compact.partial. It gives the copy the target name only when the copy is complete, matches the database, and is on the disk. The command stops without a copy when the target directory or the .partial directory exists, when the directory that holds the target does not exist, or when the source directory holds no database. It also stops when the server holds the database, when the copy does not match the database, or when the disk is full. After a failed command, delete the .partial directory before you run the command again.

The operating system can keep the moves of steps 3 and 4 in memory for some time before it writes them to the disk. The server reads from that memory, so a working server does not show that the disk holds the copy. sync makes the operating system write that data to the disk.

Upgrade from a release that keeps ballots in the round record

At its first start, the server moves the ballots out of the round records. An older release cannot read the moved ballots. Before you upgrade:

  1. Stop the server and copy database/server.
  2. If the disk has little free space, compact the database. The first start writes each moved ballot again before it removes the old copy. The command also works on the database of an older release.

To go back to the older release, restore the copy, but only before the new release accepts a ballot. The copy does not hold the availability jobs of later ballots, so their inputs would stay committed but unpublished. After the new release accepts ballots, fix forward. In avail mode, you can also go back when none of the availability jobs of the new release is pending: the older release reads the published ballots again from Avail. In mock mode, the server can read a ballot only from its database. The restored copy does not hold the ballots that the new release accepted, so those ballots are lost.

Common setup errors

SymptomCauseFix
dev_cipher.sh prints ERROR: only <n>/<m> ciphernodes stayed upThe interfold binary does not match the profile, or another node error.Read the node output above the table. For a missing Cargo feature, run pnpm dev:setup again.
Invalid CRISP_BFV_PRESET or Invalid CRISP_SKIP_PROOF_AGGREGATIONA value in crisp.dev.env is not allowed.Use insecure-64 or secure-8192, and true or false.
The server stops at startup with DATA_AVAILABILITY_MODE=mock is allowed only on local development chainsCHAIN_ID is not 31337 or 1337 and the mode is mock.Set DATA_AVAILABILITY_MODE=avail and the Avail settings. On Sepolia, you can set ALLOW_SEPOLIA_MOCK_DATA_AVAILABILITY=true.
The server stops at startup with Ethereum mainnet requires E3_PARAM_SET=2 (secure-8192)CHAIN_ID=1 with E3_PARAM_SET=0.Set E3_PARAM_SET=2.
The server stops at startup with CHAIN_ID (…) does not match the chain of HTTP_RPC_URL (…)HTTP_RPC_URL serves a different chain.Use an RPC for the chain in CHAIN_ID, or correct CHAIN_ID.
The server stops at startup with RELAY_MIN_BALANCE_ETH is required outside local chains…RELAY_MIN_BALANCE_ETH is empty outside mainnet and local chains.Set the balance that finalizeInput needs, or 0 to relay without a floor.
interfold-release.mjs prints SHA-256 is <digest>, interfold-release.json pins <digest>A downloaded release asset does not match its digest in interfold-release.json.Download the asset again. When the digests still differ, correct the digest in interfold-release.json.

Next steps