This repository contains the code and instructions for the Nightfall v3 on Hedera Workshop, which is designed to help developers understand and work with the Nightfall v3 protocol on the Hedera network. The workshop includes a series of scripts that simplify the process of deploying and interacting with the Nightfall v3 smart contracts on Hedera.
If you want to attend the live workshop at the Hedera DevDay 2026, you can follow the instructions in this README and setup eveything in advance. In particular, you can complete the Prerequisites, Initial minimal setup, and Contract deployment and circuits setup sections.
- Hedera Portal Account with both an ECDSA and ED25519 account.
- Ensure each account has at least $10 worth of HBARs on the Hedera testnet to cover transaction fees during the workshop.
- Docker v29+ with at least 2 GB RAM and 25 GB disk space for Docker images and volumes
- Docker Compose v5+
- Node.js v18
- Browser wallet extension (e.g. Rabby Wallet, MetaMask, etc.) configured to connect to the Hedera testnet:
- Network Name: Hedera Testnet
- Chain ID: 296
- Currency Symbol: HBAR
- RPC URL: http://localhost:7546 (we are going to run a local JSON-RPC relay)
- Block Explorer: https://hashscan.io/testnet
- During the workshop we are going to use Visual Studio Code
- Some utility scripts require jq to be installed on your machine
- NVM can be used to install and switch between different Node.js versions
- If you install multiple wallet extensions, configure them so they only access sites when clicked (i.e., extension setting: "This extension can read and change site data → When you click the extension"). This helps avoid wallet conflicts while using the demo UI.
Consider always having the following environment variables configured in your terminals. You can add them to your shell profile if you want, see ./nightfall_3-workshop/examples/.zshrc.example for reference:
# ED25519 key
export NATIVE_ACCOUNT_ID=0.0.xxxx
export NATIVE_ADDRESS=0x000000000...
export NATIVE_PRIVATE_KEY=302e02010....
# ECDSA key
export EVM_ACCOUNT_ID=0.0.xxxx
export EVM_ADDRESS=0xe0b73f....
export EVM_PRIVATE_KEY=0x48b52ab....The native account variables are mostly used in the utility scripts (i.e., ./scripts/create-account, ./scripts/transfer-hbar, etc.) and for the JSON-RPC relay, while the EVM variables are mostly used in the EVM related operations (i.e., contract deployment, execution, etc.).
- Create a folder for the workshop and clone two repositories into it:
mkdir nightfall-on-hedera
cd nightfall-on-hedera
git clone https://github.com/InternetOfPeers/nightfall_3.git
git clone https://github.com/InternetOfPeers/nightfall_3-workshop.gitNote: The InternetOfPeers fork of the Nightfall project contains adaptations for the Hedera network and improvements to the original codebase, and it is developed starting from the aqua_fix_ethsepolia branch of the original repository. That branch in fact contains several months more of fixes when compared to the main branch, although it is not merged back to the main branch yet.
-
Navigate to the
nightfall_3folder -
The default branch is
hederabut in case you are on a different branch, switch to thehederabranch with the following command:git checkout hedera
-
Run the following commands to build the selected images used in the workshop:
nvm install 18 NF_SERVICES_TO_START=client,deployer,optimist,proposer,worker ./bin/setup-nightfall
It would take ~5 minutes to build the selected images and setup the environment.
✔ Image ghcr.io/eyblockchain/nightfall3-worker:latest 24.2s ✔ Image docker-proposer 32.7s ✔ Image ghcr.io/eyblockchain/nightfall3-optimist:latest 25.5s ✔ Image ghcr.io/eyblockchain/nightfall3-deployer:latest 29.1s ✔ Image ghcr.io/eyblockchain/nightfall3-client:latest 26.7s
Note. If you prefer to build all the images for using all Nightfall features, regardless of the workshop, let the
NF_SERVICES_TO_STARTenvironment variable unset or void. In that case, it would take ~15 minutes to build all the images.
To deploy the contracts, you will need an EVM-compatible account on the Hedera network with some HBARs to pay for the deployment fees. You need also a native account to pay in case of Hedera errors (not EVM errors) in the transactions sent by your local JSON-RPC relay. You can create both accounts and get testnet HBARs from the Hedera Portal. Although the deployment is going to cost less than $5, it is suggested to fund each account with at least $10 worth of HBARs to easily cover transaction fees and other tests during the workshop.
You can quickly check your accounts balance with the following command:
./nightfall_3-workshop/scripts/check-balance $EVM_ADDRESS
./nightfall_3-workshop/scripts/check-balance $NATIVE_ACCOUNT_IDIf you need to quickly create a new dedicated account, you can use the following command:
./nightfall_3-workshop/scripts/create-accountIf you need to quickly transfer funds from your $NATIVE_ACCOUNT_ID to fund other accounts, you can use the following command:
./nightfall_3-workshop/scripts/transfer-dollars 10.00 <0.0.xxx or 0x123...>
./nightfall_3-workshop/scripts/transfer-hbar 100 <0.0.xxx or 0x123...>- Open a new terminal and navigate to
nightfall_3-workshopfolder - Check if all the environment variables are available in the new terminal manually or with the following command:
./scripts/check-env-variables. If any variable is missing, export it in the terminal or add it to your shell profile. - Run
./scripts/run-local-json-rpc-relay
Why running a local JSON-RPC relay instead of connecting directly to a public RPC endpoint? A local relay serves multiple purposes: it allows us to sign native transactions locally with our ED25519 key, it can be configured with different parameters and services (web socket, HTTP, caching, etc.), and it can also provide additional logging and debugging capabilities that are useful during development and testing. In addition, free public RPC endpoints like Hashio are usually protected by rate limits, and using a local relay can help us avoid hitting those limits during the workshop activities.
-
Open a new terminal and navigate to the
nightfall_3folder -
Check if all the environment variables are available in the new terminal manually or with the following command:
../nightfall_3-workshop/scripts/check-env-variables
If any variable is missing, export it in the terminal or add it to your shell profile.
-
Ensure the
$EVM_ADDRESSaccount has at least $5 worth of HBARs on the Hedera testnet to cover the deployment fees:../nightfall_3-workshop/scripts/check-balance $EVM_ADDRESS -
Check the configurations in
./bin/hedera-deployment.env(configurations for the deployer script) and./.env.deployment.hedera(configurations for Nightfall services) and update them if needed. For the sake of the workshop, the configurations are ready to use, you don't need to change anything. If in the future you want to experiment with this deployment, I suggest to update at least theMULTISIG_APPROVERSvariable (minimum two accounts) before the deployment so you can control the multisig wallet that will own the contracts after the deployment. -
Deploy the contracts and setup the circuits with the following command:
nvm install 18 ./bin/hedera-deployer $EVM_ADDRESS $EVM_PRIVATE_KEY
This script will take ~10 minutes to deploy and setup the contracts, and to generate the keys for the users. Once the deployment is complete, the deployer should exit succesfully and you should see something similar at the end of the deployer logs:
deployer-1 | [2026-02-09 21:22:19.744] DEBUG: Gas estimated at 36866 deployer-1 | [2026-02-09 21:22:19.745] DEBUG: Submitting transaction from 0xe0b73F64b0de6032b193648c08899f20b5A6141D to 0x187228fAbc1F0e97d083248797fC9eEDfb2F3eE4 with gas 73732 deployer-1 | [2026-02-09 21:22:25.332] DEBUG: Got receipt with transaction hash 0xc148894b98a857f0322d03ac44989009e3e003e15bb9b176bd77b8d812d95913 deployer-1 | [2026-02-09 21:22:25.332] DEBUG: Ownership has been transferred to the Multisig contract: 0x7Dc13FaD0786001d5447fd86aa73cC9bA60f68b4 deployer-1 | [2026-02-09 21:22:25.332] INFO: deployer bootstrap done deployer-1 exited with code 0
-
Detach from the docker logs with
CTRL+C. -
Check the
./docker/volumesfolder. You should see three subfoldersartifacts,build, andproving_filescontaining all the deployed contracts details, the generated keys, the compiled circuits, and the artifacts from the deployment. -
Retrieve the current Hedera block number and set it as the value for the
STATE_GENESIS_BLOCKvariable in./.env.deployment.hederaand./bin/hedera-node.env, manually or with the following command:../nightfall_3-workshop/scripts/set-nightfall-genesis-block ./.env.deployment.hedera ./bin/hedera-node.env
-
Destroy the deployment containers with the following command:
docker compose -p nightfall_3 down --remove-orphans
-
(Optional) Verify the contracts on Hashscan using the script below. This will make easier to follow the transactions and interactions on the Hedera block explorer during the workshop.
cd ../nightfall_3-workshop/contract-verification ./scripts/verify-all-contracts -
Close the current terminal and leave the JSON-RPC relay open, as it will be needed to start Nightfall in the next step.
-
Open a new terminal and navigate to the
nightfall_3folder -
Run the following commands to start Nightfall and its services:
nvm install 18 source .env.deployment.hedera export NF_SERVICES_TO_START=optimist,worker,client,mongodb,mongo-express export NF_SERVICES_TO_LOG=optimist,worker,client export NO_REMOVE=true ./bin/start-nightfall -l -d
To reduce the noise in the optimist logs, you can toggle the heartbeat logging with the following command:
curl -X POST http://localhost:8081/debug/toggle-heartbeat-logging \
-H "Content-Type: application/json" \
-d '{"heartBeatLogging": "false"}'To check the client's logs more easily, you can open a new terminal and run the following command:
docker logs -f nightfall_3-client-1You can do the same with the optimist container:
docker logs -f nightfall_3-optimist-1-
Open a new terminal and navigate to the
nightfall_3folder -
Configure the
./bin/hedera-node.envfile with the correct values for your deployment. For the sake of the workshop, the configurations are ready to use, you don't need to change anything. -
Run the following commands to start a proposer that will create the batches of transactions to be submitted on the Hedera network:
nvm install 18 source ./bin/hedera-node.env $EVM_ADDRESS $EVM_PRIVATE_KEY http://host.docker.internal:7546 npm run start-proposer
-
Open a new terminal and navigate to the
nightfall_3/demo-uifolder -
Run the following commands to start the demo UI:
nvm install 18 npm ci CONFIRMATIONS=1 npm run start
When you interact with Nightfall you need a ZKP public/private key pair associated to your normal EVM account. Once the funds are sent to the shielded pool, you can use the ZKP Public key of a user to generate the proofs needed to transfer or withdraw the funds. In a real world scenario, users would generate their own ZKP key pair in their wallets and keep the private key safe, but for the sake of simplicity in the workshop, we are going to generate the ZKP keys starting from predefined mnemonics, and we are going to associate those keys to another set of newly generated accounts. Finally, in a real world scenario users should share their public keys with the senders if they want to receive funds; in the demo UI we are going to bypass this step by automatically sharing the ZKP public keys of all the users in the application.
With Nightfall you can decide to move any existing ERC20, ERC721, or ERC1155 token. Fees to the proposer of the block including your transaction are paid in WHBAR for the sake of this workshop, but in a real world scenario the proposer is free to choose to accept other tokens as fees.
Again just for this demo, we are going to transfer WHBAR directly, so the setup and the transactions creation are simpler, but the flows are the same for any other token.
- Navigate to the demo UI at
http://localhost:3000with your browser. - Create two brand new wallet accounts - Carl and Dave - and connect them to the demo.
- Fund both accounts with 100 HBARs.
- Send 80 HBARs from Carl to the WHBAR contract, he will get back the same amount of WHBARs.
- Do the same with Dave.
- Flag the "Use WHBAR contract address" option and the "Connect to: Hedera Testnet" option, and press "Configure". This will update the demo UI to use the correct contract address for the WHBAR token and to connect to the local JSON-RPC relay that we are running, which is connected to the Hedera testnet.
- Select Carl's account in your wallet extension, and add Alice user in the app. Insert your own mnemonic in the input field, or chose one of the predefined from the list. This will generate the ZKP keys for Alice and associate them to Carl's EVM account.
- Switch to Dave's account in your wallet extension, and add Bob user in the app. Insert your own mnemonic in the input field, or chose one of the predefined from the list. This will generate the ZKP keys for Bob and associate them to Dave's EVM account.
- The interface should hide the "Add User" section and now you should see only three options available on the left: Deposit, Transfer, and Withdraw.
- If everything is correct, swapping between Carl and Dave's accounts in the wallet extension should automatically swap between Alice and Bob users in the application.
The final result after the users setup should be the following:
| Wallet User | EVM Account | L1 Balance | Nightfall User | L2 Balance |
|---|---|---|---|---|
| Carl | 0x123... | ~19.9 HBAR + 80 WHBAR | Alice | 0 WHBAR |
| Dave | 0x456... | ~19.9 HBAR + 80 WHBAR | Bob | 0 WHBAR |
Note. Carl and Dave's addresses are disposable and not linked to Nightfall users; they are only used to pay on-chain transaction fees. If you use the demo UI again in the future, you can create or use different EVM accounts and link them to the same Alice and Bob users by inserting the same mnemonics used during the initial setup. Everything will work as before.
The deposit operation - sending money from the L1 to the shielded pool - implies always an on-chain transaction.
- Select Carl's account in your wallet extension. You should notice Alice's account is now selected in the demo UI.
- Deposit 60.00000010 WHBAR (
6000000010). Alice will receive 60 WHBARs in the shielded pool, and 0.00000010 WHBARs (10) will be used to pay the fee for the proposer. - (only the first time) The app will ask to sign the approval for the Nightfall contract to spend Carl's WHBARs, and then the approval transaction will be submitted on the Hedera network. Wait for the transaction to be confirmed.
- The app now ask to sign the actual deposit transaction. Once submitted, nothing seems to happen yet. The transaction is pending but not in a block or on-chain yet.
- Select Dave's account in your wallet extension. You should notice Bob's account is now selected in the demo UI.
- Deposit 50.00000010 WHBAR (
5000000010). Bob will receive 50 WHBARs in the shielded pool, and 0.00000010 WHBARs (10) will be used to pay the fee for the proposer. - Repeat the same steps as for Alice to approve and submit the deposit transaction for Bob.
- Now we have two pending transactions, one for Alice and one for Bob, waiting to be included in a block by the proposer. Instead of waiting for the block to be full and being created, you can speed up the process to force the block creation: press "Make block" button.
- Switch to the Optimist logs. Notice how the transactions are submitted, how the process get the event notifications from the on-chain transactions by the relay, and how the Optimist process waits for confirmation of the first L2 block. Once the L2 block is confirmed, the transactions are finalized and the commitments included in the state.
- Switch to the demo UI: Alice's balance should now show 60 WHBARs (
6000000000), and Bob's balance should show 50 WHBARs (5000000000). If the balances don't update automatically, use the "Fetch Balances" button to trigger a manual update.
The final result after the deposit step should be the following:
| Wallet User | EVM Account | L1 Balance | Nightfall User | L2 Balance |
|---|---|---|---|---|
| Carl | 0x123... | ~19.9 HBAR + 20 WHBAR | Alice | 60 WHBAR |
| Dave | 0x456... | ~19.9 HBAR + 30 WHBAR | Bob | 50 WHBAR |
The transfer operation - sending money from a user to another user inside the shielded pool - does not require any on-chain transaction, but it requires the generation of ZKPs and their submission to the Optimist process, which will verify them and include them in the next block for the proposer to submit, or directly to the proposer depending on the configuration of the system.
In case users cannot reach a proposer or an optimist process directly, they can submit a transfer transaction on-chain. No real contract call is executed in this case, but the transaction calldata is used as a signal and data source for the proposer to understand that there are pending transfers to be included in the next block.
In this demo we are going to use the direct submission to the Proposer (that forward the transactions to the Optimist process), so we can keep eveything private and off-chain.
- Select Carl's account in your wallet extension. You should notice Alice's account is now selected in the demo UI.
- Select the Transfer function, flag the "Offchain (instant transfer)" option, and send 20.00000000 WHBARs (
2000000000) to Bob. Alice will pay 0.00000010 WHBARs (10) as fee. - Notice you are not signing any transaction with your wallet account, because the transaction is created and signed locally in the app with Alice's ZKP private key, and then submitted directly to the Proposer.
- Select Dave's account in your wallet extension. You should notice Bob's account is now selected in the demo UI.
- Select the Transfer function, flag the "Offchain (instant transfer)" option, and send 40.00000000 WHBARs (
4000000000) to Alice. Bob will pay 0.00000010 WHBARs (10) as fee. Please note the fee will be added on top of the transfer amount, so make sure to have enough balance to cover both the transfer and the fee. - Again as before, notice you are not signing any transaction with your wallet account.
- Press the "Make the block" and wait for the block to be confirmed by the Optimist process.
- If the balances don't update automatically, use the "Fetch Balances" button to trigger a manual update.
- Check the balance for both Alice and Bob in the demo UI. Alice should have now 79.99999990 WHBARs (
7999999990), and Bob should have 29.99999990 WHBARs (2999999990). Carl and Dave's balance on the L1 remained the same because they didn't spend any fund. Transaction fees were in fact paid by the Proposer. - (Optional) Experiment with another transfer, this time without selecting the "Offchain (instant transfer)" option. This time the app will ask to sign a transaction, the Optimist process will notice that transaction when it will be finalized on-chain and will put that in its mempool. Ask for creating a new block for the Proposer to submit the L2 block and see the balance change after the block confirmation.
The final result after the deposit step should be the following:
| Wallet User | EVM Account | L1 Balance | Nightfall User | L2 Balance |
|---|---|---|---|---|
| Carl | 0x123... | ~19.9 HBAR + 20 WHBAR | Alice | 79.99999990 WHBAR |
| Dave | 0x456... | ~19.9 HBAR + 30 WHBAR | Bob | 29.99999990 WHBAR |
The withdraw operation - sending money from the shielded pool to the L1 - can be executed in two ways: with a normal withdraw, which implies an on-chain transaction and a waiting period of 7 days before the funds are actually available on the user's account; or with an instant withdraw, which implies the generation of ZKPs and their submission to the Optimist process. In this case a liquidity provider can be used to get the funds immediately for an additional fee. The Liquidity provider (not configured for this demo) will be able to redeem the withdraw commitment on-chain and will get the funds in exchange of the off-chain fee agreed
- Select Carl's account in your wallet extension. You should notice Alice's account is now selected in the demo UI.
- Select the Withdraw function, set 9.99999980 WHBARs (
999999980) and press "Withdraw". Alice will pay 0.00000010 WHBARs (10) as fee on top of the withdrawal amount, and the app will ask to sign the transaction with Carl's wallet account. - After signing the transaction, the withdraw transaction is submitted on-chain.
- The Optimist process will notice the transaction, and it will add it to the mempool.
- Ask for another block to be created and wait for the transaction to be confirmed on the Hedera network
- After 7 days, Carl can redeem the funds sending a new transaction to the contract.
- On the other side, you can already check Alice's balance in the app and it should be now 10 WHBARs lower.
The final result after the deposit step should be the following:
| Wallet User | EVM Account | L1 Balance | Nightfall User | L2 Balance |
|---|---|---|---|---|
| Carl | 0x123... | ~19.9 HBAR + 20 WHBAR | Alice | 70 WHBAR |
| Dave | 0x456... | ~19.9 HBAR + 30 WHBAR | Bob | 29.99999990 WHBAR |