# AI Agents
Source: https://docs.boundless.network/developers/ai-agents
Use Boundless with AI coding agents like Claude, Cursor, Copilot, and other LLM-powered tools.
AI coding agents can use Boundless more effectively when they have access to structured skill files that describe the product's capabilities, workflows, and common patterns.
Boundless publishes a [skill.md](https://docs.boundless.network/skill.md) file following the [agentskills.io](https://agentskills.io) specification. This file gives agents structured context about:
* **Requestor workflows** — submitting proof requests, configuring offers, tracking fulfillment
* **Prover workflows** — setting up nodes, configuring Bento and Broker, depositing collateral
* **SDK and CLI usage** — common commands, environment variables, storage providers
* **Decision guidance** — when to use Groth16 vs aggregated proofs, onchain vs offchain submission
* **Common gotchas** — free RPC failures, journal size limits, collateral requirements
## Add Boundless to your agent
Install the Boundless skill into your AI agent's context using the [skills CLI](https://www.npmjs.com/package/skills):
```bash theme={null}
npx skills add https://docs.boundless.network
```
This fetches the `skill.md` from Boundless and adds it to your agent's context, so it can help you build on Boundless with accurate, up-to-date guidance.
## Monorepo skills
The [Boundless monorepo](https://github.com/boundless-xyz/boundless) also includes task-specific skills in [`.claude/skills/`](https://github.com/boundless-xyz/boundless/tree/main/.claude/skills) that provide deeper, step-by-step guidance for common workflows:
| Skill | Description |
| ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| [`requesting`](https://github.com/boundless-xyz/boundless/tree/main/.claude/skills/requesting) | Submit a proof request end-to-end — wallet setup, CLI install, deposit, submit, poll, and proof retrieval. |
| [`setup-prover`](https://github.com/boundless-xyz/boundless/tree/main/.claude/skills/setup-prover) | Deploy and manage a Boundless prover on a GPU server using Ansible. |
| [`boundless-cli`](https://github.com/boundless-xyz/boundless/tree/main/.claude/skills/boundless-cli) | Complete reference for the Boundless CLI — requestor, prover, and rewards commands. |
These skills are automatically available to agents working inside the Boundless monorepo (e.g., Claude Code, Cursor). When you clone the repo, your agent can read the skill files directly.
If you're using [Claude Code](https://docs.anthropic.com/en/docs/claude-code), the skills in `.claude/skills/` are loaded automatically when relevant. Just ask Claude to help you set up a prover or submit a proof request.
## What agents can help with
Here are some example prompts to try with your AI agent after adding the Boundless skill:
**For requestors:**
```
Help me submit my first proof request on Boundless using the CLI
```
```
I have a RISC Zero guest program — walk me through requesting a proof on Boundless
```
**For provers:**
```
Help me set up a Boundless prover on my GPU server
```
```
My broker isn't picking up requests — help me troubleshoot
```
**For developers:**
```
How do I use Steel to read EVM state in my guest program?
```
```
What's the difference between Groth16 and aggregated proofs?
```
# Kailua Docs
Source: https://docs.boundless.network/developers/kailua/book
# Kailua GitHub
Source: https://docs.boundless.network/developers/kailua/github
# Proof Lifecycle
Source: https://docs.boundless.network/developers/proof-lifecycle
Following a proof from request to verification
## Introduction
On this page, the entirety of a proof's lifetime in the Boundless Market is covered; starting from a proof request to bid submission and lock in by a prover, through to proof submission and finally to proof verification.
This overview is aimed at app developers i.e., requestors, and therefore for simplicity, it will abstract some complexity away from the specifics of provers. This will be covered in its entirety at a later stage.
## Overview of Proof Lifecycle
1. [Program development: Create a program with the RISC Zero zkVM.](#1-the-app-developer-writes-a-program-for-the-zkvm)
2. [Request Submission: Submit a request with your program, input and offer.](#2-the-app-developer-requestor-broadcasts-a-proof-request-to-the-boundless-market)
3. [Prover Bidding: Provers evaluate and bid on the request.](#3-provers-bid-on-the-request)
4. [Proof Generation: Prover generates the proof.](#4-the-prover-submits-an-aggregated-proof-and-upon-successful-verification-the-reward-is-released-to-the-prover)
5. [Proof Settlement: Market verifies the proof, ensuring it fulfills the request, and releases funds upon success.](#5-the-app-developer-retrieves-their-proof-to-use-in-their-application)
6. [Proof Utilization: Application retrieves and uses the proof in their application.](#6-proof-utilization)
## Detailed View of Proof Lifecycle
### 1. The App Developer Writes a Program for the zkVM
For more info, see [Build A Program](/developers/tutorials/build), and [Getting Started with the zkVM](https://dev.risczero.com/api/zkvm/quickstart).
The app developer begins their Boundless journey by writing an application for the zkVM in Rust. The zkVM provides a zero-knowledge proof of the correct execution of this program. A proof of execution is a receipt; it contains the output― the journal and the cryptographic proof - the seal. The seal is the cryptographic proof. In [zkVM terminology](https://dev.risczero.com/terminology#seal), the seal usually refers to a zk-STARK or SNARK. In Boundless, the seal is a Merkle inclusion proof into an aggregated proof.
With a zkVM program ready, the app developer will require some way to carry out proving. This can be done [locally](https://dev.risczero.com/api/generating-proofs/local-proving), but requires significant hardware investment and maintenance long-term, especially to enable parallelized GPU proving. The Boundless Market allows for anyone to request a proof, regardless of their hardware, they are known as requestors. Boundless handles the connection between requestors and provers i.e., people with dedicated and significant proving hardware, to facilitate proving in a decentralized and permissionless manner.
### 2. The App Developer (Requestor) Broadcasts a Proof Request to the Boundless Market
The app developer will start the proving process by requesting a proof from the Boundless Market.
Requests can be sent onchain, in a transaction to the market contract, or offchain through the order-stream server depending on the user's censorship-resistance requirements. When submitting a request offchain, they should first deposit funds to the market to cover the maximum price in their offer.
The bid matching mechanism used by the market is a [reverse Dutch auction](https://en.wikipedia.org/wiki/Reverse_auction#Dutch_reverse_auctions). An application submits a proof request that contains parameters specifying the program to be proven, the input, and an offer.
#### Offer Details
An offer contains the following:
| Parameter | Description |
| ---------------------------- | ---------------------------------------------------------- |
| **Minimum price** | The lowest price the requestor is willing to pay. |
| **Maximum price** | The highest price the requestor is willing to pay. |
| **Ramp-up start** | Defined as a timestamp. |
| **Length of ramp-up period** | Measured in seconds since the start of the bid. |
| **Lock timeout** | Measured in seconds since the start of the bid. |
| **Timeout** | Measured in seconds since the start of the bid. |
| **Lock collateral** | The slashable amount if a prover fails to deliver a proof. |
For example, an offer might specify:
| Parameter | Example Value |
| ------------------------ | ------------------------------------------ |
| Minimum price | 0.001 Ether (or equivalent in USD via SDK) |
| Maximum price | 0.002 Ether (or equivalent in USD via SDK) |
| Ramp-up start | Now |
| Length of ramp-up period | 300 seconds |
| Timeout | 3600 seconds |
| Lock Timeout | 2700 seconds |
| Lock collateral | 5 ZKC (or equivalent in USD via SDK) |
For more details, please see [Set up the Auction](/developers/tutorials/auction).
#### BoundlessMarket.sol - `submitRequest`
The BoundlessMarket contract has a `submitRequest` [function](https://github.com/boundless-xyz/boundless/blob/5ce8161abdb6f25d0945e2e6afcbba7825005a7e/contracts/src/BoundlessMarket.sol#L157):
```solidity theme={null}
function submitRequest(ProofRequest calldata request, bytes calldata clientSignature);
```
The request parameters are passed through in the `ProofRequest` struct:
```solidity theme={null}
/// @title Proof Request Struct and Library
/// @notice Represents a proof request with its associated data and functions.
struct ProofRequest {
/// @notice Unique ID for this request, constructed from the client address and a 32-bit index.
RequestId id;
/// @notice Requirements of the delivered proof.
/// @dev Specifies the program that must be run, constrains the value of the journal, and specifies a callback required to be called when the proof is delivered.
Requirements requirements;
/// @notice A public URI where the program (i.e. image) can be downloaded.
/// @dev This URI will be accessed by provers that are evaluating whether to bid on the request.
string imageUrl;
/// @notice Input to be provided to the zkVM guest execution.
Input input;
/// @notice Offer specifying how much the client is willing to pay to have this request fulfilled.
Offer offer;
}
```
With Requirements:
```solidity theme={null}
struct Requirements {
Callback callback;
Predicate predicate;
bytes4 selector;
}
```
The predicate refers to a specific constraint on the value of the journal, the public outputs. During proof verification, the image ID and the journal are checked to make sure that the proof fulfills the request. The callback specifies a callback required to be called when the proof is delivered. The selector specifies the required proof type (e.g., Groth16 or Merkle inclusion proof).
### 3. Provers Bid on the Request
After a proof request is broadcast, a reverse Dutch auction runs according to the parameters specified by the offer.
From the moment the request is broadcast, the auction price is the set at the minimum price specified in the offer. During the ramp-up period, the price is increased linearly up to the maximum price. After the ramp-up period, the price stays at the max price until the request expires. At any time, a prover can submit a bid, accepting the current price and winning the auction. Because the price only increases, the first bid is the best price for the requestor.
When the prover submits their bid, the auction ends and they "lock" the request. Once a request is locked, only that prover can be paid for submitting a proof. This ensures that the prover will not waste their compute resources, as they know no other prover can take the request instead. By increasing market efficiency, this lowers prices.
However, locking a request requires the prover to put up collateral, which will be slashed if they fail to deliver a proof before the request timeout. Slashed collateral is used to incentivize other provers to fulfill the request in the case where the locker fails to deliver the proof. Requestors choose the collateral value according to their application's requirements. A low collateral value may result in a better price, however there would be less incentive for another prover to fulfill the request in the case where the locker fails to deliver the proof. A higher collateral value may decrease the chance an unreliable prover will lock the request.
An unlocked request can be directly fulfilled, without first being locked. When this happens, the prover will be paid according to the auction price at that moment. Applications may disable locking entirely by setting the collateral amount impossibly high (e.g., higher than the total supply of the collateral token), ensuring that no prover can lock the request and disincentivizing others from proving.
Provers will calculate the minimum price at which they are willing to prove a request based on a number of factors. These can include:
* Cycle-count of the program execution, determining the proving cost.
* Lock-in collateral required
* Timeout and LockTimeout length, determining how long they have to complete the proof.
* Input and program size, determining bandwidth costs to download the request.
It is also worth noting that the prover's software will download the program, from the request's image URL, and execute it to determine the number of [cycles](https://dev.risczero.com/terminology#clock-cycles) and estimate the proving load.
*If the program fails to execute (e.g., the guest panics) the prover will not bid.*
### 4. The Prover Submits an Aggregated Proof, and Upon Successful Verification, the Reward Is Released to the Prover
To be paid the request reward, and for their lock-in collateral to be returned, the prover must submit the proof prior to the offer's expiration.
#### Provers Batch Proof Requests
The proof submitted by the prover is an aggregated proof, which proves a batch of program executions. Why is Boundless designed like this? The direct goal of aggregation is to amortize the cost of expensive proof verification onchain, both for the requestor and the prover.
Let's start with an assumption: the prover has locked themselves into a number of individual proof requests. A naive implementation would be to have the prover generate a proof for each proof request, send each proof onchain and pay gas for each verification. This would work, but it would cause a lot of needless expense; the prover has to send each individual proof onchain to the market contract. This will rack up expensive gas costs, affecting the efficiency of the market.
Starting with the provers, a clear solution is to batch the proof requests together. Instead of proving one request, sending that proof onchain, and moving onto the next request, the prover can work on multiple proof requests and prove each one without interruption, submitting one aggregated proof for the whole batch.
#### Prover Submits the Requested Proof On-Chain
In order to fulfill a batch of requests, and receive payment, the prover provides an aggregated proof. This aggregated proof is constructed as a Merkle tree, where the leaves are the proofs for the individual requests in the batch. At the root is a single Groth16 proof that attests to the validity of all proofs in the batch. Each individual request has a Merkle inclusion proof linking it to the root. Once the root is verified, the result is cached and the Merkle inclusion proofs are cheap to verify onchain.
When the prover submits the aggregated proof, the Market contract verifies it and pays the prover if all conditions are met. At the same time, the contract emits an event that signals to the requestor that their request is fulfilled, and provides the Merkle inclusion proof.
The requestor can use this Merkle inclusion proof as an effective representation of a verified execution, and use it as such in their application.
### 5. The App Developer Retrieves Their Proof, to Use in Their Application
When the prover fulfills their request, the requestor (the app developer) will receive their proof.
```rust theme={null}
let fulfillment = boundless_client
.wait_for_request_fulfillment(
request_id,
Duration::from_secs(5),
expires_at,
)
.await?;
```
The journal is the public output of the program, and the seal is the cryptographic proof. Since Boundless uses aggregated proofs, the seal will be a Merkle inclusion proof, but verification works the same as for Groth16: the application contract sends the seal to the verifier contract. We recommend using the [RiscZeroVerifierRouter](https://dev.risczero.com/api/blockchain-integration/contracts/verifier), which will allow your application to seamlessly use both Groth16 and Merkle inclusion proofs. Below is an example from the Boundless Foundry Template:
```solidity theme={null}
/// @notice Set the even number stored on the contract. Requires a RISC Zero proof that the number is even.
function set(uint256 x, bytes calldata seal) public {
// Construct the expected journal data. Verify will fail if journal does not match.
bytes memory journal = abi.encode(x);
verifier.verify(seal, imageId, sha256(journal));
number = x;
}
```
### 6. Proof Utilization
Now that the request has been fulfilled the application is ready to grab the proof and use it in an application. Details about how to use a proof can be found in the [Use a Proof](/developers/tutorials/use) section.
# Quick Start
Source: https://docs.boundless.network/developers/quick-start
Build your first guest program and request your first proof on Boundless
## Getting started with an AI agent
Paste this prompt into your AI coding agent (Claude Code, Cursor, Copilot, etc.) to get started:
```text theme={null}
Clone https://github.com/boundless-xyz/boundless, read the skill file at
.claude/skills/requesting/SKILL.md, and walk me through submitting my first
proof request on Boundless.
```
The monorepo includes a [`requesting`](https://github.com/boundless-xyz/boundless/tree/main/.claude/skills/requesting) skill with step-by-step guidance for wallet setup, CLI install, deposit, submit, and proof retrieval. See [AI Agents](/developers/ai-agents) for more.
***
The [Boundless Foundry Template](https://github.com/boundless-xyz/boundless-foundry-template) is the best way to get started. It provides a starter application which has the following components:
* A zkVM guest program, [`is-even`](https://github.com/boundless-xyz/boundless-foundry-template/blob/main/guests/is-even/src/main.rs)
* A smart contract, [`EvenNumber.sol`](https://github.com/boundless-xyz/boundless-foundry-template/blob/main/contracts/src/EvenNumber.sol)
* An example [starter application](https://github.com/boundless-xyz/boundless-foundry-template/blob/main/apps/src/main.rs) which:
* uploads the zkVM guest to IPFS (optional)
* submits a request to the market for a proof that "4" is an even number
* waits for the request to be fulfilled and receives the proof from the Boundless market
* submits the received proof to the `EvenNumber` contract for verification.
For technical support, please post your questions on the [Boundless Discussions Forum](https://github.com/boundless-xyz/boundless/discussions).
## Install RISC Zero
```bash Terminal theme={null}
curl -L https://risczero.com/install | bash
rzup install
```
See more about installation on the [RISC Zero docs](https://dev.risczero.com/api/zkvm/install).
## Clone the repository
### Using Foundry
You can either clone the repo using `git`, but we recommend using `forge init` (you'll need to install [Foundry](https://getfoundry.sh/introduction/installation/)):
```bash Terminal theme={null}
forge init --template https://github.com/boundless-xyz/boundless-foundry-template boundless-foundry-template && cd boundless-foundry-template
```
### Using Git
```bash Terminal theme={null}
git clone https://github.com/boundless-xyz/boundless-foundry-template
cd boundless-foundry-template
git submodule update --init --recursive
```
## Set up your environment variables
Export your Sepolia wallet private key as an environment variable (making sure it has enough funds), and export a valid RPC URL (you can use the public one provided):
```bash theme={null}
export RPC_URL="https://ethereum-sepolia-rpc.publicnode.com"
export PRIVATE_KEY="YOUR_PRIVATE_KEY"
```
You'll also need a deployment of the EvenNumber contract. You can use a predeployed contract on Sepolia:
```bash theme={null}
export EVEN_NUMBER_ADDRESS="0xE819474E78ad6e1C720a21250b9986e1f6A866A3"
```
If you'd like to deploy your own version of the `EvenNumber.sol` contract, please run:
`cargo build && forge script contracts/scripts/Deploy.s.sol --rpc-url ${RPC_URL:?} --broadcast -vv`
after which you can export your deployed contract address to EVEN\_NUMBER\_ADDRESS.
## Run the example app
The [example app](https://github.com/boundless-xyz/boundless-foundry-template/blob/main/apps/src/main.rs) will submit a request to the market for a proof that "4" is an even number, wait for the request to be fulfilled, and then submit that proof to the EvenNumber contract, setting the public number variable to "4".
To run the example using the pre-uploaded zkVM guest:
```bash Terminal theme={null}
RUST_LOG=info cargo run --bin app -- --number 4 --program-url https://plum-accurate-weasel-904.mypinata.cloud/ipfs/QmU7eqsYWguHCYGQzcg42faQQkgRfWScig7BcsdM1sJciw
```
The output will look something like:
```bash Terminal theme={null}
2025-07-01T10:37:08.148174Z INFO app: Number to publish: 4
2025-07-01T10:37:10.167132Z INFO risc0_zkvm::host::server::exec::executor: execution time: 5.667791ms
2025-07-01T10:37:26.144716Z INFO app: Waiting for request 2008ac70b2920c9a345ea7fff1ded1fd4302bdf06516a20c to be fulfilled
```
and the app will query the status of the proof request until it is fulfilled. Once it fulfilled, you'll see output confirming the receipt of the proof, and confirmation of the transaction sent onchain calling the `set` function with the proof as calldata:
```bash Terminal theme={null}
2025-07-01T10:39:30.164558Z INFO app: Request 2008ac70b2920c9a345ea7fff1ded1fd4302bdf06516a20c fulfilled
2025-07-01T10:39:30.164634Z INFO app: Calling EvenNumber set function
2025-07-01T10:39:30.682193Z INFO app: Broadcasting tx 0x067794fbb80a4d67eee593904dc9e6109875efd455b8eedc67609b9d547d5c0d
2025-07-01T10:39:42.529922Z INFO app: Tx 0x067794fbb80a4d67eee593904dc9e6109875efd455b8eedc67609b9d547d5c0d confirmed
2025-07-01T10:39:42.902820Z INFO app: The number variable for contract at address: 0xe819474e78ad6e1c720a21250b9986e1f6a866a3 is set to 4
```
You've now requested your first proof from Boundless, received that proof and sent that proof onchain for verification. Effectively, you've offloaded computation offchain with the same trust model as onchain, all thanks to the power of ZK. This is the true power of verifiable compute.
To learn more about each individual step, please refer to [Build A Program](/developers/tutorials/build). If you want to do some further development on your local fork of the Boundless Foundry Template, please read the [Development](https://github.com/boundless-xyz/boundless-foundry-template?tab=readme-ov-file#development) section on the README.
# Deployments
Source: https://docs.boundless.network/developers/smart-contracts/deployments
Reference for Boundless contract addresses and relevant endpoints.
## *\$ZKC* Token
| Network | \$ZKC Address |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Ethereum Mainnet | `0x000006c2A22ff4A44ff1f5d0F2ed65F781F55555` [(etherscan)](https://etherscan.io/address/0x000006c2A22ff4A44ff1f5d0F2ed65F781F55555) |
| Base Mainnet | `0xAA61bB7777bD01B684347961918f1E07fBbCe7CF` [(basescan)](https://basescan.org/address/0xaa61bb7777bd01b684347961918f1e07fbbce7cf) |
| Taiko Mainnet | `0xC284A781072442cC1882a8Db4573990B7B49DaC4` [(taikoscan)](https://taikoscan.io/address/0xC284A781072442cC1882a8Db4573990B7B49DaC4) |
## Market Contracts
### Base
| Contract Name | Contract Address |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `BoundlessMarket` | `0xfd152dadc5183870710fe54f939eae3ab9f0fe82` [(basescan)](https://basescan.org/address/0xfd152dadc5183870710fe54f939eae3ab9f0fe82) |
| `RiscZeroVerifierRouter` | `0x0b144e07a0826182b6b59788c34b32bfa86fb711` [(basescan)](https://basescan.org/address/0x0b144e07a0826182b6b59788c34b32bfa86fb711) |
| `SetVerifier` | `0x1Ab08498CfF17b9723ED67143A050c8E8c2e3104` [(basescan)](https://basescan.org/address/0x1Ab08498CfF17b9723ED67143A050c8E8c2e3104) |
| `CollateralToken` | `0xaa61bb7777bd01b684347961918f1e07fbbce7cf` [(basescan)](https://basescan.org/address/0xaa61bb7777bd01b684347961918f1e07fbbce7cf) |
### Taiko
| Contract Name | Contract Address |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| `BoundlessMarket` | `0xb3f5c7b4379052eade8c7f3fa6da37fb871da28b` [(taikoscan)](https://taikoscan.io/address/0xb3f5c7b4379052eade8c7f3fa6da37fb871da28b) |
| `RiscZeroVerifierRouter` | `0x607d196b43abc5d9BE3c7Fb8e336Ca82fec18C45` [(taikoscan)](https://taikoscan.io/address/0x607d196b43abc5d9BE3c7Fb8e336Ca82fec18C45) |
| `SetVerifier` | `0x6135DC08D14EF8a44496B009e2181426628B8ebd` [(taikoscan)](https://taikoscan.io/address/0x6135DC08D14EF8a44496B009e2181426628B8ebd) |
| `CollateralToken` | `0xC284A781072442cC1882a8Db4573990B7B49DaC4` [(taikoscan)](https://taikoscan.io/address/0xC284A781072442cC1882a8Db4573990B7B49DaC4) |
## Verifier Contracts
| Network | Verifier Contract Address |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Ethereum Mainnet | `0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319` [(etherscan)](https://etherscan.io/address/0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319) |
| Base Mainnet | `0x0b144e07a0826182b6b59788c34b32bfa86fb711` [(basescan)](https://basescan.org/address/0x0b144e07a0826182b6b59788c34b32bfa86fb711) |
| Taiko Mainnet | `0x607d196b43abc5d9BE3c7Fb8e336Ca82fec18C45` [(taikoscan)](https://taikoscan.io/address/0x607d196b43abc5d9BE3c7Fb8e336Ca82fec18C45) |
## Order Stream
The Order Stream is a service that relays your requests to provers offchain.
When a request is [submitted to the order stream](/developers/tutorials/request#offchain), it is broadcast to all provers.
| Network | Order Stream URL |
| ------------- | ----------------------------------------- |
| Base Mainnet | `https://base-mainnet.boundless.network` |
| Taiko Mainnet | `https://taiko-mainnet.boundless.network` |
# Steel Commitments
Source: https://docs.boundless.network/developers/steel/commitments
A guide to how Steel Commitments work.
Steel retrieves state for a view call at a specific block, therefore it commits the relevant block data to the journal for validation onchain; this data is known as a Steel commitment.
Concretely, a Steel commitment consists of a block identifier, and the block hash. To validate this block commitment onchain, the block hash in the commitment is compared with the block hash available onchain. If there is a discrepancy, there is no guarantee that the proof accurately reflects the correct blockchain state at the specified block.
## Steel's Trust Anchor: The Blockhash
Steel uses [revm](https://docs.rs/revm/latest/revm/) to generate an EVM execution environment, `EvmEnv` within the guest. When you create the `EvmEnv`, you can specify a block (the default is the latest block).
```rust theme={null}
// Create an EVM environment from that provider
let mut env = EthEvmEnv::builder()
.chain_spec(Ð_SEPOLIA_CHAIN_SPEC)
.provider(provider.clone())
.block_number(20842508)
.build()
.await?;
```
This block is used for RPC queries (i.e. `getStorageAt`) during the `preflight` call. Once the preflighting is done, `into_input` is called:
```rust theme={null}
let evm_input = env.into_input().await?;
```
During this `into_input` step, the preflight data is packed into a form that can be read and validated in the guest.
Crucially, during the step, Steel will use the RPC call [eth\_getProof](https://docs.alchemy.com/reference/eth-getproof) for all accounts accessed during the preflight and the return data is combined into a sparse Merkle trie.
Within the guest, calling `into_env` checks that all Merkle tries are consistent, have the correct root and **computes the corresponding block hash.**
*This blockhash is Steel's trust anchor; it needs to be recomputed within the guest to validate the integrity of the RPC data.*
```rust theme={null}
// Read the input from the guest environment.
let input: EthEvmInput = env::read();
let contract: Address = env::read();
let account: Address = env::read();
// Converts the input into a `EvmEnv`
let env = input.into_env(Ð_SEPOLIA_CHAIN_SPEC);
```
For the Steel commitment, it is this block hash, computed within the guest, that is committed to the journal:
```rust theme={null}
// Commit the block hash and number used when deriving `view_call_env` to the journal.
let journal = Journal {
commitment: evm_env.into_commitment(),
tokenAddress: contract,
};
env::commit_slice(&journal.abi_encode());
```
This block hash has to be compared onchain, alongside the verification of the proof, to validate that the Steel proof is correct and to verify the integrity of RPC data.
## What is a Steel Commitment?
A commitment consists of two values: the block ID and the block digest. The block ID encodes two values, the Steel version number and a block identifier (e.g. a block number).
```solidity theme={null}
struct Commitment {
uint256 id;
bytes32 digest;
bytes32 configID
}
```
In the `increment` function in the `Counter` contract, we saw this require statement:
```solidity theme={null}
require(Steel.validateCommitment(journal.commitment), "Invalid commitment");
```
The [Steel library](https://github.com/boundless-xyz/steel/blob/main/contracts/src/Steel.sol) contains the function `validateCommitment` :
```solidity theme={null}
function validateCommitment(Commitment memory commitment) internal view returns (bool) {
(uint240 claimID, uint16 version) = Encoding.decodeVersionedID(commitment.id);
if (version == 0) {
return validateBlockCommitment(claimID, commitment.digest);
} else if (version == 1) {
return validateBeaconCommitment(claimID, commitment.digest);
} else {
revert InvalidCommitmentVersion();
}
}
```
## Validation of Steel Commitments
Steel supports two methods of commitment validation (see `validateCommitment` in [Steel.sol](https://github.com/boundless-xyz/steel/blob/main/contracts/src/Steel.sol)); This validation onchain is essential to ensure that the proof accurately reflects the correct blockchain state.
1. Block hash commitment
```solidity theme={null}
/// @notice Validates if the provided block commitment matches the block hash of the given block number.
/// @param blockNumber The block number to compare against.
/// @param blockHash The block hash to validate.
/// @return True if the block's block hash matches the block hash, false otherwise.
function validateBlockCommitment(uint256 blockNumber, bytes32 blockHash) internal view returns (bool) {
if (block.number - blockNumber > 256) {
revert CommitmentTooOld();
}
return blockHash == blockhash(blockNumber);
}
```
This method uses the `blockhash` opcode to commit to a block hash that is no more than 256 blocks old. With Ethereum's 12-second block time, this provides a window of about 50 minutes to generate the proof and ensure that the validating transaction is contained in a block. This approach will work for most scenarios, including complex computations, as it typically provides sufficient time to generate the proof.
2. Beacon Block Root Commitment
```solidity theme={null}
/// @notice Validates if the provided beacon commitment matches the block root of the given timestamp.
/// @param timestamp The timestamp to compare against.
/// @param blockRoot The block root to validate.
/// @return True if the block's block root matches the block root, false otherwise.
function validateBeaconCommitment(uint256 timestamp, bytes32 blockRoot) internal view returns (bool) {
if (block.timestamp - timestamp > 12 * 8191) {
revert CommitmentTooOld();
}
return blockRoot == Beacon.parentBlockRoot(timestamp);
}
```
The second method allows validation using the [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) beacon roots contract. This technique extends the time window in which the proof can be validated onchain to just over a day. It requires access to a beacon API endpoint and can be enabled by calling `EvmEnv::builder().beacon_api()`. However, this approach is specific to Ethereum (L1) Steel proofs and depends on the implementation of EIP-4788.
Note that EIP-4788 only provides access to the parent beacon root, requiring iterative queries in Solidity to retrieve the target beacon root for validation. This iterative process can result in slightly higher gas costs compared to using the `blockhash` opcode. Overall, it is suitable for environments where longer proof generation times are required.
# Steel Events
Source: https://docs.boundless.network/developers/steel/events
A guide to accessing event data verifiably onchain using Steel Events.
## What are events and why are they useful?
[Smart Contract Events](https://docs.soliditylang.org/en/develop/contracts.html#events) in Solidity allow developers to emit logs containing indexed data, extending the EVM's logging capabilities to offchain applications. Offchain services can subscribe to and listen for these events via the RPC interface.
Events are used extensively in Ethereum for two main reasons:
1. They are the cheapest way to "store" data in Ethereum, which is very useful for bigger data loads.
2. They allow for offchain complex interactions like indexing, filtering, querying, etc.
However, event data is **unusable onchain** by definition. Solidity cannot query event data internally. Currently, the workflow for smart contracts reacting to events is:
* Emit an event.
* Subscribe and listen to the event offchain.
* Initiate an onchain transaction with relevant data in response to the emitted event.
## So why use Steel Events?
Allowing smart contracts to *verifiably* query event data directly onchain significantly streamlines this workflow, unlocking previously impossible trustless applications.
Steel Events enables smart contracts to verify and directly use Ethereum events onchain, eliminating the need for complex event middleware. Leveraging the same [blockhash trust anchor](/developers/steel/commitments#steels-trust-anchor-the-blockhash) and [Steel Commitments](/developers/steel/commitments) scheme, Steel Events ensures provable event data inherits Steel's robust security guarantees, unlocking powerful new use cases:
* *Trustless On-Chain Reactions:* Execute complex logic based on verified aggregations or event patterns (e.g., swap volumes, user activity), without prohibitive gas costs.
* *Secure Bridging of Off-Chain Logic:* Bring existing offchain analytics safely onchain, without modifying your smart contract logic or storage patterns.
* *Cross-Contract Interaction via Events:* Enable Contract B to verifiably react to Contract A's events, even if A has no direct query API.
This unlocks sophisticated, trustless interactions using Ethereum’s inexpensive event logs as secure, verifiable onchain inputs.
## How does Steel Events work?
Steel event syntax allows the developer to query events directly within their guest program. The Steel guest program can specify the event to query using [alloy’s `sol!` Macro](https://alloy.rs/contract-interactions/using-sol!#using-the-sol-macro). For example, we can specify the signature for the ERC20 Transfer event:
```rust theme={null}
sol! {
/// ERC-20 transfer event signature.
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
}
}
```
To query all `Transfer` events in a single block for a specific token contract, we first must specify the contract address:
```rust theme={null}
/// Address of the deployed contract to call the function on (USDT contract on Mainnet).
const CONTRACT: Address = address!("dAC17F958D2ee523a2206206994597C13D831ec7");
```
This allows Steel in the host `preflight` call to populate the EVM environment, within the guest program, with the correct contract data. This EVM environment, in tandem with Merkle storage proofs, is used to verify all relevant data within the guest, and commit the data needed, to verify the blockhash onchain, to the journal. To read about this flow in detail, please refer to [How Does Steel Work](/developers/steel/how-it-works).
### Guest Program
[Example Guest Program](https://github.com/boundless-xyz/steel/tree/main/examples/events/methods/guest/src/main.rs)
The guest program requires the usual Steel workflow:
1. Read the input from the host environment: `let input: EthEvmInput = env::read();`
2. Convert the input into an `EvmEnv` for execution, with the correct chain configuration: `let env = input.into_env(Ð_SEPOLIA_CHAIN_SPEC);`
3. Save the blockhash for the block where we are querying the event: `let event_block_hash = env.header().seal();`
After which, we are ready to query all `Transfer` events in a single block for the specified `CONTRACT`:
```rust theme={null}
// Query all `Transfer` events of the USDT contract.
let event = Event::new::(&env);
let logs = event.address(CONTRACT).query();
```
To grab the total USDT transferred across all the events in the pinned block, we iterate through each log, grab the `value` and `sum` them all into one uint256 type: total\_usdt
```rust theme={null}
// Process the events.
let total_usdt = logs.iter().map(|log| log.data.value).sum::();
```
And finally, we commit this sum, the [commitment](/developers/steel/commitments) data, and the event block hash to the journal, ready for validation onchain:
```rust theme={null}
// This commits the sum of all USDT transfers in the current block into the journal.
let journal = Journal {
commitment: env.into_commitment(),
blockHash: event_block_hash,
total_usdt,
};
env::commit_slice(&journal.abi_encode());
```
### Host Program
[Example Host Program](https://github.com/boundless-xyz/steel/tree/main/examples/events/host/src/main.rs)
The host program preflights the event query using the `Event::preflight` method. This populates the EVM environment with the relevant data ready for verification in the guest.
```rust theme={null}
// Preflight the event query to prepare the input that is required to execute the function in the guest without RPC access.
let event = Event::preflight::(&mut env);
let logs = event.address(CONTRACT).query().await?;
log::info!(
"Contract {} emitted {} events with signature: {}",
CONTRACT,
logs.len(),
IERC20::Transfer::SIGNATURE,
);
// Construct the input from the environment.
let evm_input = env.into_input().await?;
```
## Running the Events Example
To get started with events, you can use the [Steel Events example](https://github.com/boundless-xyz/steel/tree/main/examples/events):
```shell theme={null}
git clone https://github.com/boundless-xyz/steel.git && cd steel/examples/events
```
To run the example:
```shell theme={null}
RPC_URL=https://ethereum-rpc.publicnode.com RUST_LOG=info cargo run --release
```
This should give you something like this:
```shell theme={null}
Environment initialized with block 22144240
Executing preflight querying event 'Transfer(address,address,uint256)'
Contract 0xdAC17F958D2ee523a2206206994597C13D831ec7 emitted 36 events with signature: Transfer(address,address,uint256)
Total USDT transferred in block 0x6fc043151df77c16fbed28c9332641d830c6ac494e53778fd6ad42dd38ffbb85: 126914297072
```
# Steel History
Source: https://docs.boundless.network/developers/steel/history
How far can you go back in history with Steel?
## Why use Steel history?
As Steel executes a view call, it ensures integrity of the EVM state relative to a block hash or beacon block root contained in a *Steel commitment*.
When verifying a Steel call onchain, it is critical to verify the commitment, and this generally restricts how far back a Steel query can read.
* Block hash commitments are verified with the `blockhash` opcode, which has a context window of 256 blocks.
Only Steel calls against one of the last 256 blocks (approximately \~50 minutes with 12 second block time) can be verified with this method.
* Beacon block commitments, when using L1 Ethereum, are verified with the \[EIP-4788] beacon roots contract.
This technique extends the validation time to just over 24 hours.
See the [Steel Commitments](/developers/steel/commitments) page for further information.
This age limit is a consequence of the way Ethereum-based blockchains store relevant block data.
For the developer, this means that, **by default, Steel proofs verified onchain can reference data no more than 24 hours old**.
To use older view call data, the Steel library has a history feature.
## Overview
Steel history allows the developer to query view call state older than 24 hours while still using the same Steel commitment mechanism.
This is done by separating the pinned block in Steel into two separate blocks: the execution block and the commitment block.
When using Steel history, the developer must specify both the execution block *and* the commitment block:
```rust theme={null}
let builder = EthEvmEnv::builder()
.provider(&provider)
.block_number(latest - 8191) // execution block
.beacon_api(beacon_api_url)
.commitment_block_number(latest - 1) // Steel commitment block
.chain_spec(Ð_MAINNET_CHAIN_SPEC);
let mut env = builder.build().await?;
// Preflight the call at the execution block.
let mut contract = Contract::preflight(token_contract, &mut env);
```
The execution block is the block from which the view call state is retrieved
(i.e. it is the block at which the call will be executed).
The commitment block is the block used for the Steel commitment.
The commitment block has to fall within the 24 hour time window necessary for Steel commitment validation onchain, but *crucially* the execution block can go further back on the scale of days, weeks or even months.
## How does Steel history work?
The execution and commitment blocks are fundamentally related; the execution block should always be an ancestor of the commitment block. Therefore, it is possible to prove that the committed chain includes the execution block by validating a chain of beacon block roots in between the two blocks.
Steel history works backwards from the commitment block to the execution block with consecutive calls to the beacon roots contract;
validating a beacon root is a single call for every 24 hours of history.
This step takes approximately 1M cycles per 24 hours of history within the Steel guest.
Ultimately, Steel will check the integrity of the view call data in the execution block by proving that the execution block is a canonical ancestor of the commitment block.
Once onchain, successfully validating the Steel commitment will prove the integrity of the block root for the commitment block.
## How far can you go back?
There is a hard limit on how far back in time you can place the execution block:
the entire validation procedure depends on [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) which was introduced with the [Cancun upgrade](https://ethereum.org/en/history/#cancun-summary) on March 13 2024;
this is the furthest that Steel history can go back.
## How much does Steel history cost?
For the host, the developer needs to specify valid RPC URLs for both an archive execution node and a beacon node.
You can see an example in the [publisher.rs](https://github.com/boundless-xyz/steel/tree/main/examples/erc20-counter/apps/src/bin/publisher.rs) CLI args for the [erc20-counter](https://github.com/boundless-xyz/steel/tree/main/examples/erc20-counter) example.
The greatest API cost will likely be from the beacon API endpoint.
For each block between the commitment block and the execution block, Steel will query the full beacon block for verification.
Please bear in mind that the wider the gap between the execution block and the commitment block, the larger the load on the beacon endpoint will be.
In terms of compute, the number of cycles for the Steel guest will also increase linearly with the number of blocks between the commitment block and the execution block.
For every extra 24 hours of history, this is just under 500,000 cycles in the Steel guest. .
## Using Steel History
**To see example code, please see the [publisher app](https://github.com/boundless-xyz/steel/tree/main/examples/erc20-counter/apps) for the erc20-counter example which has been updated to support Steel history.**
# How does Steel work?
Source: https://docs.boundless.network/developers/steel/how-it-works
A guide to how Steel works.
A fundamental operation in smart contracts is to look up data from other contracts, such as the ERC20 token balance of a specific address. This operation is known as a “view call” - it “views” state without altering it. Steel allows developers to query EVM state, within the zkVM, by just defining the Solidity method they wish to view call (using alloy’s [sol! macro](https://alloy.rs/contract-interactions/using-sol%21/)).
```rust theme={null}
alloy::sol!(
interface IERC20 {
function balanceOf(address account) external view returns (uint);
}
);
```
*This code is taken from the erc20-counter example, which you can find [here](https://github.com/boundless-xyz/steel/tree/main/examples/erc20-counter).*
The sol! macro parses Solidity syntax to generate Rust types; this is used to call the `balanceOf` function, within the [guest program](https://dev.risczero.com/terminology#guest-program), using `balanceOfCall`:
```rust theme={null}
// GUEST PROGRAM
// Read the input from the guest environment.
let input: EthEvmInput = env::read();
let contract: Address = env::read();
let account: Address = env::read();
let evm_env = input.into_env(Ð_SEPOLIA_CHAIN_SPEC);
// Execute the view call; it returns the result in the type generated by the `sol!` macro.
let call = IERC20::balanceOfCall { account };
let balance = Contract::new(contract, &evm_env)
.call_builder(&call)
.call();
// Check that the given account holds at least 1 token.
assert!(balance >= U256::from(1));
// Commit the block hash and number used when deriving `view_call_env` to the journal.
let journal = Journal {
commitment: evm_env.into_commitment(),
tokenAddress: contract,
};
env::commit_slice(&journal.abi_encode());
```
## Proving smart contract execution within the zkVM
The zkVM guest has no network connection, and there is no way to call an RPC provider to carry out the view call from within the guest; so how does Steel make this possible?
Steel’s key innovation is the use of [revm](https://docs.rs/revm/latest/revm/) for simulation of an *EVM environment* within the guest program. This EVM environment has the necessary state populated from RPC calls, and verified with Merkle storage proofs, to carry out verifiable execution of view calls. In the [host program](https://dev.risczero.com/terminology#host-program), the [preflight call](https://risc0.github.io/risc0-ethereum/risc0_steel/struct.Contract.html#method.preflight) constructs the EVM environment, `evm_env` which is passed through as input to the guest program:
```rust theme={null}
// HOST PROGRAM
// Create an alloy provider from RPC URL
let provider = ProviderBuilder::new()
.connect_http(eth_rpc_url);
// Create an EVM environment from that provider defaulting to the latest block.
let mut env = EthEvmEnv::builder()
.chain_spec(Ð_SEPOLIA_CHAIN_SPEC)
.provider(provider.clone())
.build()
.await?;
// Preflight the call to prepare the input that is required to execute the function in the guest without RPC access.
let mut contract = Contract::preflight(token_contract, &mut env);
let evm_input = env.into_input().await?;
```
The `preflight` step calls the RPC provider for the necessary state and for the Merkle storage proofs via `eth_getProof` ([EIP-1186](https://eips.ethereum.org/EIPS/eip-1186)). These Merkle proofs are given to the guest which verifies them to prove that the RPC data is valid, without having to run a full node and without trusting the host or RPC provider.
## Verifying the proof onchain
At this point, we have generated a proof of: a view call of state onchain and some execution based on that view call state (e.g. checking that the balance is at least 1).
When using Steel, the general pattern for onchain functions incorporating Steel follows this pseudo-code:
```solidity theme={null}
contract {
function doSomething(journalData, proof) {
validate journal data
validate Steel commitment
verify proof
doSomethingElse()
}
}
```
The interesting onchain logic, *doSomethingElse()*, is only reached if the journal data, the steel commitment and the proof are all valid.
Concretely, in the erc20-counter example, the counter is only updated if the caller has a balance of at least one, and this counter update is gated by Steel and the zkVM.
```solidity theme={null}
contract Counter {
function increment(bytes calldata journalData, bytes calldata seal) external {
// Decode and validate the journal data
Journal memory journal = abi.decode(journalData, (Journal));
require(journal.tokenContract == tokenContract, "Invalid token address");
require(Steel.validateCommitment(journal.commitment), "Invalid commitment");
// Verify the proof
bytes32 journalHash = sha256(journalData);
verifier.verify(seal, imageID, journalHash);
// If the balance is at least one, update the counter
counter += 1;
}
}
```
Within a single proof, we’ve seen Steel can handle view calls orders of magnitude larger than onchain execution can handle. Specifically, one partner application has shown gas savings of 1.2 *billion* gas for a contract call using around 400,000 SLOADs. 1.2 billion gas is around 30 *blocks* worth of execution and this can be verified onchain in one proof, that costs under \$10 to generate, and less than 300k gas to verify (see [RISC Zero’s verification contracts](https://dev.risczero.com/api/blockchain-integration/contracts/verifier)).
With proof aggregation, cost savings are amortized even further, by taking multiple separate applications of RISC Zero, and wrapping them all up into a single SNARK.
# Quick Start
Source: https://docs.boundless.network/developers/steel/quick-start
Access smart contract state directly within your guest programs.
The recommended place is to start is [Steel examples](https://github.com/boundless-xyz/steel/tree/main/examples), specifically the [ERC20 Counter](https://github.com/boundless-xyz/steel/tree/main/examples/erc20-counter) example.
The [create-steel-app](https://github.com/boundless-xyz/steel/tree/main/crates/steel/docs/create-steel-app) script will allow you to set up the erc20-counter example locally in one command:
```bash theme={null}
sh <(curl -fsSL https://getsteel.xyz)
```
This example acts as your skeleton project structure for further development. Once the script is finished, you can run through a test workflow with either local proving or proving on Boundless.
Further Steel documentation will guide you through the [ERC20-counter example](https://github.com/boundless-xyz/steel/tree/main/examples/erc20-counter) as a guide to explain Steel in detail.
# What is Steel?
Source: https://docs.boundless.network/developers/steel/what-is-steel
The ZK Coprocessor for EVM apps.
## Steel: The ZK Coprocessor for EVM apps
> "Unbounded EVM computation made simple"
Steel lets Solidity developers effortlessly scale their applications by moving computation offchain without compromising on onchain security. Steel drastically reduces gas costs and this enables previously impossible applications.
Steel pulls state from any EVM chain, performs verifiable computation across multiple blocks offchain, and generates concise execution proofs. Developers simply verify these proofs onchain to access boundless compute, without worrying about gas limits.
A single Steel proof has verified a computation equivalent to 30 Ethereum blocks—saving 1.2 billion gas—generated for under \$10 and verified onchain for under 300k gas.
## Onchain vs offchain execution
Onchain execution is limited by the gas limit per block. This is fine for simple execution, but most real-world applications require significantly more capability than what is currently available, even on layer 2 rollups. With Steel, developers can carry out the same EVM execution they would onchain, but at a much larger scale. This EVM execution is within a boundless and verifiable environment offchain within the zkVM, allowing for an unprecedented amount of scaling for EVM applications.
To describe how Steel replaces onchain execution with onchain verification of smart contract execution proofs, we will walk through a simple example: a counter variable is incremented if, and only if, the ERC20 balance of a certain account is larger than 1.
This example is purely instructive and by simplifying the execution, we can focus on understanding the specifics of Steel.
### Without Steel
```solidity theme={null}
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract OnChainERC20Counter {
address public tokenContract;
uint256 public counter;
function checkBalance(address accountAddress) public view returns (uint256) {
return IERC20(tokenContract).balanceOf(accountAddress);
}
// this function will only update the counter if the account has a valid balance > 1
function increment(address accountAddress) public {
require(checkBalance(accountAddress) > 1, "balance must be greater than 1");
counter += 1;
}
}
```
The `increment` function uses the `checkBalance` function to return ERC20 the current balance of the account, and the require statement makes sure that the counter is only updated if the balance is larger than 1.
### With Steel
```solidity theme={null}
contract OffChainERC20Counter() {
address public tokenContract;
uint256 public counter;
// this function will only update the counter if the account has a valid balance > 1
function increment(bytes calldata journalData, bytes calldata seal) public {
// Decode and validate the journal data
Journal memory journal = abi.decode(journalData, (Journal));
require(journal.tokenContract == tokenContract, "Invalid Token Address");
require(Steel.validateCommitment(journal.commitment), "Invalid Steel Commitment");
// Verify the execution proof
bytes32 journalHash = sha256(journalData);
verifier.verify(seal, imageID, journalHash);
counter += 1;
}
}
```
To make sure that Steel's execution proofs can be trusted, we check the output of the zkVM program to make sure that the token contract address is correct, and we validate the [Steel Commitment](/developers/steel/commitments). Only if these are valid, the proof is verified. Upon successful verification, we can be sure that the account balance is larger than 1 and we increment the counter variable. Notice there is no check onchain of the balance or any EVM execution other than the validations and proof verification. The EVM execution happens within the zkVM guest program.
In [How does Steel work?](/developers/steel/how-it-works), we dive deeper into how exactly the zkVM guest program verifies state access and runs EVM execution, generating a smart contract execution proof, and verifying the proof onchain.
# Boundless CLI
Source: https://docs.boundless.network/developers/tooling/cli
An overview of the Boundless CLI for requestors, provers and claiming rewards.
## Overview
`boundless` is a command-line interface for interacting with the Boundless Market, specifically designed to help requestors and provers with common interactions
## Installation
The Boundless CLI source code can be found at [boundless/crates/boundless-cli](https://github.com/boundless-xyz/boundless/tree/main/crates/boundless-cli).
You'll need to [install Rust](https://doc.rust-lang.org/cargo/getting-started/installation.html), then you can run the following command to install the CLI.
```bash theme={null}
cargo install --locked --git https://github.com/boundless-xyz/boundless boundless-cli --branch release-2.0 --bin boundless
```
## Overview
### What is a module?
Once installed, simply running `boundless` gives a helpful overview:
The CLI has three main modules:
* [Requestor Module](/developers/tooling/cli#requestor): Commands for submitting proof requests
* [Prover Module](/developers/tooling/cli#prover): Commands for locking, executing, proving and fulfilling proof requests
* [Rewards Module](/developers/tooling/cli#rewards): Commands for staking and delegating \$ZKC, claiming staking and mining rewards, seeing recent rewards and more.
### Module setup
Each module has a respective interactive `setup` command which allows you to store *(in plaintext!)* variables such as relevant RPC URLs, private keys etc:
```bash theme={null}
boundless setup
```
These setup commands store secrets in the `~/.boundless/` directory. Any further command calls will first check this location for the relevant secrets, otherwise it will check the flags passed directly to the command.
### Multi-chain support
The `requestor`, `prover`, and `rewards` modules each track their own *active network*. The `setup` wizard prompts you to pick a network and stores the per-chain RPC URL and private key under `~/.boundless/secrets.toml`, keyed by chain. Subsequent commands use the active network for that module.
Supported market networks (used by `requestor` and `prover`):
| Chain ID | Name |
| -------- | ---------------- |
| 8453 | Base Mainnet |
| 167000 | Taiko Mainnet |
| 11155111 | Ethereum Sepolia |
| 84532 | Base Sepolia |
The `rewards` module operates on Ethereum L1 (Mainnet or Sepolia).
To list supported networks for a module and see which one is currently active:
```bash theme={null}
boundless prover networks
```
To switch the active network, pass `--set` with a name or chain ID:
```bash theme={null}
boundless prover networks --set "Taiko Mainnet"
# or
boundless prover networks --set 167000
```
Switching changes which chain the module targets. Per-chain credentials configured during `setup` stay associated with their chains, so you can flip between them without re-entering credentials.
### Command help pages
For a detailed help page for a specific command, run the command with the `--help` flag, for example:
```bash theme={null}
boundless prover deposit-collateral --help
```
which will detail all the relevant options annd mandatory flags:
```bash theme={null}
Usage: boundless prover benchmark [OPTIONS] --request-ids
Options:
--request-ids
Proof request ids to benchmark
-h, --help
Print help (see a summary with '-h')
Prover:
--prover-rpc-url
RPC URL for the prover network
[env: PROVER_RPC_URL=]
--prover-private-key
Private key for prover transactions
[env: PROVER_PRIVATE_KEY]
...
```
## Modules
### Requestor
The requestor module allows requestors to deposit/withdraw funds into the market, submit proof requests, track the status of any proof request, and get and verify proofs from the Boundless market.
To see all available requestor module commands, run:
```bash theme={null}
boundless requestor --help
```
#### Requestor Commands
```bash theme={null}
Usage: boundless requestor [OPTIONS]
Commands:
config Show requestor configuration status
deposit Deposit funds into the market
deposit-to Deposit funds into the market on behalf of another address
withdraw Withdraw funds from the market
deposited-balance Check the balance of an account in the market
balance Check the balance of an account (alias for deposited-balance)
submit-file Submit a fully specified proof request from a YAML file
submit Submit a proof request constructed with the given offer, input, and image
status Get the status of a given request
get-proof Get the journal and seal for a given request
verify-proof Verify the proof of the given request
setup Interactive setup wizard for requestor configuration
networks List supported networks or switch the active network
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
Global Options:
--tx-timeout Ethereum transaction timeout in seconds [env: TX_TIMEOUT=]
--log-level Log level (error, warn, info, debug, trace) [env: LOG_LEVEL=] [default: warn]
Module Configuration:
Run 'boundless requestor setup' for interactive setup
Alternatively set environment variables:
REQUESTOR_RPC_URL RPC endpoint for requestor module
REQUESTOR_PRIVATE_KEY Private key for requestor transactions
BOUNDLESS_MARKET_ADDRESS Market contract address (optional, has default)
SET_VERIFIER_ADDRESS Verifier contract address (optional, has default)
Or configure while executing commands:
Example: boundless requestor balance --requestor-rpc-url --requestor-private-key
```
#### Depositing on behalf of another address
`deposit-to` credits the requestor balance of a different address, with the caller paying for the transaction. Useful for funding a customer or shared requestor account.
```bash Terminal theme={null}
# Deposit 0.01 ETH to 's requestor balance, paid by your wallet.
boundless requestor deposit-to 0.01 --to 0xRecipientAddress
```
Only the recipient (holder of the recipient's private key) can later withdraw those funds.
### Prover
The prover module allows provers to deposit collateral into the market, lock and fulfill orders, carry out benchmarking, manually execute guest programs from the market (useful for debugging), and provides functionality to manually slash an order.
To see all available prover module commands, run:
```bash theme={null}
boundless prover --help
```
#### Prover Commands
```bash theme={null}
Usage: boundless prover [OPTIONS]
Commands:
config Show prover configuration status
deposit-collateral Deposit collateral funds into the market
deposit-collateral-to Deposit collateral funds into the market on behalf of another address
withdraw-collateral Withdraw collateral funds from the market
balance-collateral Check the collateral balance of an account
lock Lock a request in the market
fulfill Fulfill one or more proof requests
execute Execute a proof request using the RISC Zero zkVM executor
benchmark Benchmark proof requests
slash Slash a prover for a given request
setup Interactive setup wizard for prover configuration
networks List supported networks or switch the active network
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
Global Options:
--tx-timeout Ethereum transaction timeout in seconds [env: TX_TIMEOUT=]
--log-level Log level (error, warn, info, debug, trace) [env: LOG_LEVEL=] [default: info]
Module Configuration:
Run 'boundless prover setup' for interactive setup
Alternatively set environment variables:
PROVER_RPC_URL RPC endpoint for prover module
PROVER_PRIVATE_KEY Private key for prover transactions
BOUNDLESS_MARKET_ADDRESS Market contract address (optional, has default)
SET_VERIFIER_ADDRESS Verifier contract address (optional, has default)
Or configure while executing commands:
Example: boundless prover balance --prover-rpc-url --prover-private-key
```
#### Depositing collateral on behalf of another address
`deposit-collateral-to` credits the collateral balance of a different prover address, with the caller paying for the transaction. Useful for shared treasuries, automated top-up services, or staking on behalf of a managed prover.
```bash Terminal theme={null}
# Deposit 50 ZKC of collateral to , paid by your wallet.
boundless prover deposit-collateral-to 50 --to 0xRecipientProverAddress
```
If the collateral token supports EIP-2612 permit (Base ZKC does), the deposit is one transaction; otherwise it's an `approve` + `depositCollateralTo` two-step. Only the recipient can later withdraw their collateral.
### Rewards
The rewards module allows provers to deposit collateral into the market, lock and fulfill orders, carry out benchmarking, manually execute guest programs from the market (useful for debugging), and provides functionality to manually slash an order.
For a full walkthrough on the ZK mining process, see [ZK Mining Overview](/zkc/mining/overview).
To see all available rewards module commands, run:
```bash theme={null}
boundless rewards --help
```
#### Reward Commands
```bash theme={null}
Usage: boundless rewards [OPTIONS]
Commands:
config Show rewards configuration status
stake-zkc Stake ZKC tokens
balance-zkc Check ZKC balance
staked-balance-zkc Check staked ZKC balance
list-staking-rewards List staking rewards by epoch
list-mining-rewards List mining rewards by epoch
prepare-mining Prepare mining work log update
submit-mining Submit mining work updates
claim-mining-rewards Claim mining rewards
claim-staking-rewards Claim staking rewards
delegate Delegate rewards to another address
get-delegate Get rewards delegate
epoch Get current epoch information
power Check reward power and earning potential
inspect-mining-state Inspect mining state file and display detailed statistics
setup Interactive setup wizard for rewards configuration
networks List supported networks or switch the active network
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
Global Options:
--tx-timeout Ethereum transaction timeout in seconds [env: TX_TIMEOUT=]
--log-level Log level (error, warn, info, debug, trace) [env: LOG_LEVEL=] [default: warn]
Module Configuration:
Run 'boundless rewards setup' for interactive setup
Alternatively set environment variables:
REWARD_RPC_URL RPC endpoint for rewards module
REWARD_PRIVATE_KEY Private key for reward transactions
STAKING_PRIVATE_KEY Private key for staking (can differ from reward key)
MINING_STATE_FILE Path to mining state file (optional)
ZKC_ADDRESS ZKC token contract (optional, has default)
VEZKC_ADDRESS Staked ZKC NFT contract (optional, has default)
STAKING_REWARDS_ADDRESS Rewards distribution contract (optional, has default)
BEACON_API_URL Beacon API URL (optional)
Or configure while executing commands:
Example: boundless rewards balance-zkc --reward-rpc-url --staking-private-key
```
## Requesting a Proof via the Boundless CLI
In early testing, and when trying out new order parameters, it can be useful to submit a request via the Boundless CLI.
The Boundless CLI builds upon the [`boundless_market`](https://docs.rs/boundless-market/latest/boundless_market) library.
It covers multiple market interactions such as submitting proof requests, cancelling requests, executing dry runs, requesting the status of a given request, retrieving the journal and seal of a fulfilled request and verifying a proof.
To submit a proof, a valid `request.yaml` is required, this config file will specify the parameters of the request:
* Request ID
* This can be specified, or if set to 0, a random ID will be assigned.
* Requirements:
* The image ID of the program being proven.
* The contents of the outputs of the program, the journal.
This is to make sure the outputs are as expected (e.g., to ensure the right input was provided, by checking an input digest committed to the journal).
* Image URL
* The link to the program stored on any public HTTP server.
IPFS, used through a gateway, works well (Boundless will support IPFS URLs natively in the future).
* Input:
* The input bytes are passed to the program for execution.
The input can have any encoding. The bytes will be passed to the guest without modification.
* Offer:
* This includes the minimum and maximum price for the proof request, the block number to open bidding, the price ramp up period, how many blocks before the request should timeout, and the lock-in stake the prover has to escrow to submit a bid.
Below is an example of a `request.yaml` file that can be used with the `boundless submit request` command.
```yaml request.yaml theme={null}
# Unique ID for this request, constructed from the client address and a 32-bit index.
# Constructed as (address(client) << 32) | index
id: 0 # if set to 0, gets overwritten by a random id
# Specifies the requirements for the delivered proof, including the program that must be run,
# and the constraints on the journal's value, which define the statement to be proven.
requirements:
imageId: "53cb4210cf2f5bf059e3a4f7bcbb8e21ddc5c11a690fd79e87947f9fec5522a3"
predicate:
predicateType: PrefixMatch
data: "53797374"
callback:
addr: "0x0000000000000000000000000000000000000000"
gasLimit: 0
selector: "00000000"
# A public URI where the program (i.e. image) can be downloaded. This URI will be accessed by
# provers that are evaluating whether to bid on the request.
imageUrl: "https://gateway.beboundless.cloud/ipfs/bafkreie5vdnixfaiozgnqdfoev6akghj5ek3jftrsjt7uw2nnuiuegqsyu"
# Input to be provided to the zkVM guest execution.
# The input data is a encoded guest environment.
# See crates/boundless-market/src/input.rs for additional details.
input:
inputType: Inline
data: "0181a5737464696edc003553797374656d54696d65207b2074765f7365633a20313733383030343939382c2074765f6e7365633a20363235373837303030207d"
# Offer specifying how much the client is willing to pay to have this request fulfilled
# Note: prices here are in wei (ETH) and lockCollateral in the smallest ZKC unit (18 decimals).
# When using the Boundless SDK instead of the CLI, prices can be specified in USD
# (e.g., "0.50 USD") and are converted to ETH/ZKC at runtime via the price oracle.
offer:
minPrice: 100000000000000
maxPrice: 2000000000000000
rampUpStart: 0 # if set to 0, gets overwritten by the current UNIX timestamp
rampUpPeriod: 300
timeout: 3600 # 1 hor
lockTimeout: 2700 # 45 minutes
lockCollateral: 5000000000000000000 # 5 ZKC tokens
```
To submit a request, export or create a `.env` file with the following environment variables:
```bash Terminal theme={null}
export RPC_URL="https://ethereum-sepolia-rpc.publicnode.com"
export PRIVATE_KEY="YOUR_SEPOLIA_WALLET_PRIVATE_KEY"
```
Then run the following command:
```bash Terminal theme={null}
RUST_LOG=info boundless request submit request.yaml
```
To wait until the submitted request has been fulfilled, the `--wait` option can be added:
```bash Terminal theme={null}
# [!code word:--wait]
RUST_LOG=info boundless request submit request.yaml --wait
```
And to submit the request to the offchain order-stream service, make a deposit and then run `request submit` with `--offchain`.
```bash Terminal theme={null}
# [!code word:--offchain]
RUST_LOG=info boundless account deposit 0.002 # Enough for the request above; deposit more to cover multiple requests.
RUST_LOG=info boundless request submit request.yaml --wait --offchain
```
# Localnet
Source: https://docs.boundless.network/developers/tooling/localnet
Run a self-contained Boundless stack against a local anvil chain for development.
`just localnet` brings up a throwaway Boundless deployment against a local [anvil](https://book.getfoundry.sh/anvil/) chain. Use it to test requestor flows, broker config, and contract upgrades end-to-end without spending real funds.
The stack runs in Docker and tears down with one command.
## What's in the stack
| Service | Purpose | Host port |
| -------------- | ---------------------------------------------- | ------------ |
| `anvil` | Local EVM (chain ID `31337`) | 8545 |
| `deployer` | Deploys all Boundless contracts to anvil | n/a |
| `postgres` | Backing store for `order-stream` | 5435 |
| `minio` | S3-compatible object store for guest artifacts | 9100 / 9101 |
| `order-stream` | Off-chain order relay (REST API + WebSocket) | 8585 |
| `broker` | Reference prover (dev mode, runs in-stack) | host network |
The `broker` service runs only when `RISC0_DEV_MODE=1`, which is the default. Test proofs are mocked and complete in seconds.
## Prerequisites
You'll need:
* [Docker](https://docs.docker.com/engine/install/) and `docker compose`
* [Foundry](https://book.getfoundry.sh/getting-started/installation) (`forge`, `cast`, `anvil`)
* [Rust](https://www.rust-lang.org/tools/install) (the toolchain pinned in `rust-toolchain.toml`)
* [just](https://github.com/casey/just?tab=readme-ov-file#just)
If your Docker daemon has `"iptables": false` in `/etc/docker/daemon.json`, containers will fail to resolve external DNS during the build. Either remove that flag and run `sudo systemctl restart docker`, or add an explicit `"dns": ["8.8.8.8", "1.1.1.1"]` entry. Without working in-container DNS, `cargo` and `apt-get` inside the build images cannot fetch dependencies.
## First-time host build
Before the first `just localnet up`, build the host artifacts so the deployer container can pick up the assessor guest binary and Solidity bindings:
```bash Terminal theme={null}
forge build
cargo build
```
`forge build` compiles the Solidity contracts and writes ABI artifacts under `out/`. `cargo build` produces the host binaries and triggers the RISC Zero guest build under `target/riscv-guest/`.
On a typical workstation the first `cargo build` takes 10–15 minutes. Subsequent runs are incremental and complete in seconds.
## Bringing it up
From the root of the Boundless repo:
```bash Terminal theme={null}
just localnet up
```
This will:
1. Create `.env.localnet` from `.env.localnet-template` if missing.
2. Build all localnet container images (first run only).
3. Start `anvil`, `postgres`, `minio`, run the `deployer` to deploy contracts and write addresses to `.env.localnet`, then start `order-stream` and the dev `broker`.
4. Wait for every service to become `healthy` before returning.
Once it returns, the stack is ready. Confirm with:
```bash Terminal theme={null}
docker compose -f dockerfiles/compose.localnet.yml \
--profile order-stream --profile dev-broker ps
```
You should see `anvil`, `broker`, `minio`, `order-stream`, and `postgres` as `Up (healthy)`.
## Using the localnet
`just localnet up` populates `.env.localnet` with the values a requestor or prover client needs: RPC URLs, contract addresses, and a funded test wallet. Source it into your shell:
```bash Terminal theme={null}
source .env.localnet
```
The relevant values:
```bash .env.localnet theme={null}
export CHAIN_ID=31337
export RPC_URL="http://localhost:8545"
export ORDER_STREAM_URL="http://localhost:8585"
export RISC0_DEV_MODE=1
export VERIFIER_ADDRESS=...
export SET_VERIFIER_ADDRESS=...
export BOUNDLESS_MARKET_ADDRESS=...
export COLLATERAL_TOKEN_ADDRESS=...
# Funded anvil test wallet
export PRIVATE_KEY="0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6"
export ADDRESS="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
# MinIO storage for guest artifacts
export STORAGE_UPLOADER=s3
export S3_URL=http://localhost:9100
export AWS_ACCESS_KEY_ID=admin
export AWS_SECRET_ACCESS_KEY=password
```
With these set, you can submit a request through the [Boundless CLI](/developers/tooling/cli) the same way you would against a real network. The in-stack broker picks it up and fulfils it in dev mode within seconds.
## Logs and lifecycle
To tail logs across the whole stack:
```bash Terminal theme={null}
just localnet logs
```
To stop the stack while preserving anvil state and MinIO artifacts:
```bash Terminal theme={null}
just localnet down
```
To stop and wipe all volumes and the generated `.env.localnet` (full reset):
```bash Terminal theme={null}
just localnet clean
```
Localnet is for development only. The dev broker runs with `RISC0_DEV_MODE=1`, which produces fake (zero-cycle) proofs that the deployed verifier accepts in dev mode but real RISC Zero verifiers will reject. Do not use this stack to validate proving performance or to run a competitive prover.
# Boundless SDK
Source: https://docs.boundless.network/developers/tooling/sdk
An overview of the Boundless SDK for requestors.
## Overview
The Boundless Market SDK offers high-level Rust APIs for interacting with the Boundless Market smart contracts, preparing and submitting ZK proofs, and handling relevant offchain data such as images and inputs.
[Crate Documentation](https://docs.rs/boundless-market/latest/boundless_market/#boundless-market-sdk)
## Installation
```bash theme={null}
cargo add boundless-market
```
or add manually to your Cargo.toml:
```toml theme={null}
[dependencies]
boundless-market = "X.X.X"
```
where X.X.X is the latest release specified on the [Boundless GitHub Release](https://github.com/boundless-xyz/boundless/releases) page.
## SDK Workflow Overview
Below is an example of the **Boundless** end-to-end programmatic workflow:
### 1. Initialize Client
```rust theme={null}
use boundless_market::Client;
use alloy::signers::local::LocalSigner;
let client = Client::builder()
.with_rpc_url(rpc_url)
.with_private_key(LocalSigner::random())
.build()
.await?;
```
### 2. Upload Program and Input
```rust theme={null}
let program_url = client.upload_program(&std::fs::read("guest.bin")?).await?;
let input_url = client.upload_input(&[0x41, 0x42, 0x43]).await?;
```
### 3. Submit Proof Request
```rust theme={null}
let (request_id, expires_at) = client.submit_request_onchain(&request).await?;
```
### 4. Await Fulfillment
```rust theme={null}
let fulfillment = client
.wait_for_request_fulfillment(request_id, Duration::from_secs(10), expires_at)
.await?;
```
### 5. Fetch Proof Results
```rust theme={null}
// If not using wait_for_request_fulfillment:
let fulfillment = client.boundless_market.get_request_fulfillment(request_id).await?;
// Advanced: Set-Inclusion Receipt
let (journal, receipt) = client.fetch_set_inclusion_receipt(request_id, [0u8; 32].into()).await?;
```
## SDK Modules
### `client`
* `Client`: Core struct for transactions, storage, and offchain interaction.
### `contracts`
* `BoundlessMarketService`: Onchain interactions (requests, fulfillments, deposits).
* `SetVerifierService`: Manages aggregated proof verifications.
* Structures: `ProofRequest`, `Offer`, `Fulfillment`.
### `input`
* `GuestEnv`: Environment for the guest, including input (e.g. `stdin`)
### `order_stream_client`
* `OrderStreamClient`: Submit/fetch orders offchain via WebSocket.
### `storage`
* Uploaders: `S3`, `GCS`, and `Pinata` for uploading program and input data.
* Downloaders: `HTTP`, `S3`, `GCS`, and `File` for downloading programs and inputs (auto-selected based on URL scheme).
### `selector`
* Utilities for tracking/verifying proof types.
## Example: Full Proof Submission
```rust theme={null}
use boundless_market::{
Client, StorageUploaderConfig,
contracts::{FulfillmentData, RequestId, Requirements, Predicate, Offer},
request_builder::OfferParams,
};
use alloy::signers::local::PrivateKeySigner;
use alloy::primitives::U256;
use std::time::Duration;
use url::Url;
async fn proof_submission(
signer: &PrivateKeySigner,
rpc_url: Url,
storage_config: &StorageUploaderConfig,
) -> anyhow::Result<()> {
let client = Client::builder()
.with_rpc_url(rpc_url)
.with_private_key(signer.clone())
.with_uploader_config(storage_config)
.await?
.build()
.await?;
// Build the request.
let request = client.new_request()
.with_program(std::fs::read("guest.bin")?)
.with_stdin(42u32.to_le_bytes());
// Submit the request.
let (request_id, expires_at) = client.submit(request).await?;
let fulfillment = client
.wait_for_request_fulfillment(request_id, Duration::from_secs(10), expires_at)
.await?;
println!("FulfillmentData: {:?}, Seal: {:?}", fulfillment.data()?, fulfillment.seal);
Ok(())
}
```
# Set up the Auction
Source: https://docs.boundless.network/developers/tutorials/auction
Configuring auction parameters for a proof request.
## Overview
The Boundless monorepo has a template [request.yaml](https://github.com/boundless-xyz/boundless/blob/main/request.yaml) which specifies all possible request configuration variables. For testing, developers can use the [Boundless CLI to request a proof](/developers/tooling/cli#requesting-a-proof-via-the-boundless-cli).
### Why is the auction important?
Each request specifies a set of request parameters; these parameters specify the request ID, the proof requirements, the URL for the relevant guest program and its inputs, and *the auction parameters*.
This guide helps requestors understand, configure and optimize their requests via these *auction parameters*. Requestors, who understand these auction parameters, will be able to more effectively tailor their request auctions to current market conditions. This is essential for getting timely and cost-effective proofs.
### What are the auction parameters?
The auction parameters determine how the auction is executed, and therefore how provers respond to the request. Concretely, they specify the parameters of the [reverse Dutch auction](https://en.wikipedia.org/wiki/Reverse_auction#Dutch_reverse_auctions). This is the mechanism by which the requestor and prover can agree upon a price, and helps ensure the requestor receives the best price available from the market.
### Where are these auction parameters specified?
The recommended method to request a proof is via the Boundless SDK (see [Request a Proof](/developers/tutorials/request)). The Boundless SDK sets some sensible defaults which should work for most testing purposes, however it is recommended to adjust the auction parameters via the [`with_offer()` method](/developers/tutorials/request#offer).
It is also possible to [request a proof directly using the CLI](/developers/tooling/cli#requesting-a-proof-via-the-boundless-cli). This requires a valid `request.yaml` file which specifies the auction parameters under `offer`.
### What auction parameters are configurable?
| Parameter | request.yaml name | Units | Description |
| -------------- | ----------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Ramp Up Start | `rampUpStart` | seconds | This parameter specifies the time in seconds (after order creation) that the price starts ramping up from the minimum price (`min_price`) to the maximum price (`max_price`). Between order creation and this timestamp, provers can lock the order at the minimum price. |
| Ramp Up Period | `rampUpPeriod` | blocks | The duration of time period where the auction price increases linearly. Concretely, it is the number of blocks it takes for the price to increase from the minimum price (`min_price`) to the maximum price (`max_price`). |
| Lock Timeout | `lockTimeout` | seconds | This timestamp is specified as an offset from `rampUpStart`. The maximum time a prover has to submit once the ramp up period has begun. Regardless of when the prover locks, they must submit the job onchain prior to this time (i.e. `rampUpStart` + `lockTimeout` after order creation), otherwise they will be slashed. |
| Timeout | `timeout` | seconds | This timestamp is specified as an offset from `rampUpStart`. The total time after which the proof expires. After this time, if no provers have fulfilled the request, the request will stay *unfulfilled*. |
| Min Price | `minPrice` | wei (or USD/ETH via SDK) | The floor price of the auction; this can be set to 0. When using the Boundless SDK, this can be specified as a USD amount (e.g., `"0.40 USD"`) and will be converted to ETH at runtime. |
| Max Price | `maxPrice` | wei (or USD/ETH via SDK) | The ceiling price of the auction; the absolute maximum price the auction will reach. When using the Boundless SDK, this can be specified as a USD amount (e.g., `"1.00 USD"`) and will be converted to ETH at runtime. |
| Collateral | `lockCollateral` | \$ZKC (or USD via SDK) | Provers have to deposit *$ZKC* as [proving collateral](/zkc/collateral). This parameter specifies the exact amount of *$ZKC* a prover will lock as collateral when locking the order. If the prover does not fulfill the request before the primary prover deadline (the lock timeout), this collateral is slashed and 50% of the collateral becomes a bounty for secondary provers to claim upon successful proof fulfillment. When using the Boundless SDK, this can be specified as a USD amount (e.g., `"10 USD"`) and will be converted to ZKC at runtime. |
## Example request walkthrough
This sections walks through a typical proof request from the perspective of a requestor. It uses the recommendations listed [below](/developers/tutorials/auction#setting-optimal-auction-parameters) to set sensible auction parameters. Boundless is an open market and these static recommendations may not reflect the current state of the market. It is recommended to set auction parameters conservatively and adjust them, based on required latency and price, only once these test requests are being fulfilled regularly.
### Estimate relevant times based on program size
For this example request, the guest program being proven is 500MCycles. To calculate the estimated execution and proving times, [this calculator](/developers/tutorials/auction#time-calculator) assumes an average market execution speed of 30MHz and an average market proving speed of 1MHz; these assumptions can be modified in the calculator under "Advanced Settings". Therefore, for 500MCycles, the estimated execution time is \~17 seconds (rounding up) and the estimated proving time is \~500 seconds.
### Setting latency sensitive parameters
Using the table for [suggested latency sensitive parameters](/developers/tutorials/auction#latency-sensitive-parameters), the following parameters are set based on the estimated execution and proving time:
* "Ramp Up Start" is set to `5 * 17s = 85s`.
* "Ramp Up Period" is set to `10 x 17s = 170s ~= 85 blocks` (assuming Base mainnet blocktime is \~2 seconds).
* "Lock Timeout" is set to `1.25 x 500s = 625s`.
* "Timeout" is set to `3 x 500s = 1500s`.
### Setting price sensitive parameters
Using the table for [suggested price sensitive parameters](/developers/tutorials/auction#price-sensitive-parameters), the following parameters are set based on the estimated execution and proving time:
* "Minimum Price" is set to 0.0001 ETH (`100000000000000 wei`), which is around $0.40 with 1 ETH = $4000.
* "Maximum Price" is set to 0.00025 ETH (`250000000000000 wei`), which is around $1 with 1 ETH = $4000. This is about \$0.2 ETH per GCycle.
* "Lock Collateral" is set to `10 x maxPrice ~= $10 worth of ZKC` which is around 20 ZKC at \$0.5 per ZKC. ZKC uses 18 decimal places like ETH, so 20 ZKC is specified with `20000000000000000000`.
### Auction walkthrough
These auction parameters can be set in the [request.yaml](https://github.com/boundless-xyz/boundless/blob/main/request.yaml) file, which is used when [requesting a proof with the CLI](/developers/tooling/cli#requesting-a-proof-via-the-boundless-cli). This is recommended only for testing purposes to get a feel for the sensitivity of each auction parameter on the request fulfillment rate.
Therefore, the example requests auction parameters are:
```bash theme={null}
offer:
minPrice: 100000000000000 # 0.0001 ETH
maxPrice: 250000000000000 # 0.00025 ETH
lockCollateral: 20000000000000000000 # 20 ZKC
rampUpStart: 85 # seconds
rampUpPeriod: 85 # blocks
lockTimeout: 625 # 10 minutes 25 seconds
timeout: 1500 # 25 minutes
```
Once the request is submitted to the market, the auction will follow the auction parameters as specified in this diagram:
* Once the request is processed, and the order is created, the order is surfaced to the market and the auction begins.
* For 85 seconds (specified by `rampUpStart`), the order will be at the minimum price (`minPrice`). This time allows for provers to see the request, and run checks on the request (known as a preflight).During this 85 second period, provers can lock the order at the minimum price (remember that, at any time, any prover locking the order successfully will close the auction).
* At 85 seconds, the "ramp up period" begins. During this timeframe, the auction price grows linearly from the minimum price to the maximum price of 0.00025 ETH, over the span of 85 blocks (\~170 seconds on Base mainnet).
* After the "ramp up period", the price remains at the maximum price until the primary prover deadline (the "lock timeout" of 10 minutes 25 seconds since the ramp up start).
* If the prover who locked the order (the primary prover) does not fulfill by this deadline, they will be slashed. This means that they will lose their proving collateral for this order; 50% of it will be burned and 50% of it will go to a secondary prover.
* After the "lock timeout", a race amongst secondary provers begins. They have until the "timeout" (25 minutes from the ramp up start, or just under 15 minutes from the primary prover deadline) to fulfill the request otherwise the order expires. The first prover to win this race will receive 50% of the order's proving collateral as a reward; this reward is sent directly to the collateral balance of the winning secondary prover.
#### What should requestors know about provers?
Provers can lock a request at any time before the "lock timeout". When a prover locks a request, they are agreeing to be paid the price set at the time of their bid. They are also agreeing to be slashed if they do not fulfill the request before the "lock timeout". In this case, if the request was locked but not fulfilled in time, the order is slashed and the prover loses their "lock collateral". Concretely, 50% of the "lock collateral" is burned and 50% is allocated to the first prover who fulfills the proof, after the "lock timeout" but before the "timeout"; this prover is known as the secondary prover. The reward for the winning secondary prover is sent directly to the collateral balance of the winning secondary prover.
## Setting optimal auction parameters
### Time Calculator
Based on current average execution and proving MHz, this calculator will estimate the execution and proving time of a request:
For the following recommendations, the estimated execution and proving times will be labelled as `estimated_execution_time` and `estimated_proving_time` respectively.
The following two sections detail some recommended guidelines when setting the auction parameters. These guidelines are formulas, and they are to be taken with a pinch of salt. Please remember to start conservatively when tuning each parameter. Provers have to respond and some may set their pricing strategy based on each requestor's standard request profile. Therefore, it is recommended to change auction parameters in small increments during testing, and *only* change auction parameters again once proof fulfillment becomes stable.
### Latency sensitive parameters
Each requestor will have a different latency tolerance; some might be fine with receiving a proof on the scale of hours, whereas others will have more time-critical applications and require proof fulfillment on the scale of minutes. These latency requirements should dictate how requestors set the time-based auction parameters.
| Parameter | Units | Recommendation | Notes |
| -------------- | ------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Ramp Up Start | seconds | 5 x `estimated_execution_time` | This recommended time allows provers to see the order in the market, and increase the chances of provers having a free executor agent to preflight the order. If this is set lower, there is a chance that the request will be locked at a higher price as provers will only be able to bid after the auction has already started. This parameter is important, as the lock timeout and timeout are specified in seconds *after* the ramp up start timestamp. For example, setting it equal to 10 specifies 10 seconds in the future after order creation. If it is set to 0, the current UNIX timestamp, at order creation, will be used. |
| Ramp Up Period | blocks | 10 x `estimated_execution_time` | See [calculator](/developers/tutorials/auction#time-calculator). |
| Lock Timeout | seconds | 1.25 x `estimated_proving _time` | See [calculator](/developers/tutorials/auction#time-calculator). |
| Timeout | seconds | 3 x `estimated_proving_time` | See [calculator](/developers/tutorials/auction#time-calculator). |
### Price sensitive parameters
The price of a request determines how competitively provers bid on the request, and therefore how quickly the request is locked.
| Parameter | Units | Recommendation | Notes |
| --------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Minimum Price | wei (or USD/ETH via SDK) | For testing purposes, set the minimum price to 0 (as of October 2025)). To guarantee the lowest latency in both locking and fulfillment, it is recommended to set the minimum price as non-zero. | As market demand and supply normalizes, competition will stabilize prices at non-zero. When using the SDK, you can specify prices in USD (e.g., `"0.40 USD"`) for stable, exchange-rate-independent pricing. |
| Maximum Price | wei (or USD/ETH via SDK) | It is recommended to test the `maxPrice` parameter by starting low, and bumping the value up only when requests are not locked whatsoever. | Setting a substantial price ensures requests are visible and attractive to provers. For testing purposes, the recommended lowest amount (as of October 2025) for the maximum price is \$0.1 of ETH per GCycle (1 billion cycles). When using the SDK, USD amounts are automatically converted to ETH at runtime. |
| Lock Collateral | wei (*\$ZKC*) (or USD via SDK) | 10 x `maxPrice` | Please note that, as of October 2025, setting the lock collateral to 10 x `maxPrice` may result in some larger requests (50GCycles+) not being locked due to the large amount of *\$ZKC* required as lock collateral. For testing purposes, the recommended lowest amount for the lock collateral is 5 x `maxPrice`; the aim should always be to increase the `lockCollateral` to 10 x `maxPrice` once request fulfillment is stable. When using the SDK, collateral can be specified in USD (e.g., `"10 USD"`) and is converted to ZKC at runtime. |
# Migrating from Bonsai
Source: https://docs.boundless.network/developers/tutorials/bonsai
A practical guide for developers transitioning from Bonsai to Boundless.
For technical support, please post your questions on the [Boundless Discussions Forum](https://github.com/boundless-xyz/boundless/discussions).
## What happened to Bonsai?
Bonsai was RISC Zero's centralized proving service, delivering proofs via an API. As of December 2025, Bonsai is no longer available. Boundless is intended to be a replacement for all your proving needs. If you want to read about the differences between Bonsai and Boundless, please continue on this page. Otherwise, if you have a guest program ready and you are eager to get started, you can follow the [Request a Proof](/developers/tutorials/request) tutorial to create your first proof request on Boundless.
Boundless is the blockchain native way to work with ZK proofs; while the initial setup is more complex than Bonsai's configuration, you gain access to verifiable compute through a highly available, permissionless and self-sustaining proving protocol that aligns with Web3's core principles.
## Key Benefits
* Native proof aggregation amortizes the onchain gas costs for proof verification across the entire Boundless network; you won't have to worry about paying high gas costs for proof verification in your app.
* Strong cryptoeconomic guarantees (through prover slashing and automatic retry logic) on the delivery time of proofs, which is highly configurable via the [auction parameters](/developers/tutorials/auction) parameters.
* The [Smart Contract Requestor](/developers/tutorials/smart-contract-requestor) and [Callback](/developers/tutorials/callbacks) features eliminate the need to build (often extensive) offchain infrastructure for both proof request and proof delivery.
* Build up your app's community by integrating with the industry's only fully open source multi-GPU proving stack.
## Key Differences
While Bonsai offers simplicity through centralization, Boundless provides:
* *Liveness Guarantees*: Decentralized protocol with multiple provers
* *High Availability*: No single point of failure
* *Long-term sustainability*: Community-driven protocol evolution
* *Transparent pricing*: Market-driven proof costs
| Aspect | Bonsai | Boundless |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| *Setup* | 2 environment variables (BONSAI\_API\_URL + BONSAI\_API\_KEY) | Wallet + Storage Provider |
| *Proof Format* | STARK/SNARK (Groth16) | Proof aggregated via SetVerifier |
| *Proof Delivery* | Directly to host program (Offchain) | Onchain |
| *Proof Verification* | Via [receipt.verify](https://docs.rs/risc0-zkvm/latest/risc0_zkvm/struct.Receipt.html#method.verify) or [Verifier Contracts](/developers/smart-contracts/verifier-contracts) | [Verifier Contracts](/developers/smart-contracts/verifier-contracts) |
| *Architecture* | Centralized service | Decentralized protocol |
| *Integration* | Drop-in replacement to guest program | New integration required |
## Getting Started
### Setup
Before you get started, you'll need to set up:
* *Wallet*: Configure a developer wallet for on-chain transactions. For dev testing, something as simple as Metamask will do; make sure to create a new account for your dev testing. You'll also need funds before you can submit a request.
* *Storage Provider*: You'll need to set up either IPFS or S3 for provers to be fulfill your requests. We recommend IPFS via the Pinata gateway, see further instructions [here](/developers/tutorials/request#storage-provider).
### Sending your first proof request
Transitioning to Boundless requires:
* Rearchitecting your proof request and retrieval flow
* Managing proof fulfillment asynchronously
* Handling potential proof request failures or delays
The documentation listed below, along with the [Boundless Foundry Template](https://github.com/boundless-xyz/boundless-foundry-template) and the [Boundless Examples](https://github.com/boundless-xyz/boundless/tree/main/examples), will help you get started in no time.
1. *[Quick Start Guide](/developers/quick-start)*: Set up your first Boundless project and request your first proof with our example app, [Boundless Foundry Template](https://github.com/boundless-xyz/boundless-foundry-template).
2. *[Request a Proof](/developers/tutorials/request)*: Go into more specifics when it comes to requesting a proof with request/offer configuration.
3. *[Proof Tracking](/developers/tutorials/tracking)*: Implement monitoring of requests and retrieving proofs when they are finished
4. *[Use a Proof](/developers/tutorials/use)*: Understand how to use a Boundless proof in your app.
5. *[Boundless Tooling](/developers/tooling/sdk)*: Explore the SDK and CLI to configure your request flow to your specific needs.
### Proof Verification
If you are verifying proofs onchain, Boundless proofs verify in exactly the same manner as proofs delivered via Bonsai, and via the same [Verifier Router contract](/developers/smart-contracts/verifier-contracts) you are used to using. There should be no upgrade required, unless you are using an old version of (i.e. 1.X.X) R0VM.
# Build a Program
Source: https://docs.boundless.network/developers/tutorials/build
Learn how to build a guest program.
## Overview
This page covers the [Boundless Foundry Template](https://github.com/boundless-xyz/boundless-foundry-template/) as a quick start.
At its core, Boundless allows app developers to receive zero-knowledge (ZK) proofs for their programs.
Before [requesting a proof](/developers/tutorials/request), developers should have a program that compiles and executes successfully.
If you want to read more about the zkVM, it is highly recommended to read the following pages on the [zkVM dev docs](https://dev.risczero.com/api):
* [Introduction](https://dev.risczero.com/api)
* [What is a zkVM Program?](https://dev.risczero.com/api/zkvm)
* zkVM
* [Installation](https://dev.risczero.com/api/zkvm/install)
* [Quick Start](https://dev.risczero.com/api/zkvm/quickstart)
* [Hello World](https://dev.risczero.com/api/zkvm/tutorials/hello-world)
For some more intermediate and advanced reading on the zkVM, please refer to:
* [Optimizing your zkVM Program](https://dev.risczero.com/api/zkvm/profiling)
* [zkVM Technical Specification](https://dev.risczero.com/api/zkvm/zkvm-specification)
* [The RISC Zero STARK Protocol](https://dev.risczero.com/proof-system/proof-system-sequence-diagram)
If you have any technical questions about the zkVM, please join [RISC Zero's Discord](https://discord.com/invite/risczero) and ask away. 👋
## Boundless Foundry Template
The [Boundless Foundry Template](https://github.com/boundless-xyz/boundless-foundry-template) builds on RISC Zero's Foundry Template to incorporate Boundless with a simple example app.
It consists of three main parts:
* the zkVM program, [`is-even`](#is-even)
* the application smart contract, [`EvenNumber.sol`](#evennumbersol)
* the application backend, [`app`](#appssrcmainrs)
The example app executes the following steps:
1. Uploads the guest program and creates the proof request.
2. Sends the proof request to Boundless.
3. Retrieves the proof from Boundless.
4. Sends the proof to the application contract.
The zkVM program for this example app is a simple program that takes an input number, checks if it is even and if so, outputs the number to the public outputs of the computation (known as the [journal](https://dev.risczero.com/terminology#journal)).
The entire program is only \~20 lines, so let's run through it:
### [`is-even`](https://github.com/boundless-xyz/boundless-foundry-template/blob/main/contracts/src/EvenNumber.sol)
```rust theme={null}
use std::io::Read;
use alloy_primitives::U256;
use alloy_sol_types::SolValue;
use risc0_zkvm::guest::env;
fn main() {
// Read the input data for this application.
let mut input_bytes = Vec::::new();
env::stdin().read_to_end(&mut input_bytes).unwrap();
// Decode and parse the input
let number = ::abi_decode(&input_bytes).unwrap();
// Run the computation.
// In this case, asserting that the provided number is even.
assert!(!number.bit(0), "number is not even");
// Commit the journal that will be received by the application contract.
// Journal is encoded using Solidity ABI for easy decoding in the app contract.
env::commit_slice(number.abi_encode().as_slice());
}
```
First, the guest receive inputs from the host program, and then these input bytes are decoded and parsed:
```rust theme={null}
fn main() {
// Read the input data for this application. // [!code focus]
let mut input_bytes = Vec::::new(); // [!code focus]
env::stdin().read_to_end(&mut input_bytes).unwrap(); // [!code focus]
// Decode and parse the input // [!code focus]
let number = ::abi_decode(&input_bytes).unwrap(); // [!code focus]
....
}
```
To check if the number is even:
```rust theme={null}
fn main() {
....
// Run the computation. // [!code focus]
// In this case, asserting that the provided number is even. // [!code focus]
assert!(!number.bit(0), "number is not even"); // [!code focus]
....
}
```
Finally, if the number is even, the assert will pass and the number can be committed to the [journal](https://dev.risczero.com/terminology#journal).
```rust theme={null}
fn main() {
....
// Commit the journal that will be received by the application contract. [!code focus]
// Journal is encoded using Solidity ABI for easy decoding in the app contract. [!code focus]
env::commit_slice(number.abi_encode().as_slice()); // [!code focus]
....
}
```
### [`EvenNumber.sol`](https://github.com/boundless-xyz/boundless-foundry-template/blob/main/contracts/src/EvenNumber.sol)
The `EvenNumber` smart contract holds a `uint256` variable called `number`. This number is guaranteed to be even,
however the smart contract itself never checks the number's parity directly.
It verifies a ZK proof of a program that has checked if the number is even. This is done in the `set` function:
```solidity theme={null}
/// @notice Set the even number stored on the contract. Requires a RISC Zero proof that the number is even.
function set(uint256 x, bytes calldata seal) public {
// Construct the expected journal data. Verify will fail if journal does not match.
bytes memory journal = abi.encode(x);
verifier.verify(seal, imageId, sha256(journal));
number = x;
}
```
This `set` function is called with two arguments, a number `x`, and the `seal`.
The number `x` is the number that the ZK proof has proven is even, and it is used to reconstruct the journal (the [journal](https://dev.risczero.com/terminology#journal) refers to the public outputs of the program).
The `seal` is the proof. In [zkVM terminology](https://dev.risczero.com/terminology#seal), the seal usually refers to a zk-STARK or SNARK directly.
In Boundless, the seal is actually a Merkle inclusion proof that the program's proof verification has been included in the Batch Verification Tree. See [Proof Lifecycle](/developers/proof-lifecycle) to read more about this.
#### Proof Verification Using the VerifierRouter Contract
The main logic of this function is carried out with `verifier.verify`:
```solidity theme={null}
/// @notice Set the even number stored on the contract. Requires a RISC Zero proof that the number is even.
function set(uint256 x, bytes calldata seal) public {
// Construct the expected journal data. Verify will fail if journal does not match.
bytes memory journal = abi.encode(x); // [!code focus]
verifier.verify(seal, imageId, sha256(journal)); // [!code focus]
number = x;
}
```
* Verifier
* The `verifier` variable points to the [RISC Zero Verifier Router contract](https://dev.risczero.com/api/blockchain-integration/contracts/verifier). This contract supports verification of seals that are either proofs returned from Boundless or zkSNARKs directly from the zkVM.
* [ImageID](https://dev.risczero.com/terminology#image-id)
* The image ID is a unique identifier for a program in the zkVM. It is checked during proof verification to ensure proof integrity. Concretely, by saving the image ID to an immutable variable in the smart contract on deployment, this makes sure that only proofs from the correct program (in this case, `is-even`) are valid.
* [Journal](https://dev.risczero.com/terminology#journal)
* The journal refers to the public outputs of the program. Similar to the image ID, the journal ensures proof consistency by verifying that the journal in the [receipt claim](https://dev.risczero.com/terminology#receipt-claim) (the seal cryptographically attests to the receipt claim) matches the expected journal.
The `EvenNumber.sol` smart contract has one main function: `set`. This function will update the state `number` *if and only if* a valid ZK proof is verified from the correct zkVM program (identified via the image ID).
### [`apps/src/main.rs`](https://github.com/boundless-xyz/boundless-foundry-template/blob/main/apps/src/main.rs)
As an example of how to request and publish a proof an example app is provided.
It is a CLI that takes a `--number` argument, and does all the work required to set a new even number on the contract.
The example app executes the following steps:
1. Uploads the guest program and creates the proof request.
2. Sends the proof request to Boundless.
3. Retrieves the proof from Boundless.
4. Sends the proof to the application contract.
Instructions for running this example can be found in the [Boundless Foundry Template](https://github.com/boundless-xyz/boundless-foundry-template).
For a detailed walkthrough of this app file, please refer to [Request A Proof](/developers/tutorials/request), where this program is explained in detail.
# Callbacks
Source: https://docs.boundless.network/developers/tutorials/callbacks
Callbacks are a feature of the Boundless Market that allows you to receive notifications when a proof is fulfilled.
## Automatic Proof Delivery
Boundless supports proof delivery to application contracts through callbacks. When requesting a proof, you can specify a contract address that implements the `IBoundlessMarketCallback` interface. When the proof is fulfilled, the Boundless Market will automatically call this contract with the proof data.
The callback contract must implement the following interface:
```solidity theme={null}
interface IBoundlessMarketCallback {
function handleProof(bytes32 imageId, bytes calldata journal, bytes calldata seal) external;
}
```
We provide a template for implementing the `IBoundlessMarketCallback` interface: [BoundlessMarketCallback.sol](https://github.com/boundless-xyz/boundless/blob/main/contracts/src/BoundlessMarketCallback.sol). This implements best practices for implementing the handleProof function.
## Some Callback Considerations
### Boundless does not guarantee the success of callback execution.
Callbacks are executed as part of a try-catch block using the specified gas limit. Successful execution of the callback is not required for a request to be marked as fulfilled. It is important for requestors to set a high enough gas limit to ensure the callback can execute to completion.
### Callbacks have at-least-once delivery semantics.
Proof request submission is permissionless in Boundless. Any user can submit a request that causes any contract's callback to be executed, potentially re-using proofs that have been previously submitted. It is important for callbacks to be robust to multiple invocations.
### Callback invocation does not specify which [`Requirements`](/developers/proof-lifecycle#boundlessmarketsol---submitrequest) were used to generate the proof.
Proofs are submitted to the callback with the journal and seal generated by the prover. When the callback is invoked, it does not specify which [`Requirements`](/developers/proof-lifecycle#boundlessmarketsol---submitrequest) used to generate the proof. It is important for the callback to verify the image ID and journal are as expected before accepting the callback as valid.
## Example: Counter with callback
The [Counter with callback](https://github.com/boundless-xyz/boundless/tree/main/examples/counter-with-callback) example submits a request to the market for a proof that "4" is an even number, and specifies that the proof should be delivered to the Counter contract.
When creating the proof request, the requestor specifies the callback contract address and a gas limit:
```rust theme={null}
let request = client.new_request()
.with_program(program)
.with_stdin(input)
.with_requirements(
RequirementParams::builder()
.callback_address(counter_address)
.callback_gas_limit(100_000)
);
// Submit the request
let (request_id, expires_at) = client.submit(request).await?;
```
Our Counter contract implements the `handleProof` function, which checks if we've already seen this proof, and if not, increments a counter and emits an event:
```solidity [Counter.sol] theme={null}
function _handleProof(bytes32 imageId, bytes calldata journal, bytes calldata seal) internal override {
// Since a callback can be triggered by any requestor sending a valid request to the Boundless Market,
// we need to perform some checks on the proof before proceeding.
// First, the validation of the proof (e.g., seal is valid, the caller of the callback is the BoundlessMarket)
// is done in the parent contract, the `BoundlessMarketCallback`.
// Here we can add additional checks if needed.
// For example, we can check if the proof has already been verified,
// so that the same proof cannot be used more than once to run the callback logic.
bytes32 journalAndSeal = keccak256(abi.encode(journal, seal));
if (verified[journalAndSeal]) {
revert AlreadyVerified();
}
// Mark the proof as verified.
verified[journalAndSeal] = true;
// run the callback logic
count += 1;
emit CounterCallbackCalled(imageId, journal, seal);
}
```
Once the proof is fulfilled, our example checks the counter contract and confirms that the value was incremented by the callback:
```rust theme={null}
alloy::sol! {
#[sol(rpc)]
interface ICounter {
function count() external view returns (uint256);
}
}
let fulfillment =
client.wait_for_request_fulfillment(request_id, Duration::from_secs(5), expires_at).await?;
// We interact with the Counter contract by calling the getCount function to check that the callback
// was executed correctly.
let counter_address = address!("0x000000000000000000000000000000000c0077e5");
let counter = ICounter::ICounterInstance::new(counter_address, client.provider().clone());
let count = counter
.count()
.call()
.await?;
```
# Proof Composition
Source: https://docs.boundless.network/developers/tutorials/proof-composition
Proof composition allows you to build upon existing proofs by verifying them within new zkVM guest programs.
## Overview
For example code of composition in action, please see the [Proof Composition Example](https://github.com/boundless-xyz/boundless/tree/main/examples/composition)
Proof composition enables you to build upon existing proofs by verifying them within new zkVM guest programs. This is particularly useful when you want to prove a sequence of related statements without reproving each step.
For example, let's say you have:
1. A proof that block `n` is valid
2. You want to prove that both blocks `n` and `n+1` are valid
Instead of reproving block `n`, you can:
1. Use the existing proof of block `n`
2. Prove only block `n+1` under the assumption that `n` is valid
3. Resolve this assumption by verifying the previous proof within your new proof
This approach is more efficient than reproving everything from scratch.
## How Composition Works
Proof composition works by:
1. Requesting a raw Groth16 proof from the Boundless Market
2. Using this proof as input to a new zkVM guest program
3. Verifying the proof within the guest program
4. Building upon the verified result to prove new statements
## Example: Composing Echo and Identity Proofs
The [Proof Composition example](https://github.com/boundless-xyz/boundless/tree/main/examples/composition) demonstrates how to compose proofs using the Echo and Identity guest programs.
First, we request a raw Groth16 proof from the Echo guest program:
```rust theme={null}
let mut requirements = Requirements::new(Predicate::digest_match(image_id, journal.digest()));
if groth16 {
requirements = requirements.with_groth16_proof();
}
```
We then use this proof as input to the Identity guest program:
```rust theme={null}
// Build the IDENTITY input from the ECHO receipt
let identity_input = (Digest::from(ECHO_ID), echo_receipt);
let identity_guest_env =
RequestInput::builder().write_frame(&postcard::to_allocvec(&identity_input)?).build_env();
// Request a proof from the Boundless market using the IDENTITY guest
let (identity_journal, identity_seal) =
boundless_proof(&boundless_client, IDENTITY_ELF, identity_guest_env, false)
.await
.context("failed to prove IDENTITY")?;
```
Finally, we can use the composed proof to interact with a smart contract:
```rust theme={null}
alloy::sol! {
#[sol(rpc)]
interface ICounter {
function increment(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) external;
}
}
// Interact with the Counter contract using the composed proof
let counter_address = address!("0x000000000000000000000000000000000c0077e5");
let counter = ICounter::ICounterInstance::new(counter_address, boundless_client.provider().clone());
let journal_digest = B256::from_slice(identity_journal.digest().as_bytes());
let image_id = B256::from_slice(Digest::from(IDENTITY_ID).as_bytes());
let call_increment =
counter.increment(identity_seal, image_id, journal_digest).from(boundless_client.caller());
// Execute the transaction
let pending_tx = call_increment.send().await?;
let tx_hash = pending_tx
.with_timeout(Some(TX_TIMEOUT))
.watch()
.await?;
```
# Proof Types
Source: https://docs.boundless.network/developers/tutorials/proof-types
Boundless supports different types of proof delivery, from efficient merkle inclusion proofs to raw Groth16 proofs for cross-chain verification.
## Default: Merkle Inclusion Proofs
By default, Boundless delivers proofs on-chain as merkle inclusion proofs:
1. Proofs are batched together are aggregated into a single Groth16 proof.
2. The aggregated proof is verified once on-chain
3. Individual proofs are verified through cheap merkle inclusion proofs into this root
This design is what makes Boundless cost-effective for on-chain verification.
## Options: Requesting a Specific Proof Type
While Merkle inclusion proofs are efficient for on-chain verification, there may be cases where you need to access the underlying proof instead of a merkle inclusion proof.
For example:
1. Cross-chain verification where you need to verify the proof on a different chain.
2. Integration with other systems that expect a specific proof type.
3. Custom verification logic that requires the full proof.
### Request a Groth16 Proof
Boundless supports requesting a raw Groth16 proof instead of a merkle inclusion proof. You can specify this in your proof request by setting the `proof_type` to `ProofType::Groth16`:
```rust theme={null}
let request = client.new_request()
.with_program(program)
.with_stdin(input)
.with_groth16_proof(); // Request raw Groth16 proof
```
### Request a Blake3 Groth16 Proof
Blake3 Groth16 proofs are only supported with the `ClaimDigestMatch`
predicate, meaning that you should only use this if you do not require the
journal to be delivered on-chain. Blake3 Groth16 proofs also require the
journal to be of size 32 bytes.
Boundless supports requesting a Blake3 Groth16 proof. This proof type allows for proofs to be verified in environments where SHA2 hashing is impossible or expensive (e.g. BitVM). You can specify this in your proof request by setting the `proof_type` to `ProofType::Blake3Groth16`:
```rust theme={null}
let request = client
.new_request()
.with_program(program)
.with_stdin(input)
.with_blake3_groth16_proof(); // Request Blake3 Groth16 proof
```
## Considerations
When choosing between proof types, consider:
1. **Gas Costs**
* Merkle inclusion proofs are much cheaper to verify on-chain
* Raw Groth16 proofs require full SNARK verification each time. This will increase the price of the proof
2. **Use Case Requirements**
* If you only need on-chain verification, use the default merkle inclusion proof
* If you need cross-chain verification or raw proof data, use Groth16
* If you need to compose the proof by verifying it within another zkVM guest program, use Groth16
* If you don't need the journal on-chain, consider using `ClaimDigestMatch` to save gas (see [Journal Delivery](#journal-delivery-onchain))
* If your journal size exceeds 10KB, use `ClaimDigestMatch` and design your application to store journals off-chain (see [Journal Size Limits](#journal-size-limits))
### Journal Delivery Onchain
When a proof request is fulfilled, the journal can optionally be delivered on-chain. This is controlled by the predicate type you specify in your `Requirement`:
* **`DigestMatch` / `PrefixMatch`** require the journal to be delivered on-chain when the request is fulfilled.
* **`ClaimDigestMatch`** does not require journal delivery. Only the claim digest is verified on-chain.
If your application doesn't need the journal on-chain, using `ClaimDigestMatch` can lead to lower prices since provers won't need to submit potentially large journal data.
#### Journal Size Limits
To prevent griefing attacks where requestors force provers to post expensive amounts of calldata on-chain, there is a **10KB limit** on journal size for on-chain delivery. Provers will ignore requests that require journals larger than 10KB to be posted on-chain.
If your journal exceeds 10KB, use `ClaimDigestMatch` and design your application to store journals off-chain (e.g. via IPFS, a blob storage service, or your own backend).
## Example: Proof Composition using Proof Types
In the [Proof Composition example](https://github.com/boundless-xyz/boundless/tree/main/examples/proof-composition), we demonstrate how to compose a proof from multiple proofs.
Composing a proof requires us to verify a previously generated Groth16 proof within the zkVM guest program. This requires us to request a raw Groth16 proof from the Boundless Market.
In the composition example, we first request a raw Groth16 proof from the Boundless Market using the `ECHO` guest program.
```rust theme={null}
let request = client.new_request()
.with_program(program)
.with_stdin(input)
.with_groth16_proof(); // Request raw Groth16 proof
```
We then provide the Groth16 proof as input to the `IDENTITY` zkVM guest program, and verify the proof.
```rust theme={null}
use risc0_zkvm::guest::env;
use risc0_zkvm::{Digest, Receipt};
fn main() {
let (image_id, receipt): (Digest, Receipt) = env::read();
let claim = receipt.claim().unwrap();
receipt.verify(image_id).unwrap();
....
}
```
# Request a Proof
Source: https://docs.boundless.network/developers/tutorials/request
Request a proof from the Boundless market.
The [Boundless Market SDK](/developers/tooling/sdk) allows developers to build and submit requests to the Boundless protocol; the SDK has sensible defaults, designed to make sending \~95% of requests straightforward.
Therefore, this page is split into two sections:
* The first section, [Sending A Request](#sending-a-request), shows the quickest and easiest way to request a proof using these *sensible defaults*, without any additional configuration.
* The second section, [Request Configuration](#request-configuration), covers all available configuration options for the 5% of requests that require fine-tuning.
The *Sending a Request* section uses the counter example as a template, its source code can be found at: [boundless/examples/counter](https://github.com/boundless-xyz/boundless/tree/main/examples/counter)
## Sending a Request
If you want to submit a one-off request via the Boundless CLI, please see [Requesting a Proof via the Boundless
CLI](/developers/tooling/cli#requesting-a-proof-via-the-boundless-cli).
### 1. Setting environment variables
We recommend using [clap](https://crates.io/crates/clap) to parse these environment variables, as seen in [apps/L37-52](https://github.com/boundless-xyz/boundless/blob/cdc2435b6119a009c2cc73dc227a250bee7594fc/examples/counter/apps/src/main.rs#L37-L52).
#### Blockchain
We recommend using Alchemy for your RPC URL during testing; their free tier is more than enough to test requesting a proof. Receiving proofs requires event queries, which public RPCs may not support.
Since we are submitting requests onchain, we will need private key for a wallet with sufficient funds on Sepolia, and a working RPC URL:
```bash theme={null}
export RPC_URL="https://..."
export PRIVATE_KEY="abcdef..."
```
#### Storage Uploader
For this tutorial, we suggest using a Pinata API key which will upload your program at runtime.
If you do not want to use an API key, or if you want to use a provider other than Pinata (e.g. S3 or GCS), you can pre-upload your program to a public URL (this could be hosted via Pinata or any other service).
To see more information about storage options, please read [Storage Providers](/developers/tutorials/request#storage-providers).
To make a program, and its inputs, accessible to provers, they need to be hosted at a public URL. We recommend using IPFS for storage, particularly via [Pinata](https://pinata.cloud), as their free tier comfortably covers most Boundless use cases. The SDK also supports [S3](/developers/tutorials/request#s3) and [GCS](/developers/tutorials/request#google-cloud-storage-gcs).
Before submitting a request, you'll need to:
* Sign up for an account with [Pinata](https://pinata.cloud).
* Generate an API key following their [documentation](https://docs.pinata.cloud/account-management/api-keys).
* Copy the JWT token and set it as the `PINATA_JWT` environment variable:
```bash theme={null}
export PINATA_JWT="abcdef..."
```
### 2. Build the Boundless Client
```rust theme={null}
let client = Client::builder()
.with_rpc_url(args.rpc_url)
.with_private_key(args.private_key)
.with_uploader_config(&args.storage_config)
.await?
.build()
.await?;
```
### 3. Create and Submit a Proof Request
```rust theme={null}
// Create a request using new_request
let request = client.new_request().with_program(ECHO_ELF).with_stdin(echo_message.as_bytes());
// Submit the request
let (request_id, expires_at) = client.submit(request).await?;
```
### 4. Retrieve the Proof
Once submitted, you can keep track of the request using:
```rust theme={null}
// Wait for the request to be fulfilled. The market will return the fulfillment.
tracing::info!("Waiting for request {:x} to be fulfilled", request_id);
let fulfillment = client
.wait_for_request_fulfillment(
request_id,
Duration::from_secs(5), // check every 5 seconds
expires_at,
)
.await?;
tracing::info!("Request {:x} fulfilled", request_id);
```
This will store the `journal` and `seal` from the Boundless market, together they represent the public outputs of your guest and the proof itself, respectively. You can [use a proof in your application](/developers/tutorials/use) to access the power of verifiable compute using Boundless.
## Request Configuration
### Storage Providers
The Boundless Market SDK supports multiple storage backends for uploading programs and inputs: **IPFS (Pinata)**, **S3**, and **Google Cloud Storage (GCS)**. The SDK uses `StorageUploaderConfig` with clap, so the storage backend is configured via environment variables or CLI flags.
#### IPFS (Pinata)
To use Pinata for IPFS uploads, set the following environment variable:
```bash theme={null}
export PINATA_JWT="abcdef..."
```
The SDK picks the storage backend based on which env vars are set. When `PINATA_JWT` is set, it uses Pinata to upload programs and inputs to IPFS.
#### S3
To use S3 as your storage backend, set the following environment variables:
```bash theme={null}
export S3_BUCKET="bucket-name"
export S3_URL="https://s3.us-east-1.amazonaws.com" # optional, for S3-compatible services
export AWS_ACCESS_KEY_ID="abcdef..." # optional, uses AWS default credential chain if not set
export AWS_SECRET_ACCESS_KEY="abcdef..." # optional, uses AWS default credential chain if not set
export AWS_REGION="us-east-1" # optional, can be inferred from environment
```
Once these are set, this will automatically use the specified [AWS S3 bucket](https://docs.aws.amazon.com/AmazonS3/latest/userguide/creating-buckets-s3.html) for storage of programs and inputs.
By default, the SDK generates S3 presigned URLs that expire after 12 hours. If your request takes longer to fulfill, provers cannot download your program or inputs after expiry. For long-running requests, you have a few options:
* Use IPFS storage instead
* Set `S3_PUBLIC_URL=true` to return public HTTPS URLs (requires a public bucket)
* Set `S3_PRESIGNED=false` to use direct S3 URLs with appropriate bucket policies
#### Google Cloud Storage (GCS)
GCS support requires the `gcs` feature flag: `cargo add boundless-market --features gcs`
To use Google Cloud Storage, set the following environment variables:
```bash theme={null}
export GCS_BUCKET="your-bucket-name"
```
**Authentication** is resolved via the [Google Cloud Application Default Credentials (ADC)](https://cloud.google.com/docs/authentication/application-default-credentials) chain:
1. `GOOGLE_APPLICATION_CREDENTIALS` environment variable pointing to a service account JSON key file
2. Well-known file locations (`~/.config/gcloud/application_default_credentials.json`, set up via `gcloud auth application-default login`)
3. Workload Identity on GKE, metadata server on Compute Engine, etc.
You can also provide credentials directly via `GCS_CREDENTIALS_JSON` when loading from a secrets manager without writing to disk.
**Configuration:**
| Environment Variable | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GCS_BUCKET` | **(Required)** GCS bucket name |
| `GCS_URL` | Custom endpoint URL (for emulators like `fake-gcs-server`) |
| `GCS_CREDENTIALS_JSON` | Service account JSON string (bypasses ADC) |
| `GCS_PUBLIC_URL` | Set to `true` to return public HTTPS URLs (`https://storage.googleapis.com/{bucket}/{key}`) instead of `gs://` URLs. Requires the bucket to be publicly readable. |
For public buckets, set `GCS_PUBLIC_URL=true` so provers can download via standard HTTPS without needing GCS credentials. After each upload, a HEAD request verifies the object is publicly accessible.
#### No Storage Provider
If you don't set any storage-related environment variables, no storage backend is configured. This means you will need to upload your program ahead of time, and provide the public URL. For the inputs, you can also pass them inline (i.e. in the transaction) if they are small enough. Otherwise, you can upload inputs ahead of time as well.
### Uploading Programs
Provers must be able to access your guest program via a publicly accessible URL; the Boundless Market SDK allows you to directly upload your program in a few different ways.
#### Manually
```rust theme={null}
let client = Client::builder()
.with_uploader_config(&args.storage_config)
.await?
.build()
.await?;
let program_url = client.upload_program(program).await?;
```
After which, you'd create a request with:
```rust theme={null}
let request = client.new_request()
.with_program_url(program_url)?
.with_input_url(input_url);
```
If you already have the `program_url`, you do not need to upload the program again; you can simply use `with_program_url` with a hard-coded URL.
#### Automagically
If you are working in a monorepo (i.e. your zkVM host/guest is in the same repo), you can take advantage of [risc0-build](https://docs.rs/risc0-build/latest/risc0_build/) which automatically builds and exposes the ELF for the guest. The counter example uses this method:
```rust theme={null}
// Import ECHO_ELF from your guest code
use guest_util::{ECHO_ELF};
// Create a request using new_request
let request = client.new_request()
.with_program(ECHO_ELF)
.with_stdin(b"Hello, world!");
```
### Inputs
When working with trusted provers, you can store inputs in *Amazon S3* and restrict access via AWS S3's permission management - [Sensitive Inputs
tutorial](/developers/tutorials/sensitive-inputs).
To execute and run proving, the prover requires the inputs of the program. Inputs can be provides as a public URL, or "inline" by including them directly in the request.
Program inputs are uploaded to the same storage provider. This can be done manually like so:
```rust theme={null}
let input_url = client.upload_input(&input_bytes).await?;
```
or if we look back at the counter example, we can see that the inputs are included directly into the request builder:
```rust theme={null}
// Create a request using new_request
let request = client.new_request().with_program(ECHO_ELF).with_stdin(echo_message.as_bytes()); // [!code hl] // [!code focus]
// Submit the request
let (request_id, expires_at) = client.submit(request).await?;
```
In this example, inputs are included inline if they are small (e.g. less than 1 kB) or uploaded to a public URL first if they are large.
When submitting requests onchain with inline inputs, this will cost more gas if the inputs are large. The offchain order-stream service also places limits on the size of inline input.
### Size Limits
Provers enforce limits on file and journal sizes. Requests exceeding these limits are ignored by most provers.
#### File Size Limits
Programs and input files must be under **50MB**. This applies to guest program ELF binaries and input files uploaded to storage providers.
Oversized files would force provers to waste bandwidth and storage, so they reject such requests.
#### Journal Size Limits
Journals delivered on-chain must be under **10KB**. Larger journals would force provers to post expensive calldata, so they reject such requests.
If your journal exceeds 10KB, use the `ClaimDigestMatch` predicate and store journals off-chain. See [Journal Size Limits](/developers/tutorials/proof-types#journal-size-limits) for details.
### Proof Types
By default, the Boundless SDK requests [aggregated proofs](/developers/tutorials/use). However, you can also request Groth16 proofs, which are SNARK proofs that are highly efficient for onchain verification.
#### Requesting a Groth16 Proof
To request a Groth16 proof instead of the default aggregated proof, use the `with_groth16_proof()` method when building your request:
```rust theme={null}
// Request an un-aggregated proof from the Boundless market using the ECHO guest.
let echo_request = client
.new_request()
.with_program(ECHO_ELF)
.with_stdin(echo_message.as_bytes())
.with_groth16_proof(); // [!code hl] // [!code focus]
// Submit the request
let (request_id, expires_at) = client.submit(echo_request).await?;
```
For a complete working example of requesting a Groth16 proof, see the [composition example](https://github.com/boundless-xyz/boundless/blob/main/examples/composition/apps/src/main.rs).
#### Requesting a Blake3 Groth16 Proof
Blake3 Groth16 proofs allow verification in environments where SHA2 hashing is impossible or expensive (e.g. BitVM). To request a Blake3 Groth16 proof, use the `with_blake3_groth16_proof()` method:
```rust theme={null}
let request = client
.new_request()
.with_program(program)
.with_stdin(input)
.with_blake3_groth16_proof(); // [!code hl] // [!code focus]
```
Blake3 Groth16 proofs are only supported with the `ClaimDigestMatch` predicate, meaning you should only use this if you do not require the journal to be delivered on-chain. Additionally, the journal must be exactly 32 bytes.
For more details on proof types and when to use each, see [Proof Types](/developers/tutorials/proof-types).
### Onchain vs Offchain
The Boundless protocol allows you to submit requests both onchain and offchain.
The default approach attempts offchain submission first, falling back to onchain submission if needed:
```rust theme={null}
// Create a request using new_request
let request = client.new_request().with_program(ECHO_ELF).with_stdin(echo_message.as_bytes());
// Submit the request (tries offchain first, falls back to onchain)
let (request_id, expires_at) = client.submit(request).await?; // [!code focus]
```
#### Onchain
To submit onchain only, bypassing offchain submission:
```rust theme={null}
// Create a request using new_request
let request = client.new_request().with_program(ECHO_ELF).with_stdin(echo_message.as_bytes());
// Submit onchain only
let (request_id, expires_at) = client.submit_onchain(request).await?; // [!code focus]
```
#### Offchain
To submit offchain only, without onchain fallback:
```rust theme={null}
// Create a request using new_request
let request = client.new_request().with_program(ECHO_ELF).with_stdin(echo_message.as_bytes());
// Submit offchain only
let (request_id, expires_at) = client.submit_offchain(request).await?; // [!code focus]
```
### Offer
The [Offer](/developers/tutorials/auction) specifies how much the requestor will pay for a proof, by setting the auction parameters; price, timing, stake requirements, and expiration.
The Client helps you build requests and set these parameters. Within the client, the [OfferLayer](https://docs.rs/boundless-market/latest/boundless_market/request_builder/offer_layer/struct.OfferLayer.html) creates the offer. It contains a set of defaults, and logic to assign a price to your request.
By default, the parameters maximize the chance of request fulfillment.
They use relaxed timeouts, dynamic market pricing, and recommended collateral.
Change them only when necessary.
There are two ways to configure auction parameters:
1. Using `client_builder.config_offer_layer` to configure the offer building logic.
2. Using `request.with_offer` to override parameters for a specific request. This gives you direct control over the offer.
#### When to Use Each Approach
* Use `config_offer_layer` when:
* You want to configure cycle-based pricing that applies to all requests
* You need to adjust gas estimates or other calculation parameters
* You want consistent pricing logic across multiple requests
* Use `with_offer` when:
* You need to override the automatic calculations for a specific request
* You want to set exact prices rather than using cycle-based and gas-price calculations
* You have special requirements for a particular proof request
#### Per-Request Configuration with `with_offer`
Use `with_offer` when you want to override specific pricing parameters for an individual request:
```rust showLineNumbers theme={null}
// Create a request using new_request
let request = client.new_request()
.with_program(program)
.with_stdin(input)
.with_offer(
OfferParams::builder()
// The market uses a reverse Dutch auction mechanism to match requests with provers.
// Each request has a price range that a prover can bid on.
// Prices can be specified in ETH or USD (converted at runtime via price oracle).
.min_price(Amount::parse("0.40 USD", None)?) // or: parse_ether("0.001")?
.max_price(Amount::parse("1.00 USD", None)?) // or: parse_ether("0.002")?
// The timeout is the maximum number of blocks the request can stay
// unfulfilled in the market before it expires. If a prover locks in
// the request and does not fulfill it before the lock timeout, the
// prover can be slashed.
.timeout(1000)
.lock_timeout(500)
.ramp_up_period(100)
);
// Submit the request
let (request_id, expires_at) = client.submit(request).await?;
```
USD-denominated prices require a price oracle to be configured on the client via `ClientBuilder::with_price_oracle_manager()`. The oracle fetches live ETH/USD and ZKC/USD rates and converts amounts at request submission time.
#### Client-Level Configuration with `config_offer_layer`
Use `config_offer_layer` when you want to adjust how the SDK calculates auction parameters based on cycle count and gas prices. This is particularly useful when you want to use cycle-based pricing:
```rust theme={null}
// Configure the offer layer logic when building the client
let client = Client::builder()
.with_rpc_url(args.rpc_url)
.with_private_key(args.private_key)
.with_uploader_config(&args.storage_config)
.await?
.config_offer_layer(|config| config
// Set the price per cycle for automatic pricing calculations.
// Can be specified in ETH or USD (converted at runtime via price oracle).
.max_price_per_cycle(Amount::parse("0.00001 USD", None).unwrap()) // or: parse_units("0.1", "gwei").unwrap()
.min_price_per_cycle(Amount::parse("0.000001 USD", None).unwrap()) // or: parse_units("0.01", "gwei").unwrap()
// Configure default timeouts and auction parameters
.ramp_up_period(36)
.lock_timeout(120)
.timeout(300)
)
.build()
.await?;
```
With this configuration, the SDK will execute the request to estimate cycles and calculate appropriate prices.
The SDK warns when an overridden parameter may reduce fulfillment chances.
### Funding Modes
For most use-cases, we recommend using the default setting of `Always`, which ensures that your requests will always be fully funded and thus can be fulfilled by the network. *Setting any other funding mode is considered an
advanced feature*, and may lead to a degredation of proof fufillment rate.
When submitting requests onchain, the Boundless Market SDK needs to fund the request with ETH to cover the `max_price` (see [ Auction Parameters](/developers/tutorials/auction#what-auction-parameters-are-configurable)) of the proof.
For more advanced use-cases, the SDK provides several funding modes to control how this funding is handled, allowing you to optimize gas costs and manage your onchain balance efficiently.
The funding mode can be configured when building the client using `with_funding_mode`:
```rust theme={null}
let client = Client::builder()
.with_rpc_url(args.rpc_url)
.with_private_key(args.private_key)
.with_uploader_config(&args.storage_config)
.await?
.with_funding_mode(FundingMode::Always) // [!code hl] // [!code focus]
.build()
.await?;
```
#### Always (Default)
The `Always` mode always sends `max_price` as the transaction value with each request. This is the simplest mode and ensures your requests are always fully funded.
```rust theme={null}
let funding_mode = FundingMode::Always;
```
If your balance is more than 3x the `max_price`, the SDK will log a warning suggesting you consider a different funding mode to avoid overfunding.
#### Never
The `Never` mode never sends value with the request. Use this mode only if you are managing the onchain balance through other means (e.g., manual top-ups, external funding management).
```rust theme={null}
let funding_mode = FundingMode::Never;
```
When using `Never` mode, you must ensure your onchain balance is sufficient to cover the `max_price` of each request. Otherwise, requests may fail.
#### AvailableBalance
The `AvailableBalance` mode uses the available onchain balance for funding the request. If the balance is insufficient, only the difference will be sent as value.
```rust theme={null}
let funding_mode = FundingMode::AvailableBalance;
```
This mode is useful when you want to minimize the amount of ETH sent with each transaction while ensuring requests are properly funded.
#### BelowThreshold
The `BelowThreshold` mode sends value only if the balance is below a configurable threshold. If the balance is below the threshold, the difference will be sent as value (up to `max_price`).
```rust theme={null}
let threshold = parse_ether("0.1")?; // 0.1 ETH
let funding_mode = FundingMode::BelowThreshold(threshold);
```
Set the threshold appropriately to avoid underfunding. The threshold should be at least as large as your typical `max_price` to ensure requests can be funded when needed.
#### MinMaxBalance
The `MinMaxBalance` mode maintains a minimum and maximum balance by funding requests accordingly. If the balance is below `min_balance`, the request will be funded to bring the balance up to `max_balance` (or to cover `max_price`, whichever is greater).
```rust theme={null}
let min_balance = parse_ether("0.05")?; // 0.05 ETH minimum
let max_balance = parse_ether("0.2")?; // 0.2 ETH maximum
let funding_mode = FundingMode::MinMaxBalance {
min_balance,
max_balance,
};
```
This mode should minimize the number of onchain fundings while ensuring sufficient balance is maintained. It's ideal for applications that make frequent requests and want to optimize gas costs by reducing the number of funding transactions.
When the balance drops below `min_balance`, the SDK will fund up to `max_balance` in a single transaction, reducing the need for frequent top-ups.
# Request Stream
Source: https://docs.boundless.network/developers/tutorials/request-stream
Send a continuous stream of proof requests to the Boundless Market based on blockchain events.
## Overview
The Request Stream pattern continuously submits proof requests to the Boundless Market based on blockchain events. Use this pattern when your application proves properties about each block or block range: monitoring block hashes, tracking state transitions, or verifying computations across multiple blocks.
The Request Stream example source code can be found at: [boundless/examples/request-stream](https://github.com/boundless-xyz/boundless/tree/main/examples/request-stream)
## How It Works
The pattern monitors the blockchain for new blocks. Every N blocks (configurable; default is 2), it collects block hashes and constructs a proof request with them as input. The request is submitted to the Boundless Market, and the pattern waits for a prover to fulfill it before repeating for the next block range.
## Setting Up
### Environment Variables
You'll need the same environment variables as a standard request:
```bash theme={null}
export RPC_URL="https://..."
export PRIVATE_KEY="abcdef..."
export PINATA_JWT="abcdef..." # or configure S3 storage
```
For more details on storage providers, see [Storage Providers](/developers/tutorials/request#storage-providers).
### CLI Arguments
The request stream example accepts the following arguments:
```rust theme={null}
struct Args {
/// URL of the Ethereum RPC endpoint
rpc_url: Url,
/// Private key used to interact with the Boundless Market
private_key: PrivateKeySigner,
/// Number of blocks to include in each request
blocks_per_request: u64, // default: 2
/// Storage provider configuration
storage_config: StorageProviderConfig,
/// Boundless Market deployment (optional)
deployment: Option,
}
```
## Creating a Block Range Stream
The core of the request stream pattern is creating an async stream that monitors the blockchain and emits events when new block ranges are ready.
### Stream Pattern Benefits
Streams process events as they arrive, give consumers control over processing rate (backpressure), and compose with other stream operations like filter and map.
### Implementation
```rust theme={null}
async fn create_block_range_stream(
provider: P,
blocks_per_request: u64,
) -> Result> + Send>>> {
let initial_block = provider.get_block_number().await?;
let provider = std::sync::Arc::new(provider);
let provider_clone = provider.clone();
Ok(Box::pin(async_stream::stream! {
let mut last_processed_block = initial_block;
loop {
let target_block = last_processed_block + blocks_per_request;
// Poll until we reach the target block
loop {
let current_block = provider_clone.get_block_number().await?;
if current_block >= target_block {
break;
}
tokio::time::sleep(Duration::from_secs(2)).await;
}
// Collect block hashes in the range
let start_block = last_processed_block + 1;
let end_block = target_block;
let mut block_hashes = Vec::new();
for block_num in start_block..=end_block {
let block = provider_clone
.get_block_by_number(BlockNumberOrTag::Number(block_num))
.await?;
block_hashes.push(block.header.hash);
}
yield Ok(BlockRangeEvent {
start_block,
end_block,
block_hashes,
});
last_processed_block = end_block;
}
}))
}
```
## Processing Events and Submitting Requests
Once you have a stream of block range events, you can process them and submit proof requests:
### Main Processing Loop
```rust theme={null}
// Create the client
let client = Client::builder()
.with_rpc_url(args.rpc_url)
.with_deployment(args.deployment)
.with_storage_provider_config(&args.storage_config)?
.with_private_key(args.private_key.clone())
.build()
.await?;
// Upload the program once
let program_url = client
.storage_provider
.as_ref()
.unwrap()
.upload_program(ECHO_ELF)
.await?;
// Create the event stream
let provider = client.boundless_market.instance().provider().clone();
let mut stream = create_block_range_stream(provider, args.blocks_per_request).await?;
// Process events
while let Some(event_result) = stream.next().await {
let event = event_result?;
// Prepare input data
let input = input_function(&event.block_hashes);
let request_id = request_id_function(args.private_key.address(), event.start_block);
// Build and submit the request
let request = client
.new_request()
.with_program_url(program_url.clone())?
.with_request_input(input)
.with_request_id(request_id);
let (submitted_request_id, expires_at) = client.submit(request).await?;
// Wait for fulfillment
let _fulfillment = client
.wait_for_request_fulfillment(
submitted_request_id,
Duration::from_secs(5),
expires_at,
)
.await?;
}
```
## Input Construction
The input data must be prepared in a format that your guest program can understand. In this example, we concatenate block hashes:
```rust theme={null}
fn input_function(block_hashes: &[B256]) -> RequestInput {
let mut input = Vec::new();
// Concatenate all block hashes into a single byte vector
// Each hash is 32 bytes (B256)
for hash in block_hashes {
input.extend_from_slice(hash.as_slice());
}
RequestInput::builder().write_slice(&input).build_inline().unwrap()
}
```
In production, you might want to serialize data in a structured format (e.g., using bincode, serde) or include additional metadata. Ensure your guest program can deserialize this format.
## Request IDs
A Request ID is a 256-bit value containing your address and a 32-bit index. Bits 0-31 hold the index (u32), bits 32-191 hold the requestor address (160 bits), and bits 192+ hold flags such as the smart contract signature flag.
### Choosing an Index
In this example, we use the start block number as the index:
```rust theme={null}
fn request_id_function(address: Address, start_block: u64) -> RequestId {
let request_index = start_block as u32;
RequestId::new(address, request_index)
}
```
Each block range receives a unique, deterministic request ID, making it straightforward to identify which block range a request corresponds to.
The index must be unique per requestor address. If you submit multiple requests with the same index, only one will be accepted.
## Full Example
```rust theme={null}
async fn run(args: Args) -> Result<()> {
// Step 1: Create the Boundless client
let client = Client::builder()
.with_rpc_url(args.rpc_url)
.with_deployment(args.deployment)
.with_storage_provider_config(&args.storage_config)?
.with_private_key(args.private_key.clone())
.build()
.await?;
// Step 2: Upload the program
let program_url = client
.storage_provider
.as_ref()
.unwrap()
.upload_program(ECHO_ELF)
.await?;
// Step 3: Create the event stream
let provider = client.boundless_market.instance().provider().clone();
let mut stream = create_block_range_stream(provider, args.blocks_per_request).await?;
// Step 4: Process events and submit requests
while let Some(event_result) = stream.next().await {
let event = event_result?;
// Prepare request data
let input = input_function(&event.block_hashes);
let request_id = request_id_function(args.private_key.address(), event.start_block);
// Build the request
let request = client
.new_request()
.with_program_url(program_url.clone())?
.with_request_input(input)
.with_request_id(request_id);
// Submit the request
let (request_id, expires_at) = client.submit(request).await?;
// Wait for fulfillment
let fulfillment = client
.wait_for_request_fulfillment(
request_id,
Duration::from_secs(5),
expires_at,
)
.await?;
tracing::info!("Request fulfilled: {:?}", fulfillment);
}
Ok(())
}
```
## Use Cases
This pattern suits applications that prove properties about each block or block range, verify state transitions continuously, process data in batches as new blocks arrive, or generate proofs for events as they occur onchain.
## Next Steps
See [request configuration](/developers/tutorials/request#request-configuration) for fine-tuning requests, [using proofs](/developers/tutorials/use) for integrating proofs into your application, and [callbacks](/developers/tutorials/callbacks) for automatic proof delivery.
# Sensitive Inputs via AWS S3
Source: https://docs.boundless.network/developers/tutorials/sensitive-inputs
This tutorial shows how to upload program inputs to Amazon S3, and use IAM roles and optionally SSE-KMS to gate access to specific trusted Boundless provers. It also shows how a prover can access these sensitive inputs.
## Overview
When [requesting a proof](/developers/tutorials/build), requestors need to upload both their program binary (ELF) and the associated inputs to a compatible storage provider.
This allows the prover to download the required program and inputs to begin proving.
For most use cases, storing these publicly is acceptable.
However, in situations where the program inputs are sensitive, Boundless allows requestors to work with *trusted provers*.
This allows requestors to effectively store inputs *privately* on Amazon S3 for storage.
With an appropriate [bucket policy](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-policies.html) (and optional [KMS key policy](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html) for server-side encryption), only provers with the necessary [IAM role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html) can download the input file and begin proving.
This guide walks through the setup necessary, for both the requestor and prover, to enable secure s3 inputs for a given proof request.
## Prerequisites
* The [aws CLI](https://aws.amazon.com/cli/) installed and configured with the requestor's AWS credentials.
* *Requestor*: An AWS account with S3 (and optionally [KMS](https://docs.aws.amazon.com/kms/latest/developerguide/overview.html)) access.
* *Prover*: A set of AWS credentials which map to a valid AWS account ID.
## Requestor
### Summary
The general workflow for the requestor is:
* Create an S3 bucket
* Create an IAM role for the prover (using AWS account ID from the prover)
* Upload the inputs to that S3 bucket
* (Optional) Enable SSE-KMS for server-side encryption
* Gate access to the S3 bucket to *only* the prover IAM role
* Request a proof with the S3 url as the input URL
### 1. Create the S3 bucket
To continue, an [AWS account](https://signin.aws.amazon.com/signup?request_type=register) is required. After that, you'll need the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) to set up your S3 bucket, create roles and set the required policies.
You can follow the official instructions at [Step 1: Create your first S3 bucket](https://docs.aws.amazon.com/AmazonS3/latest/userguide/GetStartedWithS3.html#creating-bucket).
To store the inputs, we will need to first create an S3 bucket. This can be done with the CLI with:
```bash theme={null}
aws s3 mb s3:// --region
```
For the bucket name, it is recommended to follow the structure:
```bash theme={null}
s3://-boundless-prover--
```
*Example*: `123456789012-boundless-prover-prod-us-east-1`
To find your account ID, you can use the CLI:
```bash theme={null}
aws sts get-caller-identity --query Account --output text
```
or follow the instructions listed [here](https://www.apn-portal.com/knowledgebase/articles/FAQ/Where-Can-I-Find-My-AWS-Account-ID).
For a list of AWS regions, please see [Available AWS regions](https://docs.aws.amazon.com/global-infrastructure/latest/regions/aws-regions.html#available-regions).
### 2. Create the required prover role
An IAM role is an AWS identity with its own permission policy that any trusted user, service, or account can temporarily assume to get short-lived credentials instead of keeping permanent keys.
To learn more, please see [IAM Roles](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html).
With the bucket created, we need to create an IAM role for prover; this will allow the prover access to the inputs stored in the S3 bucket. To create the role, we will need two things:
* A JSON specifying the [trust policy](https://aws.amazon.com/blogs/security/how-to-use-trust-policies-with-iam-roles/) for the IAM role.
* The prover's AWS 12-digit account ID.
To create the trust policy JSON, use a text editor and copy the following:
```bash title="prover-trust-policy.json" theme={null}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::222222222222:root" },
"Action": "sts:AssumeRole"
}
]
}
```
Make sure to replace `222222222222` with the AWS account ID of the prover.
Saving the above as `prover-trust-policy.json`, next:
```bash theme={null}
aws iam create-role \
--role-name ProverInputDownloadRole \
--assume-role-policy-document ./prover-trust-policy.json
```
The output will be in JSON format; there will be a key specifying "Arn":
```bash theme={null}
...
"Arn": "arn:aws:iam::123456789012:role/ProverInputDownloadRole",
...
```
The value string refers to the full *Amazon Resource Name* (ARN) of the IAM role created for the prover. Make sure to save this, you will need it when creating the bucket policy.
### 3. Upload the Input file to S3
Replace ``, ``, and optionally ``, with the correct paths:
```bash theme={null}
aws s3 cp ./input.json s3:////input.json \
--sse aws:kms \
--sse-kms-key-id # optional: leave off to skip SSE-KMS
```
### 3a. (Optional) KMS key policy
When using server-side encryption with AWS KMS keys (known as [SSE-KMS](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html)), S3 will call [kms Decrypt](https://docs.aws.amazon.com/cli/latest/reference/kms/decrypt.html) every time someone requests to download the object; if the prover role lacks `kms:Decrypt` permission on that key, the download is blocked with an `AccessDenied`.
This server-side encryption adds another check on top of the IAM role requirement. For some use cases, at-rest encryption is necessary for compliance (HIPAA, SOC 2 etc.).
If you enabled `--sse-kms` in [Upload the input file to S3](/developers/tutorials/sensitive-inputs#3-upload-the-input-file-to-s3), you can specify the key policy either way the KMS console or via the AWS CLI. For up to date information on how to do that, please refer to the official AWS [Change a Key Policy](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-modifying.html) documentation.
An example key policy would be:
```json theme={null}
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "" },
"Action": "kms:Decrypt",
"Resource": "*"
}]
}
```
### 4. Set Bucket Policy
From the requestor side, we need to limit access to the input file to provers with the right AWS credentials. In practice, this means S3 will only complete the `GetObject` call for provers with credentials matching the allowed IAM role. To enforce this, the requestor needs to set a [bucket policy](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-policies.html).
A bucket policy is a JSON document attached to an S3 bucket. This JSON specifies how to allow or deny requests based on:
* who is calling (the *Principal*)
* what the call asks for (the *Action*)
* which bucket/resource to give access to (the *Resource*)
The requestor creates this JSON once, stores it with the relevant bucket and S3 will evaluate every request against the rules specified.
Below is an example bucket policy, copy it and save it to `prover_policy.json`:
```json title="prover_policy.json" theme={null}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::/*"
}
]
}
```
Only provers whose temporary credentials let them assume `` can download the input file they need to start proving.
The `` string refers to the full *Amazon Resource Name* (ARN) of the IAM role created for the prover. This was generated during [Create the required prover role](/developers/tutorials/sensitive-inputs#2-create-the-required-prover-role).
*Make sure to replace `` with the string we saved earlier*, something like:
```bash theme={null}
"arn:aws:iam::123456789012:role/ProverInputDownloadRole"
```
where the 12-digit number refers to the AWS account ID of the prover.
Once saved to `prover-policy.json`, you can set the policy on your bucket with:
```
aws s3api put-bucket-policy \
--bucket \
--policy ./prover_policy.json
```
### 5. Submit a Request to the Boundless market
With your inputs now sitting privately in S3, you may now [request a proof](/developers/tutorials/request).
#### Uploading your inputs using the Boundless SDK
If you have already uploaded your inputs using the `aws` CLI above, you can skip the information below. Otherwise, if you are interested in using the Boundless SDK to upload your inputs to your S3 bucket, you will need to:
* make sure your AWS credentials are set in environment variables, specifically:
* `AWS_ACCESS_KEY_ID` for the access key (optional if using the AWS default credential chain)
* `AWS_SECRET_ACCESS_KEY` for the secret key (optional if using the AWS default credential chain)
* `S3_BUCKET` for the bucket name of the bucket created in [Create the S3 bucket](/developers/tutorials/sensitive-inputs#1-create-the-s3-bucket)
* `S3_URL` for the bucket endpoint URL of the bucket created in [Create the S3 bucket](/developers/tutorials/sensitive-inputs#1-create-the-s3-bucket)
* `AWS_REGION` for the bucket region
* and last, but not least, make sure `S3_PRESIGNED=false` to use direct S3 URLs
After this setup, you may request a proof programmatically as [Request a Proof](/developers/tutorials/request) recommends; your inputs will be automatically uploaded to your gated S3 bucket, however remember that you still need to go through all the necessary gating policies as laid out in this tutorial to make sure your inputs are private and only available to select provers.
If you're interested in doing a one-off test, take a look at the [requestor module](/developers/tooling/cli#requestor) in the Boundless CLI.
Relevant Links:
[StorageUploader](https://docs.rs/boundless-market/latest/boundless_market/storage/trait.StorageUploader.html), [StorageUploaderConfig](https://docs.rs/boundless-market/latest/boundless_market/storage/struct.StorageUploaderConfig.html).
## Prover
### Summary
The general workflow for the prover is:
* Export base AWS credentials to environment variables
* `AWS_ACCESS_KEY_ID`
* `AWS_SECRET_ACCESS_KEY`
* `AWS_REGION`
* Export IAM role to assume to environment variable
* `AWS_ROLE_ARN`
* Spin up the broker
* Test with aws cli: `assume-role` to verify credentials
### 1. Set AWS credentials in environment variables
The prover has to make sure that the Docker container that runs the broker starts with two kinds of AWS credentials in its environment:
1. base credentials (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_REGION`) which will be used to call [sts::AssumeRole](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html).
2. the role to assume (e.g. `AWS_ROLE_ARN=arn:aws:iam:::role/ProverInputDownloadRole` which is generated when the requestor created the role during [Create the required prover role](/developers/tutorials/sensitive-inputs#2-create-the-required-prover-role)).
To set these manually, make sure the export the following environment variables before spinning up the [broker](/provers/proving-stack#what-is-the-broker):
```bash theme={null}
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_REGION=...
export AWS_ROLE_ARN=...
```
Once these are set, you can run:
```bash theme={null}
just broker
```
### 2. Verify IAM role authentication
With the environment variables set:
```bash theme={null}
export AWS_ACCESS_KEY_ID=AKIA………………
export AWS_SECRET_ACCESS_KEY=abcd………………
export AWS_REGION=us-east-1
```
```bash title="One-off test for prover to verify everything is working" theme={null}
aws sts assume-role \
--role-arn arn:aws:iam::111111111111:role/ProverInputDownloadRole \
--role-session-name testProverSession \
--query 'Credentials.[AccessKeyId,Expiration]' \
--output table
```
If the output looks something like:
```
------------------------------
| DescribeCredentials |
+----------------+-----------+
| AKIA... | 2025-05-07T12:34:56Z |
+----------------+-----------+
```
the role was assumed successfully and the broker will be able to download the sensitive inputs from S3 directly.
## Troubleshooting
| What you see | Root cause (most likely first) | Fix it fast |
| ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `UnsupportedScheme` or “unsupported protocol scheme ""” before any request goes out | Broker tried to build an S3 URL from empty strings – usually the bucket name, region, or credentials are missing in `ENV` | Check that **all** four env-vars listed above are populated inside the container; an unset bucket var is the classic culprit. |
| `AccessDenied` / HTTP 403 when the broker calls `GetObject` | Bucket policy doesn’t grant `s3:GetObject` to the **role** the broker assumed | In the bucket policy, set `"Principal": { "AWS": "" }` and **remove any stray `"Principal":"*"`**. |
| `AccessDenied (KMS.AccessDeniedException)` right after S3 authenticates | Object is encrypted with SSE-KMS, but the KMS **key policy** (or the IAM permissions of the role) is missing `kms:Decrypt` | Add the role ARN to the key policy, or attach a policy that grants `kms:Decrypt` on that key. |
| `An error occurred (AccessDenied) when calling the AssumeRole operation` before any S3 call | The base creds can’t assume the role—either the role trust policy doesn’t list the prover’s account, or the creds lack `sts:AssumeRole` | Ask the requestor to Verify the **trust policy** on `ProverInputDownloadRole` and be sure the base IAM user/role has `sts:AssumeRole` permission. |
| Everything works for every prover (even un-trusted ones) | Bucket policy still has `"Principal":"*"` or Block Public Access is disabled | Lock the policy down to the specific role and enable **BlockPublicPolicy** if you want S3 to reject future “\*” policies. |
# Smart Contract Requestors
Source: https://docs.boundless.network/developers/tutorials/smart-contract-requestor
Smart Contract Requestors enable permissionless proof request submission by 3rd parties that are authorized for payment by a smart contract.
## Overview
This feature enables proof requests to be submitted permissionlessly by 3rd parties, that are authorized for payment by a smart contract. This is particularly useful for:
1. DAO-like entities that need to request proofs to drive protocol operations
2. Service agreements where contracts authorize funding for proofs that meet specific criteria
## How it Works
### Entities
* **Request Builder**
* Builds and submits proof requests to the market
* Fully permissionless role
* Incentivized outside of the Boundless protocol
* **Smart Contract Requestor**
* ERC-1271 contract that authorizes proof requests
* Contains logic for validating requests
* Deposits funds to Boundless Market for fulfilling requests
* **Provers**
* Regular market provers who fulfill requests by the deadline
### Flow
Smart Contract Requestors use ERC-1271 signatures to authorize proof requests.
1. Request Builder constructs a request meeting the Smart Contract Requestor's criteria
2. Request Builder submits the request with:
* Smart Contract Requestor's address as the client
* Signature encoding the data that the smart contract requestor needs to validate the request
3. Boundless Market requests authorization of the request by calling `isValidSignature` on the Smart Contract Requestor
4. Smart Contract Requestor receives a hash of the submitted request, and the data provided by the Request Builder
5. Smart Contract Requestor validates the request and returns the ERC-1271 magic value if it authorizes the request
6. Boundless Market takes payment from the smart contract requestor, and provers fulfill the request
## Considerations
### Request ID
In Boundless, Request IDs are specified by the Request Builder. The Boundless Market contract ensures that only one payment will ever be issued for each request id.
For Smart Contract Requestors, the Request ID is especially important as it acts as a nonce, ensuring the requestor does not pay twice for the same batch of work. It is important to design a nonce structure that maps each batch of work to a particular nonce value, and for the Smart Contract Requestor to validate that the work specified by the Request ID matches the work specified in the proof request.
### Signature Encoding
The signature encoding is used to encode the data that the smart contract requestor needs to validate the request. Boundless guarantees that it will call `isValidSignature` with a hash of the request that was submitted, so typically you would want to encode enough information to recreate the request hash and validate that it matches the hash provided by Boundless.
# Example: Daily Echo Proof
The [Smart Contract Requestor example](https://github.com/boundless-xyz/boundless/tree/main/examples/smart-contract-requestor) demonstrates a contract that authorizes payment for one proof of the "Echo" guest program per day. It shows a simple example of how to design a Request ID nonce scheme, as well as how to encode the request data in the signature for the Smart Contract Requestor to validate.
In this example, we use the Request ID to represent "days since epoch". Our zkVM guest program outputs the input that it was called with, so we use this property to ensure that the program was run with the correct input for the day.
First, we construct the Request ID. We use the index of the Request ID to represent each day since the unix epoch, ensuring that we will only ever pay for one request per day. Note we also set a flag to indicate that this request's signature should be validated using ERC-1271's `isValidSignature` function, and not a regular ECDSA recovery:
```rust theme={null}
#
let now = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let days_since_epoch = (now / (24 * 60 * 60)) as u32;
let request_id = RequestId::new(smart_contract_requestor_address, days_since_epoch)
.set_smart_contract_signed_flag();
```
Next, we want to ensure that for the specific day that the request is submitted, the proof was generated using the correct input. In our case, we want the input to be the current day since epoch, ensuring that that day's work was paid for.
Here we make use of a powerful pattern, where we have ensured that our guest program outputs its input as part of it's journal. When constructing our Proof Request, we then set a `Requirement`, with the predicate type `DigestMatch`, to ensure that the journal of the guest program matches the value we expect.
In this example we expect the input of the program to be the current day since epoch, so we validate that by creating a digest match predicate with days\_since\_epoch as the expected journal.
First we execute the guest program locally with our expected input, to generate the expected journal.
```rust theme={null}
// We encode the input as Big Endian, as this is how Solidity represents values. This simplifies validating
// the requirements of the request in the smart contract client.
let input = days_since_epoch.to_be_bytes();
let guest_env = GuestEnv::from_stdin(input);
let input_url = client
.upload_input(&guest_env.encode()?)
.await
.context("failed to upload input")?;
// Execute the guest program locally to get the journal for use in our requirements
let env = guest_env.try_into()?;
let session_info = default_executor().execute(env, ECHO_ELF)?;
let journal = session_info.journal;
```
Then we create our proof request, setting a `Requirement` that the journal should match the expected journal.
```rust theme={null}
// Create the request params
let request = client.new_request()
.with_program_url(program_url)?
.with_stdin(input);
// Build the request
let request = client.build_request(request).await?;
```
When combined with the nonce structure of the request id, this ensures that for each daily batch of work, the correct input was used.
Here we are using the echo guest, which simply echoes the input back. Since for each day we want the input to the guest to be "days since epoch", and since the program just echoes the input back, we can guarantee the correct input was used by checking that the output matches "days since epoch".
In this example we expect the input of the program to be the current day since epoch, so we validate that by creating a digest match predicate with days\_since\_epoch as the expected journal.
Our Smart Contract Requestor expects the full abi encoded ProofRequest to be provided as the signature.
```solidity SmartContractRequestor.sol theme={null}
function isValidSignature(bytes32 requestHash, bytes memory signature) external view returns (bytes4) {
// This smart contract client expects the full abi encoded ProofRequest to be provided as the signature.
ProofRequest memory request = abi.decode(signature, (ProofRequest));
// ...
}
```
So we encode the signature that the Smart Contract Requestor requires to validate the request is constructed correctly. Now we submit the request to the market, and wait for it to be fulfilled.
```rust theme={null}
let signature: Bytes = request.abi_encode().into();
let (request_id, expires_at) =
client.submit_request_onchain_with_signature(&request, signature).await?;
tracing::info!("Request {:x} submitted", request_id);
```
When the request is locked or fulfilled, Boundless Market will call `isValidSignature` on the Smart Contract Requestor with the request hash and the signature. Here we walk through the logic of our example contract:
First, we decode the request from the signature.
```solidity SmartContractRequestor.sol theme={null}
ProofRequest memory request = abi.decode(signature, (ProofRequest));
```
Recall that the Request ID represents the day of work being processed, so first we check that the request id is within the expected range for days that we are willing to pay for.
```solidity SmartContractRequestor.sol theme={null}
(, uint32 daysSinceEpoch) = request.id.clientAndIndex();
if (daysSinceEpoch < START_DAY_SINCE_EPOCH || daysSinceEpoch > END_DAY_SINCE_EPOCH) {
return 0xffffffff;
}
```
Next we check that the image id is as expected, ensuring that the request specifies the correct guest program.
```solidity SmartContractRequestor.sol theme={null}
// Validate that the request provided is as expected.
// For this example, we check the image id is as expected, and that the predicate restricts
// the output to match the day specified in the id.
if (request.requirements.imageId != ECHO_ID) {
return 0xffffffff;
}
```
Next, we validate the predicate type and data are correct, ensuring that the request was executed with the correct input and resulted in the correct output.
```solidity SmartContractRequestor.sol theme={null}
// Validate the predicate type and data are correct. This ensures that the request was executed with
// the correct input and resulted in the correct output. In this case it ensures that the input
// to the request was the correct day since epoch that corresponds to the request id.
if (request.requirements.predicate.predicateType != PredicateType.DigestMatch) {
return 0xffffffff;
}
bytes32 expectedPredicate = sha256(abi.encodePacked(daysSinceEpoch));
if (bytes32(request.requirements.predicate.data) != expectedPredicate) {
return 0xffffffff;
}
```
Finally, we validate that the EIP-712 hash of the request provided in the signature matches the hash that was provided by BoundlessMarket. This ensures that Boundless is processing the same request that we have validated.
```solidity SmartContractRequestor.sol theme={null}
// Validate that the EIP-712 hash of the request provided in the signature matches the hash that was
// provided by BoundlessMarket. This ensures that Boundless is processing the same request that we have
// validated.
if (_hashTypedData(request.eip712Digest()) == requestHash) {
return MAGICVALUE;
}
return 0xffffffff;
```
If all of these checks pass, the request is valid and the smart contract requestor will pay for the request.
Relevant links: [Smart Contract Requestor Example](https://github.com/boundless-xyz/boundless/tree/main/examples/smart-contract-requestor), [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271)
# Tracking Proof Requests
Source: https://docs.boundless.network/developers/tutorials/tracking
Check the status of your proof request in the Boundless market.
## Using the CLI
The [Boundless CLI](/developers/tooling/cli) provides the following command:
```shell Terminal theme={null}
boundless requestor status
```
## Programmatically
When you submit a proof request to Boundless, you can track its status programmatically using the Boundless client. This allows you to integrate request monitoring directly into your applications.
### Basic Request Tracking
Here's how to wait for a request to be fulfilled:
```rust no_run theme={null}
#
// Submit your request
let request = client.new_request()
.with_program(program)
.with_stdin(input);
let (request_id, expires_at) = client.submit(request).await?;
// Wait for the request to be fulfilled
tracing::info!("Waiting for request {:x} to be fulfilled", request_id);
let fulfillment = client
.wait_for_request_fulfillment(
request_id,
Duration::from_secs(5), // check every 5 seconds
expires_at,
)
.await?;
tracing::info!("Request {:x} fulfilled", request_id);
```
### How It Works
The `wait_for_request_fulfillment` method:
1. *Polls the request status* at the specified interval (e.g., every 5 seconds)
2. *Checks for completion states*:
* `Fulfilled`: Request completed successfully - returns journal and seal
* `Expired`: Request timed out - returns an error
* Other states: Continues polling
3. *Provides status updates* via logging during the wait
### Request Status Types
Boundless tracks these request states:
* *Unknown*: Request may be open for bidding or not exist
* *Locked*: A prover has committed to fulfilling the request
* *Fulfilled*: Proof generation completed successfully
* *Expired*: Request timed out before fulfillment
### Error Handling
When tracking requests, handle these potential outcomes:
```rust no_run theme={null}
#
match client.wait_for_request_fulfillment(request_id, check_interval, expires_at).await {
Ok(fulfillment) => {
// Process the fulfilled proof
tracing::info!("Proof received with fulfillment data: {:?}", fulfillment.data()?);
}
Err(ClientError::MarketError(MarketError::RequestHasExpired(_))) => {
tracing::error!("Request expired before fulfillment");
// Handle expiration - maybe retry with different parameters
}
Err(e) => {
tracing::error!("Request tracking failed: {}", e);
// Handle other errors
}
}
```
### Advanced Tracking
For more control, you can manually check request status:
```rust no_run theme={null}
#
// Check status once
let status = client.boundless_market.get_status(request_id, Some(expires_at)).await?;
match status {
RequestStatus::Fulfilled => {
// Retrieve the fulfillment for the fulfilled request
let fulfillment = client
.wait_for_request_fulfillment(request_id, Duration::from_secs(1), expires_at)
.await?;
}
RequestStatus::Locked => {
tracing::info!("Request locked by a prover, awaiting fulfillment");
}
RequestStatus::Expired => {
tracing::warn!("Request has expired");
}
RequestStatus::Unknown => {
tracing::info!("Request is open for bidding");
}
}
```
## Boundless Explorer
### Getting Started
The [Boundless Explorer](https://explorer.boundless.network/) provides a straightforward web interface to monitor and analyze proof activity on the Boundless network.
Developers can track live proof request statuses, analyze associated costs, review transaction details, and assess proof performance metrics. Provers can monitor active requests, track earnings, evaluate their efficiency, and benchmark performance against peers.
Access the explorer to:
* Search through [Orders](https://explorer.boundless.network/orders) and their current status.
* Check the list of all [Boundless Requestors](https://explorer.boundless.network/requestors).
* Check the list of all [Boundless Provers](https://explorer.boundless.network/provers).
* View trends and analyze network patterns on the [Stats](https://explorer.boundless.network/stats) page.
### Need Help?
* Join our [Discord community](https://discord.gg/aXRuD6spez)
* Report issues via [GitHub](https://github.com/boundless-xyz/boundless)
# Troubleshooting a Request
Source: https://docs.boundless.network/developers/tutorials/troubleshooting
Debugging a request on Boundless.
For technical support, please post your questions on the [Boundless Discussions Forum](https://github.com/boundless-xyz/boundless/discussions).
If you submitted a request, and it is stuck in an "Unknown" / "Submitted" state, there may be something that is preventing the provers from picking up your request.
* The guest may be panicking or otherwise failing to complete the job.
* Pre-flight your request locally before sending it.
* The `boundless` CLI does this by default, and the execution step in the Foundry template also accomplishes this.
* If your request is submitted onchain, you can use the following command to execute it locally. If it succeeds, this is not the issue.
```bash Terminal theme={null}
RUST_LOG=info boundless proving execute --request-id $REQUEST_ID
```
* The offer price may be too low for the size of the job.
* Try increasing the max price and ramp-up period.
* The auction mechanism will ensure that the price you pay is the lowest price any online prover is willing to pay.
* The lock-in collateral may be too high.
* The timeout may be too short.
* The ramp-up start block may be in the past, causing the request to immediately expire.
* The program or input URLs may not be accessible to the prover.
# Use a Proof
Source: https://docs.boundless.network/developers/tutorials/use
After receiving a Boundless proof, you are ready to consume it in your app to access verifiable compute.
## Overview
After [requesting a proof](/developers/tutorials/request), the next step is to use that proof in the application's workflow.
The exact way proofs are used will vary depending on the architecture of the application.
However, there is a common pattern; once a proof is received from the Boundless market, the next step will be to verify that proof onchain.
It is recommended that the application contract calls the [RiscZeroVerifierRouter](https://dev.risczero.com/api/blockchain-integration/contracts/verifier) for verification.
This allows handling many types of proofs, and proof system versions seamlessly.
In Boundless, the seal, which is often a zk-STARK or SNARK, will usually be Merkle inclusion proof into an aggregated proof.
These Merkle inclusion proofs are cheap to verify, and reuse a cached verification result from a batch of proofs verified with a single SNARK.
## Proof Verification
The [Boundless Foundry Template](https://github.com/boundless-xyz/boundless-foundry-template), walks through a simple application which, with an input number, *x*:
1. Uses a simple guest program to check if *x* is even.
2. Requests, and receives, a proof of *x* being even from the Boundless Market.
3. Calls the `set` function on the `EvenNumber` smart contract with the arguments: *x* and the seal (the proof bytes).
4. The `set` function verifies the proof that *x* is even; if the proof is valid, the `number` variable (in smart contract state) is set to equal *x*.
Concretely, receiving the proof ([see code](https://github.com/boundless-xyz/boundless-foundry-template/blob/main/apps/src/main.rs)) from the Boundless Market returns a journal and a seal:
```rust theme={null}
let fulfillment = boundless_client
.wait_for_request_fulfillment(request_id, Duration::from_secs(5), expires_at)
.await?;
```
Using Alloys [sol! Macro](https://alloy.rs/contract-interactions/using-sol%21/), the rust types/bindings are generated for the [`EvenNumber.sol`](https://github.com/boundless-xyz/boundless-foundry-template/blob/main/contracts/src/EvenNumber.sol) contract.
To create an `EvenNumber` contract instance:
```rust theme={null}
let even_number = IEvenNumber::new(
args.even_number_address,
boundless_client.provider().clone(),
);
```
To call the `set` function on the `EvenNumber` contract, a “set number” transaction is created:
```rust theme={null}
let set_number = even_number
.set(U256::from(args.number), seal)
.from(boundless_client.caller());
```
Finally, this transaction is broadcasted with:
```rust theme={null}
let pending_tx = set_number.send().await.context("failed to broadcast tx")?;
let tx_hash = pending_tx
.with_timeout(Some(TX_TIMEOUT))
.watch()
.await
.context("failed to confirm tx")?;
tracing::info!("Tx {:?} confirmed", tx_hash);
```
Definition of the `set` function on [`EvenNumber.sol`](https://github.com/boundless-xyz/boundless-foundry-template/blob/main/contracts/src/EvenNumber.sol):
```solidity [EvenNumber.sol] theme={null}
/// @notice Set the even number stored on the contract. Requires a RISC Zero proof that the number is even.
function set(uint256 x, bytes calldata seal) public {
bytes memory journal = abi.encode(x);
verifier.verify(seal, imageId, sha256(journal));
number = x;
}
```
Calling the [`set` function](https://github.com/boundless-xyz/boundless-foundry-template/blob/main/contracts/src/EvenNumber.sol) will verify the proof via the [RISC Zero verifier contract](https://dev.risczero.com/api/blockchain-integration/contracts/verifier).
The `verify` call will revert if the proof is invalid, otherwise the number variable will be updated to x, which is now certainly even.
Each application will have its own requirements and flows, but this is a common pattern and a good starting point for building your own application.
Relevant links: [Boundless Foundry Template](https://github.com/boundless-xyz/boundless-foundry-template/tree/main), [Journal](https://dev.risczero.com/terminology#journal), [Seal](https://dev.risczero.com/terminology#seal)
# What is Boundless?
Source: https://docs.boundless.network/developers/what
Boundless is a universal protocol that brings ZK to every chain.
## Overview
Using Boundless, developers can create expressive, high-throughput applications that bypass traditional block size/gas limits.
Developers submit proof requests and provers compete to fulfill them, earning direct rewards and protocol-level incentives through proof of verifiable work.
By abstracting away the complexity of proof generation, aggregation, and onchain settlement, Boundless allows developers to build without worrying about underlying infrastructure, while provers provide strong liveness guarantees, censorship-resistance, and continuously improve the cost curve driven by open market dynamics.
This architecture decouples execution from consensus and introduces a new paradigm for verifiable computing. As the number of Boundless prover nodes grows, the total capacity of the protocol increases, scaling compute across every chain.
## Next Steps
Choose the path that fits what you’re building:
* Building a rollup? Get fast finality and stronger security with ZK proofs with [Kailua](https://github.com/boundless-xyz/kailua)
* Building an app? Offload your execution to bypass gas limits with [Steel](/developers/steel/what-is-steel)
* Building something custom? [Submit a request](/developers/tutorials/request) and let Boundless handle the proving.
* Want to run a [prover node](/provers/quick-start)? Turn your hardware into income.
# Why Boundless?
Source: https://docs.boundless.network/developers/why
A story of re-execution - ZK execution vs Blockchain Execution
Traditionally, blockchains rely on a "global re-execution" model, where every node redundantly processes every transaction to achieve consensus on the network's state. While this model is secure and transparent, it creates a fundamental constraint: the network is limited by the slowest node. This means the collective computational capacity of all nodes is underutilized. The result is that blockchains can only handle simple computations, with anything complex becoming too expensive or hitting gas limits.
## The Solution: Decoupling Execution From Consensus
Boundless transforms this model using zero-knowledge proofs. Instead of being limited by the slowest node, the network can harness the total collective capacity of all nodes. Each node generates execution proofs that any blockchain can verify without re-execution. The underlying networks maintain their security and consensus while eliminating redundant computation.
Boundless enables abundant compute on any blockchain by leveraging a decentralized market to handle complex computations and generate succinct, reusable proofs. These proofs, verified onchain, act as building blocks for innovation while driving efficiency through aggregation as demand grows. With each new prover on the market adding capacity and each new application amplifying benefits, the network strengthens and scales, creating a self-reinforcing system of increasing computational power and efficiency.
# AI Agents
Source: https://docs.boundless.network/provers/ai-agents
Use Boundless with AI coding agents like Claude, Cursor, Copilot, and other LLM-powered tools.
AI coding agents can use Boundless more effectively when they have access to structured skill files that describe the product's capabilities, workflows, and common patterns.
Boundless publishes a [skill.md](https://docs.boundless.network/skill.md) file following the [agentskills.io](https://agentskills.io) specification. This file gives agents structured context about:
* **Requestor workflows** — submitting proof requests, configuring offers, tracking fulfillment
* **Prover workflows** — setting up nodes, configuring Bento and Broker, depositing collateral
* **SDK and CLI usage** — common commands, environment variables, storage providers
* **Decision guidance** — when to use Groth16 vs aggregated proofs, onchain vs offchain submission
* **Common gotchas** — free RPC failures, journal size limits, collateral requirements
## Add Boundless to your agent
Install the Boundless skill into your AI agent's context using the [skills CLI](https://www.npmjs.com/package/skills):
```bash theme={null}
npx skills add https://docs.boundless.network
```
This fetches the `skill.md` from Boundless and adds it to your agent's context, so it can help you build on Boundless with accurate, up-to-date guidance.
## Monorepo skills
The [Boundless monorepo](https://github.com/boundless-xyz/boundless) also includes task-specific skills in [`.claude/skills/`](https://github.com/boundless-xyz/boundless/tree/main/.claude/skills) that provide deeper, step-by-step guidance for common workflows:
| Skill | Description |
| ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| [`requesting`](https://github.com/boundless-xyz/boundless/tree/main/.claude/skills/requesting) | Submit a proof request end-to-end — wallet setup, CLI install, deposit, submit, poll, and proof retrieval. |
| [`setup-prover`](https://github.com/boundless-xyz/boundless/tree/main/.claude/skills/setup-prover) | Deploy and manage a Boundless prover on a GPU server using Ansible. |
| [`boundless-cli`](https://github.com/boundless-xyz/boundless/tree/main/.claude/skills/boundless-cli) | Complete reference for the Boundless CLI — requestor, prover, and rewards commands. |
These skills are automatically available to agents working inside the Boundless monorepo (e.g., Claude Code, Cursor). When you clone the repo, your agent can read the skill files directly.
If you're using [Claude Code](https://docs.anthropic.com/en/docs/claude-code), the skills in `.claude/skills/` are loaded automatically when relevant. Just ask Claude to help you set up a prover or submit a proof request.
## What agents can help with
Here are some example prompts to try with your AI agent after adding the Boundless skill:
**For requestors:**
```
Help me submit my first proof request on Boundless using the CLI
```
```
I have a RISC Zero guest program — walk me through requesting a proof on Boundless
```
**For provers:**
```
Help me set up a Boundless prover on my GPU server
```
```
My broker isn't picking up requests — help me troubleshoot
```
**For developers:**
```
How do I use Steel to read EVM state in my guest program?
```
```
What's the difference between Groth16 and aggregated proofs?
```
# Bento Technical Design
Source: https://docs.boundless.network/provers/bento
An overview of how Bento is designed.
## Overview
Bento's infrastructure is composed of a few core open source projects:
* [Docker](https://docs.docker.com/get-started/docker-overview)
* [PostgreSQL](https://www.postgresql.org)
* [Redis](https://redis.io)
* [MinIO](https://min.io)
* [Grafana](https://grafana.com) *(optional for monitoring)*
### Bento Components
Bento's components are built on top of this core infrastructure:
* [API](#rest-api)
* TaskDB
* CPU (executor) Agent
* GPU (prover) Agent
* Aux Agent
These components are the basis for Bento and therefore, they are critical for its operation.
## Technical Design
Bento's design philosophy is centered around [TaskDB](#taskdb). TaskDB is a database schema in PostgreSQL that acts as a central communications hub, scheduler, and queue for the entire Bento system.
The following diagram is a visual representation of the proving workflow:
Bento has the application containers:
* REST API
* Agents (of different work types exec/gpu/aux/snark)
As demonstrated above, Bento breaks down tasks into these major actions:
* **Init/Setup (executor)** - this action generates [continuations](https://dev.risczero.com/terminology#continuations) or [segments](https://dev.risczero.com/terminology#segment) to be proven and places them on Redis.
* **Prove + lift (GPU/CPU agent)** - proves a segment on CPU/GPU and lifts the result to Redis.
* **Join** - takes two lifted proofs and joins them together into one proof.
* **Resolve** - produces a final join and verifies all the unverified claims, effectively completing any composition tasks.
* **Finalize** - Uploads the final proof to minio.
* **SNARK** - Convert a STARK proof into a SNARK proof using [rapidsnark](https://github.com/iden3/rapidsnark).
For a more in depth information see [the recursive proving docs](https://dev.risczero.com/api/recursion).
### Redis
For optimal performance, the machine running TaskDB should be co-located with the GPUs i.e. in the same datacenter.
In order to share intermediate files (such as segments) between workers, Redis is used as a fast intermediary. Bento writes to Redis for fast cross machine file access and provides a high bandwidth backbone for sharing data between nodes and workers.
The Redis node's memory configuration is important for the size of proofs running. Because each segment is \~5 - 10 MB in size it is possible to overload Redis's node memory with too much data if the STARK proof is large enough and the GPU workers are not consuming the segments fast enough.
We recommend a high memory node for the Redis container as well as active monitoring / alerts (see [monitoring](/provers/monitoring) for more details) on the Redis node to ensure it does not overflow the possible memory.
### MinIO
MinIO is used for object storage, including proof inputs and receipts. The system applies a lifecycle rule on startup to expire objects under the `inputs/` prefix, preventing unbounded disk growth. The `INPUT_TTL_DAYS` environment variable controls the TTL (default: 1 day).
### TaskDB
For optimal performance, the machine running TaskDB should be co-located with the GPUs i.e. in the same datacenter.
TaskDB is the center of how Bento schedules and prioritizes work. It provides the ability to create a job which will contain many tasks, each with different actions in a stream of work. This stream is ordered by priority and dependencies. TaskDB's core job is to correctly emit work to agents via long polling in the right order and priority. As segments stream out of the executor, TaskDB delegates the work plan such that GPU nodes can start proving before the executor completes.
#### Prioritizing Work Streams
TaskDB also has the ability to prioritize specific work streams using two separate modes:
* **Priority multiplier mode** allows for individual users and task types to be schedules ahead of other users.
* **Dedicated resources mode** allows for a stream's user to get priority access to N workers on that stream. For example, if `user1` has a 10 GPU stream then that work will always get priority over the normal pool of users that have dedicated count of 0. But once `user1` has 10 concurrent GPU tasks, any additional work is scheduled alongside the rest of the priority pool of user work.
## The Agent
Bento agents are long polling daemons that opt-in to specific actions. An agent can be configured to act as a:
* Executor
* GPU worker
* CPU worker
* SNARK agent
This allows Bento to run on diverse hardware that can specialize in tasks that need specific hardware:
* **Executor** - needs low core count but very high single thread core clock CPU performance
* **GPU** - needs a GPU device to run GPU accelerated proving
* **CPU** (optional) - run prove+lift on a CPU instead of a GPU, not advised for performance reasons
* **SNARK** - Needs a high CPU thread count and core speed node
The agent polls for work, runs the work, monitors for failures and reports status back to TaskDB.
## Further Information
### More on the Executor
The executor (init) task is the first process run within a STARK proving workflow and iteratively generates the continuations work plan of prove+lift, join, resolve and finalize.
Internally, each "user" of Bento gets their own stream for each type of work. So `user1` would have their own stream for CPU, GPU, Aux, and SNARK work types. Each stream has settings for priority multiplier and dedicated resources described above.
### More on the GPU
The GPU agent does the heavy lifting of proving itself. Work is broken into power of 2 segments sizes (128K, 256K, 500K, 1M, 2M, 4M cycles). The GPU's amount of VRAM will dictate which power of 2 to use as the `SEGMENT_SIZE`.
As a general rule of thumb, for segment sizes of:
* 1 million cycles requires 9\~10GB of GPU VRAM
* 2 million cycles requires 17\~18GB of GPU VRAM
* 4 million cycles requires 32\~34GB of GPU VRAM
The performance optimization guide has a whole section on [segment size benchmarking](/provers/performance-optimization#finding-the-maximum-segment_size-for-gpu-vram).
### More on SNARK
This agent will convert a STARK proof into a SNARK proof using [rapidsnark](https://github.com/iden3/rapidsnark). Performance is dependent on core clocks *AND* thread counts. Having a lot of cores *but* a very low core clock speed can adversely affect performance for the SNARK process.
## REST API
The REST API provides a external interface to start / stop / monitor jobs and tasks within TaskDB. Bento is intended to be a drop in replacement for Bonsai, including being partially Bonsai API compatible. The [Bonsai API docs](https://api.bonsai.xyz/swagger-ui/) provide a good reference for the Bento REST API.
# Broker Configuration & Operation
Source: https://docs.boundless.network/provers/broker
Optimizing the Broker for optimal performance on the market.
## Overview
The Broker is a service that runs within the [Bento](/provers/proving-stack#what-is-bento) proving stack. It is responsible for market interactions including bidding on jobs, locking them, issuing job requests to the Bento proving cluster, and submitting proof fulfillments onchain.
## Broker Configuration
Broker will live-reload the `broker.toml` when it changes. In most cases, you will not need to restart the Broker for the configuration to take effect.
Broker configuration is primarily managed through the `broker.toml` file in the Boundless directory. This file is mounted into the Broker container and it is used to configure the Broker daemon.
### Deposit / Balance
The Boundless market requires funds (ZKC) deposited as collateral before a prover can bid on requests.
Brokers must first deposit some ZKC into the market contract to fund their account.
These funds cover collateral during lock-in.
It is recommend that a broker keep a balance on the market >= `max_collateral` (configured via broker.toml).
#### Deposit Collateral to the Market
You will need the Boundless CLI installed to deposit/check your balance.
Please see [Installing the Boundless CLI](/developers/tooling/cli#installation) for instructions.
```bash Terminal theme={null}
export RPC_URL=
export PRIVATE_KEY=
# Example: 'prover deposit-collateral 100'
boundless prover deposit-collateral
```
#### Check Current Collateral Balance
```bash Terminal theme={null}
export RPC_URL=
export PRIVATE_KEY=
boundless prover balance-collateral [wallet_address]
```
You can omit the `PRIVATE_KEY` environment variable here and specify your `wallet_address` as a optional parameter to the `balance` command, i.e., `account balance 0x000....`
### Settings in Broker.toml
Quotation marks matter in TOML so please pay particular attention to the quotation marks for config values.
Below are all `broker.toml` settings organized by section:
#### \[market] Settings
| Setting | Description |
| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| min\_mcycle\_price | The minimum price per mega-cycle (i.e. million RISC-V cycles) for the broker to attempt to lock an order. Accepts `" ETH"` (e.g., `"0.00002 ETH"`) or `" USD"` (e.g., `"0.02 USD"`); USD values are converted to ETH at runtime via the price oracle. Plain numbers without a suffix still work and default to ETH for backward compatibility. This value is used for both primary orders (compared against the order's ETH price per mcycle) and secondary fulfillment orders (converted to ZKC via price oracle for collateral-based comparisons). |
| expected\_probability\_win\_secondary\_fulfillment | Expected probability (as a percentage) of winning a secondary fulfillment proof race. When another prover fails to fulfill an order and is slashed, multiple provers may race to fulfill the order for the collateral reward. This setting scales the expected reward of those lock-expired orders by the configured percentage before comparing against `min_mcycle_price`. Default: `50`. Values below 100 discount the reward (conservative — fewer secondary orders picked up), 100 means no discount, values above 100 boost the reward (aggressive — more secondary orders picked up). |
| priority\_requestor\_addresses | Optional priority requestor addresses that can bypass the mcycle limit and max input size limit. If enabled, the order will be preflighted without constraints. |
| priority\_requestor\_lists | URLs to fetch requestor priority lists from. Requestor priority lists specify requestors that the broker should prioritize for proving. Requestors on these lists are considered more likely to request useful work with profitable pricing. Priority requestors are prioritized when there is a surplus of demand: their requests will be preflighted first, locked first (in situations where multiple orders exceed minimum lock pricing), and configuration for max\_mcycle\_limit and max\_file\_size will be skipped. These lists will be periodically refreshed and merged with priority\_requestor\_addresses. |
| peak\_prove\_khz | Estimated peak performance of the proving cluster, in kHz. Used to estimate proving capacity and accept only as much work as your prover cluster can handle. Estimates can be derived from benchmarking using the Boundless CLI. See [Benchmarking Bento](#benchmarking-bento). |
| max\_mcycle\_limit | Optional max cycles (in mcycles). Orders over this max\_cycles will be skipped after preflight. |
| max\_journal\_bytes | Max journal size in bytes. Orders that produce a journal larger than this size in preflight will be skipped. Since journals must be posted onchain to complete an order, an excessively large journal may prevent completion of a request. |
| min\_deadline | Min seconds left before the deadline to consider bidding on a request. If there is not enough time left before the deadline, the prover may not be able to complete proving of the request and finalize the batch for publishing before expiration. |
| lookback\_blocks | On startup, the number of blocks to look back for possible open orders. |
| max\_collateral | Maximum collateral amount that the broker will use to lock orders. Accepts `" ZKC"` (e.g., `"200 ZKC"`) or `" USD"` (e.g., `"100 USD"`); USD values are converted to ZKC at runtime via the price oracle. Requests that require a higher collateral amount than this will be skipped. |
| max\_file\_size | Max input / image file size allowed for downloading from request URLs. |
| max\_fetch\_retries | Max retries for fetching input / image contents from URLs. |
| assessor\_default\_image\_url | Default URL for assessor image. This URL will be tried first before falling back to the image URL from the boundless market contract. |
| set\_builder\_default\_image\_url | Default URL for set builder image. This URL will be tried first before falling back to the image URL from the set verifier contract. |
| max\_concurrent\_proofs | Maximum number of concurrent proofs that can be processed at once. Used to limit proof tasks spawned to prevent overwhelming the system. |
| max\_concurrent\_preflights | Maximum number of orders to concurrently preflight. Used to limit preflight tasks spawned to prevent overwhelming the system. Recommended default: `8` to be able to follow all market orders. |
| order\_pricing\_priority | Determines how orders are prioritized for pricing. Options: "random" (default, process orders in random order), "observation\_time" (prioritize orders in the order they were observed, FIFO), "shortest\_expiry" (prioritize orders with the earliest deadline first). |
| order\_commitment\_priority | Determines how orders are prioritized when committing to prove them. Options: "cycle\_price" (default, prioritize orders with the highest ETH price per cycle), "random" (process orders in random order), "shortest\_expiry" (prioritize orders with the earliest deadline first), "tightest\_deadline" (prioritize orders whose deadline is closest, giving preference to the most time-urgent work), "price" (prioritize orders with the highest ETH payment regardless of cycle count). |
| max\_critical\_task\_retries | Max critical task retries on recoverable failures. The broker service has a number of subtasks. Some are considered critical. If a task fails, it will be retried, but after this number of retries, the process will exit. |
| allow\_client\_addresses | Optional allow list for customer address. If enabled, all requests from clients not in the allow list are skipped. |
| deny\_requestor\_addresses | Optional deny list for requestor address. If enabled, all requests from clients in the deny list are skipped. |
| lockin\_priority\_gas | Optional additional gas to add to the transaction for lockinRequest, good for increasing the priority if competing with multiple provers during the same block. |
| balance\_warn\_threshold | Optional balance warning threshold (in native token). If the submitter balance drops below this the broker will issue warning logs. |
| balance\_error\_threshold | Optional balance error threshold (in native token). If the submitter balance drops below this the broker will issue error logs. |
| collateral\_balance\_warn\_threshold | Optional collateral balance warning threshold (in collateral tokens). If the collateral balance drops below this the broker will issue warning logs. |
| collateral\_balance\_error\_threshold | Optional collateral balance error threshold (in collateral tokens). If the collateral balance drops below this the broker will issue error logs. |
| cache\_dir | Optional cache directory for storing downloaded images and inputs. If not set, files will be re-downloaded every time. |
| lockin\_gas\_estimate | Gas estimate for lockin call. Used for estimating the gas costs associated with an order during pricing. If not set a conservative default will be used. |
| fulfill\_gas\_estimate | Gas estimate for fulfill call. Used for estimating the gas costs associated with an order during pricing. If not set a conservative default will be used. |
| groth16\_verify\_gas\_estimate | Gas estimate for proof verification using the RiscZeroGroth16Verifier. Used for estimating the gas costs associated with an order during pricing. If not set a conservative default will be used. |
| max\_order\_expiry\_secs | Maximum order expiry duration in seconds (current time to order expiry time). Orders exceeding this duration are skipped. Prevents orders with long deadlines from tying up resources or storing inputs beyond MinIO's TTL. |
| min\_mcycle\_price\_overrides | Optional per-requestor and per-selector overrides for `min_mcycle_price`. Allows fine-grained pricing control so you can set different minimum prices for specific requestors or program selectors. See example below. |
| telemetry\_mode | Controls broker telemetry reporting. `"full"` (default) sends anonymized health and performance metrics to help the Boundless team diagnose issues across the network. No sensitive data (private keys, wallet balances) is collected. Set to `"logsonly"` to disable remote reporting and only emit local debug logs. |
#### \[prover] Settings
| Setting | Description |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| status\_poll\_retry\_count | Number of retries to poll for proving status. Provides a little durability for transient failures. |
| status\_poll\_ms | Polling interval to monitor proving status (in millisecs). |
| req\_retry\_count | Number of retries to query a prover backend for on failures. Used for API requests to a prover backend, creating sessions, preflighting, uploading images, etc. Provides a little durability for transient failures. |
| req\_retry\_sleep\_ms | Number of milliseconds to sleep between retries. |
| proof\_retry\_count | Number of retries for running the entire proof generation process. This is separate from the request retry count, as the proving process is a multi-step process involving multiple API calls to create a proof job and then polling for the proof job to complete. |
| proof\_retry\_sleep\_ms | Number of milliseconds to sleep between proof retries. |
| set\_builder\_guest\_path | Set builder guest program (ELF) path. When using a durable deploy, set this to the published current SOT guest program path on the system. |
| assessor\_set\_guest\_path | Assessor ELF path. |
| reaper\_interval\_secs | Interval for checking expired committed orders (in seconds). This is the interval at which the ReaperTask will check for expired orders and mark them as failed. If not set, it defaults to 60 seconds. |
| reaper\_grace\_period\_secs | Grace period before marking expired orders as failed (in seconds). This provides a buffer time after an order expires before the reaper marks it as failed. This helps prevent race conditions with the aggregator that might be processing the order. If not set, it defaults to 10800 seconds (3 hours). |
#### \[batcher] Settings
| Setting | Description |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| batch\_max\_time | Max batch duration before publishing (in seconds). |
| min\_batch\_size | Batch size (in proofs) before publishing. |
| block\_deadline\_buffer\_secs | Batch blocktime buffer. Number of seconds before the lowest block deadline in the order batch to flush the batch. This should be approximately snark\_proving\_time \* 2. |
| txn\_timeout | Timeout, in seconds for transaction confirmations. |
| single\_txn\_fulfill | Use the single TXN submission that batches submit\_merkle / fulfill\_batch into a single transaction. Requires the `submitRootAndFulfill` method be present on the deployed contract. |
| withdraw | Whether to withdraw from the prover balance when fulfilling. |
| batch\_poll\_time\_ms | Polling time, in milliseconds. The time between polls for new orders to aggregate and how often to check for batch finalize conditions. |
| batch\_max\_journal\_bytes | Max combined journal size (in bytes) that once exceeded will trigger a publish. |
| batch\_max\_fees | Max batch fees (in ETH) before publishing. |
| max\_submission\_attempts | Number of attempts to make to submit a batch before abandoning. |
#### \[price\_oracle] Settings
The price oracle fetches live ETH/USD and ZKC/USD exchange rates, enabling broker config values to be specified in USD. Chainlink (on-chain, ETH/USD only) and CoinGecko (off-chain, both pairs) are enabled by default; CoinMarketCap is optionally available with an API key. Prices from multiple sources are aggregated using the configured `aggregation_mode`.
| Setting | Description |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| eth\_usd | Price source for ETH/USD. `"auto"` (default) uses live oracle prices. Set to a static number (e.g., `"2500.00"`) to fix the price and disable live fetching. |
| zkc\_usd | Price source for ZKC/USD. `"auto"` (default) uses live oracle prices. Set to a static number (e.g., `"1.00"`) to fix the price and disable live fetching. |
| refresh\_interval\_secs | How often to refresh prices from sources, in seconds (default: `60`). |
| aggregation\_mode | How to combine prices from multiple sources: `"median"` (default), `"priority"` (use first available source), or `"average"`. |
| max\_secs\_without\_price\_update | Maximum seconds without a successful price update before the broker logs an error (default: `43200` / 12h). Set to `0` to disable this check. |
**Example configuration with USD-denominated pricing:**
```toml theme={null}
[market]
min_mcycle_price = "0.02 USD"
max_collateral = "100 USD"
expected_probability_win_secondary_fulfillment = 50
[price_oracle]
eth_usd = "auto"
zkc_usd = "auto"
```
**Example per-requestor and per-selector pricing overrides:**
```toml theme={null}
[market]
min_mcycle_price = "0.02 USD"
# Override min_mcycle_price for a specific requestor address
[[market.min_mcycle_price_overrides]]
requestor = "0x1234...abcd"
min_mcycle_price = "0.01 USD"
# Override min_mcycle_price for a specific program selector
[[market.min_mcycle_price_overrides]]
selector = "aabbccdd"
min_mcycle_price = "0.03 USD"
```
### Chain Monitor
v2.0 introduces a rewritten chain-watching layer, `ChainMonitorV2`, that replaces the legacy two-service architecture (`ChainMonitorService` + `MarketMonitor`). Instead of polling `eth_getLogs` for market events on every tick, `ChainMonitorV2` uses `eth_getBlockReceipts` to fetch all receipts per block in a single call, then filters for market events locally. This reduces the number of RPC calls, especially on chains like Base where `eth_getLogs` is expensive or rate-limited.
In steady state, `ChainMonitorV2` requires roughly **2 RPC requests per block**:
* `eth_getBlockByNumber` to follow the chain head and get the base fee
* `eth_getBlockReceipts` to get all receipts and transaction fee data
It also performs local EIP-1559 gas estimation from receipt data, removing the need for separate `eth_feeHistory` calls.
On startup, `ChainMonitorV2` uses adaptive log retrieval with binary-search chunking to auto-discover the maximum block range accepted by the RPC provider, catching up on any missed events for open orders. This means the broker won't miss events if it restarts or experiences downtime.
#### Selecting the chain monitor
Selection is per-chain via the `rpc_mode` field under `[market]` in `broker.toml` (or in `chain-overrides/broker.{chain_id}.toml`):
```toml broker.toml theme={null}
[market]
rpc_mode = "auto" # or "v2" or "legacy"
```
* `"auto"` (default): chain-specific default. Uses `v2` for Base and most chains, `legacy` for Taiko (167000), where the public RPC rate-limits `eth_getBlockReceipts`.
* `"v2"`: pin to `ChainMonitorV2`.
* `"legacy"`: pin to the old `ChainMonitorService` + `MarketMonitor` pair (`eth_getLogs`-based).
You can tune the RPC request timeout (default 15s) to cut off hanging requests so the retry and fallback layers can activate:
```bash Terminal theme={null}
export BROKER_EXTRA_ARGS="--rpc-request-timeout 15"
just prover
```
#### Sequential Fallback Transport
`ChainMonitorV2` uses a `SequentialFallbackTransport` that tries RPC providers in priority order (rather than in parallel), minimizing calls to paid or metered fallback endpoints. When multiple RPC URLs are configured, the transport includes health tracking: it skips providers after consecutive failures and periodically retries them to detect recovery.
The retry and fallback layers are stacked so that on a single RPC failure, the sequential fallback immediately tries the next URL. The outer retry layer only kicks in once all URLs have been exhausted.
#### Analyzing RPC Usage
To analyze RPC call patterns, enable debug logging for the relevant modules and pipe the output to a log file. An [analysis script](https://github.com/boundless-xyz/protocol-experiments/tree/main/broker-rpc) is available to parse these logs:
```bash Terminal theme={null}
RISC0_DEV_MODE=1 \
RUST_LOG=INFO,broker::chain_monitor_v2=debug,broker::rpcmetrics=debug \
BROKER_EXTRA_ARGS="--listen-only" \
just prover 2>&1 | tee rpc_metrics.log
```
Not all RPC providers support the `eth_getBlockReceipts` method that `ChainMonitorV2` relies on. If your provider doesn't support it, set `rpc_mode = "legacy"` for the affected chain. Free public RPCs are unreliable; use a dedicated RPC provider for production.
## Broker Operation
```txt Terminal theme={null}
2024-10-23T14:37:37.364844Z INFO bento_cli: image_id: a0dfc25e54ebde808e4fd8c34b6549bbb91b4928edeea90ceb7d1d8e7e9096c7 | input_id: eccc8f06-488a-426c-ae3d-e5acada9ae22
2024-10-23T14:37:37.368613Z INFO bento_cli: STARK job_id: 0d89e2ca-a1e3-478f-b89d-8ab23b89f51e
2024-10-23T14:37:37.369346Z INFO bento_cli: STARK Job running....
2024-10-23T14:37:39.371331Z INFO bento_cli: STARK Job running....
2024-10-23T14:37:41.373508Z INFO bento_cli: STARK Job running....
2024-10-23T14:37:43.375780Z INFO bento_cli: Job done!
```
### Benchmarking Bento
Start a bento cluster:
```bash Terminal theme={null}
just bento
```
Set the `RPC_URL` environment variable to the network the order is on:
```bash Terminal theme={null}
export RPC_URL=
```
Then, run the benchmark:
```bash Terminal theme={null}
boundless prover benchmark --request-ids
```
where IDS is a comma-separated list of request IDs from the network or order stream configured.
It is recommended to pick a few requests of varying sizes and programs, biased towards larger proofs for a more representative benchmark.
To run programs manually, and for performance optimizations, see [performance optimizations](/provers/performance-optimization).
### Running the Broker service with bento
Running a broker with `just` will also start the Bento cluster through docker compose.
`just` installation instructions can be found [here](https://github.com/casey/just#installation).
```bash Terminal theme={null}
just broker
```
### Make sure Bento is running
A Broker needs a [Bento](/provers/proving-stack#what-is-bento) instance to operate. Please follow the [quick start](/provers/quick-start) guide to get Bento up and running.
To check Bento is running correctly, you can send a sample proof workload:
> Before running this, [install Bento CLI](/provers/quick-start#running-a-test-proof)
```bash Terminal theme={null}
# In the bento directory
RUST_LOG=info bento_cli -c 32
```
### Running a standalone broker
To run broker with an already initialized Bento cluster or with a different prover, you can build and run a broker directly with the following:
```bash Terminal theme={null}
cargo build --bin broker --release
# Run with flags or environment variables based on network/configuration
./target/release/broker
```
### Stopping The Broker Service
```bash Terminal theme={null}
just broker down
```
If running the broker on a network, there may be locked proofs that have not been fulfilled yet. Follow the [Safe Upgrade Steps](#safe-upgrade-steps) to ensure shutdown and/or restart without loss of stake.
## Safe Upgrade Steps
There can be subtle breaking changes between releases that may affect your broker's state. Following these upgrade steps helps minimize issues from state breaking changes.
When upgrading your Boundless broker to a new version, follow these steps to ensure a safe migration:
```bash Terminal theme={null}
just broker clean
# Or stop the broker without clearing volumes
just broker down
```
This will wait for any committed orders to finalize before shutting down. Avoid sending kill signals to the broker process and ensure either through the broker logs or through indexer that your broker does not have any incomplete locked orders before proceeding.
While it is generally not necessary to clear volumes unless specifically noted in release, it is recommended to avoid any potential state breaking changes.
See [releases](https://github.com/boundless-xyz/boundless/releases) for latest tag to use.
```bash Terminal theme={null}
git checkout
# Example: git checkout v0.9.0
```
```bash Terminal theme={null}
just broker
```
## Running Multiple Brokers
You can run multiple broker instances simultaneously to serve different networks at the same time while sharing the same Bento cluster. The Docker compose setup supports this through the `broker2` service example.
### Multi-Broker Configuration
Each broker instance requires:
1. **Separate configuration file**: Create different `broker.toml` files (e.g., `broker.toml`, `broker2.toml`, etc.)
2. **Different RPC URL**: Use different chain endpoints via setting respective `RPC_URL` environment variables, or modifying the compose file manually (`prover-compose.yml` by default, `compose.yml` for the legacy stack).
3. **Optional separate private key**: Use different `PRIVATE_KEY` variables if desired for different accounts on different networks.
### Environment Variables for Multi-Broker Setup
If using the default compose file (`prover-compose.yml`, or `compose.yml` if `PROVER_STACK=legacy` is set) and uncommenting the second broker config:
```bash [.env] theme={null}
# Export environment variables for the first broker
export RPC_URL=
export PRIVATE_KEY=0x...
# Export environment variables for the second broker
export RPC_URL_2=
```
Then, create the new broker config file that the second broker will use:
```bash Terminal theme={null}
# Copy from an existing broker config file
cp broker.toml broker2.toml
# Or creating one from a fresh template
cp broker-template.toml broker2.toml
```
Then, modify configuration values for each network, keeping the following in mind:
* The `peak_prove_khz` setting is shared across all brokers
* For example, if you have [benchmarked](#benchmarking-bento) your broker to be able to prove at 500kHz, the values in each config should not sum up to be more than 500kHz.
* `max_concurrent_preflights` defaults to `8` and should be set to a value that the bento cluster can keep up with
* It is recommended that the max concurrent preflights across all networks is less than the number of exec agents you have specified in your compose file (`prover-compose.yml` by default, `compose.yml` for the legacy stack).
* `max_concurrent_proofs` is a per-broker configuration, and is not shared across brokers
Then, just start the cluster as you normally would with:
```bash Terminal theme={null}
just broker
```
## Broker Optimization
### Increasing Lock-in Rate
Once your broker is running, there are a few methods to optimize the lock-in rate. These methods are aimed at making your broker service more competitive in the market through different means:
1. Decreasing the `min_mcycle_price` would tune your Broker to bid at lower prices for proofs. This value can be specified in ETH (e.g., `"0.00001 ETH"`) or USD (e.g., `"0.02 USD"`).
2. Increasing `lockin_priority_gas` expedites your market operations by consuming more gas which could help outrun other bidders.
### Tuning Service Settings
The `[prover]` settings in `broker.toml` are used to configure the prover service and significantly impact the operation of the service. The most important configuration variable to monitor and iteratively tune is `txn_timeout`. This is the number of seconds to wait for a transaction to be confirmed before timing out. Therefore, if you see timeouts in your logs, `txn_timeout` can be increased to wait longer for transaction confirmations onchain.
# Boundless CLI
Source: https://docs.boundless.network/provers/cli
An overview of the Boundless CLI for requestors, provers and claiming rewards.
## Overview
`boundless` is a command-line interface for interacting with the Boundless Market, specifically designed to help requestors and provers with common interactions
## Installation
The Boundless CLI source code can be found at [boundless/crates/boundless-cli](https://github.com/boundless-xyz/boundless/tree/main/crates/boundless-cli).
You'll need to [install Rust](https://doc.rust-lang.org/cargo/getting-started/installation.html), then you can run the following command to install the CLI.
```bash theme={null}
cargo install --locked --git https://github.com/boundless-xyz/boundless boundless-cli --branch release-2.0 --bin boundless
```
## Overview
### What is a module?
Once installed, simply running `boundless` gives a helpful overview:
The CLI has three main modules:
* [Requestor Module](/developers/tooling/cli#requestor): Commands for submitting proof requests
* [Prover Module](/developers/tooling/cli#prover): Commands for locking, executing, proving and fulfilling proof requests
* [Rewards Module](/developers/tooling/cli#rewards): Commands for staking and delegating \$ZKC, claiming staking and mining rewards, seeing recent rewards and more.
### Module setup
Each module has a respective interactive `setup` command which allows you to store *(in plaintext!)* variables such as relevant RPC URLs, private keys etc:
```bash theme={null}
boundless setup
```
These setup commands store secrets in the `~/.boundless/` directory. Any further command calls will first check this location for the relevant secrets, otherwise it will check the flags passed directly to the command.
### Multi-chain support
The `requestor`, `prover`, and `rewards` modules each track their own *active network*. The `setup` wizard prompts you to pick a network and stores the per-chain RPC URL and private key under `~/.boundless/secrets.toml`, keyed by chain. Subsequent commands use the active network for that module.
Supported market networks (used by `requestor` and `prover`):
| Chain ID | Name |
| -------- | ---------------- |
| 8453 | Base Mainnet |
| 167000 | Taiko Mainnet |
| 11155111 | Ethereum Sepolia |
| 84532 | Base Sepolia |
The `rewards` module operates on Ethereum L1 (Mainnet or Sepolia).
To list supported networks for a module and see which one is currently active:
```bash theme={null}
boundless prover networks
```
To switch the active network, pass `--set` with a name or chain ID:
```bash theme={null}
boundless prover networks --set "Taiko Mainnet"
# or
boundless prover networks --set 167000
```
Switching changes which chain the module targets. Per-chain credentials configured during `setup` stay associated with their chains, so you can flip between them without re-entering credentials.
### Command help pages
For a detailed help page for a specific command, run the command with the `--help` flag, for example:
```bash theme={null}
boundless prover deposit-collateral --help
```
which will detail all the relevant options annd mandatory flags:
```bash theme={null}
Usage: boundless prover benchmark [OPTIONS] --request-ids
Options:
--request-ids
Proof request ids to benchmark
-h, --help
Print help (see a summary with '-h')
Prover:
--prover-rpc-url
RPC URL for the prover network
[env: PROVER_RPC_URL=]
--prover-private-key
Private key for prover transactions
[env: PROVER_PRIVATE_KEY]
...
```
## Modules
### Requestor
The requestor module allows requestors to deposit/withdraw funds into the market, submit proof requests, track the status of any proof request, and get and verify proofs from the Boundless market.
To see all available requestor module commands, run:
```bash theme={null}
boundless requestor --help
```
#### Requestor Commands
```bash theme={null}
Usage: boundless requestor [OPTIONS]
Commands:
config Show requestor configuration status
deposit Deposit funds into the market
deposit-to Deposit funds into the market on behalf of another address
withdraw Withdraw funds from the market
deposited-balance Check the balance of an account in the market
balance Check the balance of an account (alias for deposited-balance)
submit-file Submit a fully specified proof request from a YAML file
submit Submit a proof request constructed with the given offer, input, and image
status Get the status of a given request
get-proof Get the journal and seal for a given request
verify-proof Verify the proof of the given request
setup Interactive setup wizard for requestor configuration
networks List supported networks or switch the active network
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
Global Options:
--tx-timeout Ethereum transaction timeout in seconds [env: TX_TIMEOUT=]
--log-level Log level (error, warn, info, debug, trace) [env: LOG_LEVEL=] [default: warn]
Module Configuration:
Run 'boundless requestor setup' for interactive setup
Alternatively set environment variables:
REQUESTOR_RPC_URL RPC endpoint for requestor module
REQUESTOR_PRIVATE_KEY Private key for requestor transactions
BOUNDLESS_MARKET_ADDRESS Market contract address (optional, has default)
SET_VERIFIER_ADDRESS Verifier contract address (optional, has default)
Or configure while executing commands:
Example: boundless requestor balance --requestor-rpc-url --requestor-private-key
```
#### Depositing on behalf of another address
`deposit-to` credits the requestor balance of a different address, with the caller paying for the transaction. Useful for funding a customer or shared requestor account.
```bash Terminal theme={null}
# Deposit 0.01 ETH to 's requestor balance, paid by your wallet.
boundless requestor deposit-to 0.01 --to 0xRecipientAddress
```
Only the recipient (holder of the recipient's private key) can later withdraw those funds.
### Prover
The prover module allows provers to deposit collateral into the market, lock and fulfill orders, carry out benchmarking, manually execute guest programs from the market (useful for debugging), and provides functionality to manually slash an order.
To see all available prover module commands, run:
```bash theme={null}
boundless prover --help
```
#### Prover Commands
```bash theme={null}
Usage: boundless prover [OPTIONS]
Commands:
config Show prover configuration status
deposit-collateral Deposit collateral funds into the market
deposit-collateral-to Deposit collateral funds into the market on behalf of another address
withdraw-collateral Withdraw collateral funds from the market
balance-collateral Check the collateral balance of an account
lock Lock a request in the market
fulfill Fulfill one or more proof requests
execute Execute a proof request using the RISC Zero zkVM executor
benchmark Benchmark proof requests
slash Slash a prover for a given request
setup Interactive setup wizard for prover configuration
networks List supported networks or switch the active network
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
Global Options:
--tx-timeout Ethereum transaction timeout in seconds [env: TX_TIMEOUT=]
--log-level Log level (error, warn, info, debug, trace) [env: LOG_LEVEL=] [default: info]
Module Configuration:
Run 'boundless prover setup' for interactive setup
Alternatively set environment variables:
PROVER_RPC_URL RPC endpoint for prover module
PROVER_PRIVATE_KEY Private key for prover transactions
BOUNDLESS_MARKET_ADDRESS Market contract address (optional, has default)
SET_VERIFIER_ADDRESS Verifier contract address (optional, has default)
Or configure while executing commands:
Example: boundless prover balance --prover-rpc-url --prover-private-key
```
#### Depositing collateral on behalf of another address
`deposit-collateral-to` credits the collateral balance of a different prover address, with the caller paying for the transaction. Useful for shared treasuries, automated top-up services, or staking on behalf of a managed prover.
```bash Terminal theme={null}
# Deposit 50 ZKC of collateral to , paid by your wallet.
boundless prover deposit-collateral-to 50 --to 0xRecipientProverAddress
```
If the collateral token supports EIP-2612 permit (Base ZKC does), the deposit is one transaction; otherwise it's an `approve` + `depositCollateralTo` two-step. Only the recipient can later withdraw their collateral.
### Rewards
The rewards module allows provers to deposit collateral into the market, lock and fulfill orders, carry out benchmarking, manually execute guest programs from the market (useful for debugging), and provides functionality to manually slash an order.
For a full walkthrough on the ZK mining process, see [ZK Mining Overview](/zkc/mining/overview).
To see all available rewards module commands, run:
```bash theme={null}
boundless rewards --help
```
#### Reward Commands
```bash theme={null}
Usage: boundless rewards [OPTIONS]
Commands:
config Show rewards configuration status
stake-zkc Stake ZKC tokens
balance-zkc Check ZKC balance
staked-balance-zkc Check staked ZKC balance
list-staking-rewards List staking rewards by epoch
list-mining-rewards List mining rewards by epoch
prepare-mining Prepare mining work log update
submit-mining Submit mining work updates
claim-mining-rewards Claim mining rewards
claim-staking-rewards Claim staking rewards
delegate Delegate rewards to another address
get-delegate Get rewards delegate
epoch Get current epoch information
power Check reward power and earning potential
inspect-mining-state Inspect mining state file and display detailed statistics
setup Interactive setup wizard for rewards configuration
networks List supported networks or switch the active network
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
Global Options:
--tx-timeout Ethereum transaction timeout in seconds [env: TX_TIMEOUT=]
--log-level Log level (error, warn, info, debug, trace) [env: LOG_LEVEL=] [default: warn]
Module Configuration:
Run 'boundless rewards setup' for interactive setup
Alternatively set environment variables:
REWARD_RPC_URL RPC endpoint for rewards module
REWARD_PRIVATE_KEY Private key for reward transactions
STAKING_PRIVATE_KEY Private key for staking (can differ from reward key)
MINING_STATE_FILE Path to mining state file (optional)
ZKC_ADDRESS ZKC token contract (optional, has default)
VEZKC_ADDRESS Staked ZKC NFT contract (optional, has default)
STAKING_REWARDS_ADDRESS Rewards distribution contract (optional, has default)
BEACON_API_URL Beacon API URL (optional)
Or configure while executing commands:
Example: boundless rewards balance-zkc --reward-rpc-url --staking-private-key
```
## Requesting a Proof via the Boundless CLI
In early testing, and when trying out new order parameters, it can be useful to submit a request via the Boundless CLI.
The Boundless CLI builds upon the [`boundless_market`](https://docs.rs/boundless-market/latest/boundless_market) library.
It covers multiple market interactions such as submitting proof requests, cancelling requests, executing dry runs, requesting the status of a given request, retrieving the journal and seal of a fulfilled request and verifying a proof.
To submit a proof, a valid `request.yaml` is required, this config file will specify the parameters of the request:
* Request ID
* This can be specified, or if set to 0, a random ID will be assigned.
* Requirements:
* The image ID of the program being proven.
* The contents of the outputs of the program, the journal.
This is to make sure the outputs are as expected (e.g., to ensure the right input was provided, by checking an input digest committed to the journal).
* Image URL
* The link to the program stored on any public HTTP server.
IPFS, used through a gateway, works well (Boundless will support IPFS URLs natively in the future).
* Input:
* The input bytes are passed to the program for execution.
The input can have any encoding. The bytes will be passed to the guest without modification.
* Offer:
* This includes the minimum and maximum price for the proof request, the block number to open bidding, the price ramp up period, how many blocks before the request should timeout, and the lock-in stake the prover has to escrow to submit a bid.
Below is an example of a `request.yaml` file that can be used with the `boundless submit request` command.
```yaml request.yaml theme={null}
# Unique ID for this request, constructed from the client address and a 32-bit index.
# Constructed as (address(client) << 32) | index
id: 0 # if set to 0, gets overwritten by a random id
# Specifies the requirements for the delivered proof, including the program that must be run,
# and the constraints on the journal's value, which define the statement to be proven.
requirements:
imageId: "53cb4210cf2f5bf059e3a4f7bcbb8e21ddc5c11a690fd79e87947f9fec5522a3"
predicate:
predicateType: PrefixMatch
data: "53797374"
callback:
addr: "0x0000000000000000000000000000000000000000"
gasLimit: 0
selector: "00000000"
# A public URI where the program (i.e. image) can be downloaded. This URI will be accessed by
# provers that are evaluating whether to bid on the request.
imageUrl: "https://gateway.beboundless.cloud/ipfs/bafkreie5vdnixfaiozgnqdfoev6akghj5ek3jftrsjt7uw2nnuiuegqsyu"
# Input to be provided to the zkVM guest execution.
# The input data is a encoded guest environment.
# See crates/boundless-market/src/input.rs for additional details.
input:
inputType: Inline
data: "0181a5737464696edc003553797374656d54696d65207b2074765f7365633a20313733383030343939382c2074765f6e7365633a20363235373837303030207d"
# Offer specifying how much the client is willing to pay to have this request fulfilled
# Note: prices here are in wei (ETH) and lockCollateral in the smallest ZKC unit (18 decimals).
# When using the Boundless SDK instead of the CLI, prices can be specified in USD
# (e.g., "0.50 USD") and are converted to ETH/ZKC at runtime via the price oracle.
offer:
minPrice: 100000000000000
maxPrice: 2000000000000000
rampUpStart: 0 # if set to 0, gets overwritten by the current UNIX timestamp
rampUpPeriod: 300
timeout: 3600 # 1 hor
lockTimeout: 2700 # 45 minutes
lockCollateral: 5000000000000000000 # 5 ZKC tokens
```
To submit a request, export or create a `.env` file with the following environment variables:
```bash Terminal theme={null}
export RPC_URL="https://ethereum-sepolia-rpc.publicnode.com"
export PRIVATE_KEY="YOUR_SEPOLIA_WALLET_PRIVATE_KEY"
```
Then run the following command:
```bash Terminal theme={null}
RUST_LOG=info boundless request submit request.yaml
```
To wait until the submitted request has been fulfilled, the `--wait` option can be added:
```bash Terminal theme={null}
# [!code word:--wait]
RUST_LOG=info boundless request submit request.yaml --wait
```
And to submit the request to the offchain order-stream service, make a deposit and then run `request submit` with `--offchain`.
```bash Terminal theme={null}
# [!code word:--offchain]
RUST_LOG=info boundless account deposit 0.002 # Enough for the request above; deposit more to cover multiple requests.
RUST_LOG=info boundless request submit request.yaml --wait --offchain
```
# Monitoring
Source: https://docs.boundless.network/provers/monitoring
Some tips on monitoring your prover.
For technical support, please post your questions on the [Boundless Discussions Forum](https://github.com/boundless-xyz/boundless/discussions).
## Telemetry
The broker includes an optional telemetry service that reports anonymized health and performance metrics. When enabled, this helps the Boundless team identify issues, diagnose bugs, and prioritize improvements across the network. Telemetry is designed to be lightweight and non-intrusive — no sensitive data such as private keys or wallet balances is collected.
Telemetry is **enabled by default**. To opt out, add the following to your `broker.toml`:
```toml broker.toml theme={null}
[market]
telemetry_mode = "logsonly"
```
This switches to local debug logging only, with no data sent to any remote service.
## Grafana
The Bento / Broker Docker compose stack includes a [Grafana](https://grafana.com/) instance with some template dashboards. To access them, Grafana is hosted at `http://localhost:3000`. Default credentials are defined in `.env.broker-template` as `admin:admin`.
### Bento Dashboard
The Bento dashboard connects to the TaskDB PostgreSQL instance to get live data for the status of different proofs flowing through the proving cluster. It is useful to monitor performance and queue depth.
### Broker Dashboard
The broker dashboard connects to the broker's SQLite database to see the status of different orders and batches moving through the broker's workflows.
## Onchain
The recommended method to monitor your broker's activity and health is via the [Boundless Explorer](https://explorer.boundless.network).
### Balances
For smooth broker operation, it is critical to monitor both your hot wallet balance of ETH and the market balance of collateral.
If your broker runs out of ETH balance, it will be unable to cover gas costs for transactions and as such it will be unable to lock and fulfill orders.
If you running low of collateral funds on the broker account on the market contract, the broker will be unable to lock-in orders with higher collateral values.
It is strongly recommended to keep your market balance above the broker's configured `max_collateral` parameter (which may be specified in ZKC or USD — check your `broker.toml` to see the current value).
### Broker Logs
Unrecoverable errors may cause the broker process to exit. Make sure to have a restart policy on the container. As the software matures, more errors should become recoverable.
The broker logs are the very helpful for monitoring broker interactions with the market. It is designed with the intention that `DEBUG` / `INFO` / `WARN` log should not require manual intervention, but anything logged at an `ERROR` level should be a cause of concern.
To see a live stream of the broker logs:
```bash Terminal theme={null}
docker compose logs -f broker
```
and to see the last 100 lines of the logs:
```bash Terminal theme={null}
docker compose logs --tail=100 broker
```
# Performance Optimization
Source: https://docs.boundless.network/provers/performance-optimization
Some tips on optimizing your prover setup.
Bento performance is tightly coupled to the specific environment, equipment, and compute provider's requirements. Recommendations in this guide should be used as reference rather than a concrete recommendation on configuration.
## Recommended Tools
We recommend the following tools to monitor performance and resource use:
* [nvtop](https://github.com/Syllo/nvtop) - A tool to monitor GPU utilization.
* [htop](https://htop.dev) - A tool to monitor CPU utilization, system memory, and process status.
Both of these tools warrant a decent size terminal window on your desktop to monitor the performance during experiments.
## Testing
### Isolating Tests
Operating competing workloads can lead to unpredictable results. We recommend isolating your test system from other workloads to ensure that the performance tuning results are consistent and reliable. This includes stopping configured [Broker](/provers/broker#stopping-the-broker-service) services:
```bash Terminal theme={null}
docker ps
docker stop
```
Alternatively you can start Bento without running the broker service:
```bash Terminal theme={null}
just bento
```
### Defining a Test Harness
It is recommended to benchmark using an example of your actual workload. Using a representative workload will provide more accurate turn around times, and validate your program and inputs file for the proofs you plan to generate.
> Before running this, [install Bento CLI](/provers/quick-start#running-a-test-proof)
To try a realistic example:
```bash Terminal theme={null}
RUST_LOG=info bento_cli -f /path/to/program -i /path/to/input`
```
If you intend to operate across a variety of different workloads (such as those that may be fed by the [Broker](/provers/broker)), you can also use the following command to generate a synthetic workload:
```bash Terminal theme={null}
RUST_LOG=info bento_cli -c
```
where `` is the number of times the synthetic guest is executed. A value of 4096 is a good starting point, however on smaller or less performant hosts, you may want to reduce this to 2048 or 1024 while performing some of your experiments. For functional testing, 32 is sufficient.
The typical test process will be:
1. Start `nvtop` and `htop`
2. Execute the test harness above and copy the job id
3. Upon completion of the job use the [`script/job_status.sh`](https://github.com/boundless-xyz/boundless/blob/main/scripts/job_status.sh) to view the results
#### Example test run of 1024 iterations:
```bash Terminal theme={null}
RUST_LOG=info bento_cli -c 1024
```
```txt Terminal theme={null}
2024-10-17T15:27:34.469227Z INFO bento_cli: image_id: a0dfc25e54ebde808e4fd8c34b6549bbb91b4928edeea90ceb7d1d8e7e9096c7 | input_id: 3740ebbd-3bef-475f-b23d-6c2bf96c6551
2024-10-17T15:27:34.479904Z INFO bento_cli: STARK job_id: 895a996b-b0fa-4fc8-ae7a-ba92eeb6b0b1
2024-10-17T15:27:34.480919Z INFO bento_cli: STARK Job running....
....
2024-10-17T15:27:56.509275Z INFO bento_cli: STARK Job running....
2024-10-17T15:27:58.513718Z INFO bento_cli: Job done!
```
```bash Terminal theme={null}
bash scripts/job_status.sh 895a996b-b0fa-4fc8-ae7a-ba92eeb6b0b1
```
```txt Terminal theme={null}
jobs_count
------------
19
(1 row)
remaining_jobs
----------------
0
(1 row)
task times:
task_id | task_type | state | wall_time | started_at
---------+-----------+-------+-----------+----------------------------
init | Executor | done | 0.530216 | 2024-10-17 15:27:35.00974
0 | Prove | done | 3.299771 | 2024-10-17 15:27:35.661319
1 | Prove | done | 3.129968 | 2024-10-17 15:27:35.818467
3 | Prove | done | 2.998964 | 2024-10-17 15:27:38.963914
2 | Join | done | 1.123467 | 2024-10-17 15:27:38.9684
4 | Prove | done | 2.901972 | 2024-10-17 15:27:40.105599
7 | Prove | done | 3.001664 | 2024-10-17 15:27:41.977001
5 | Join | done | 1.237363 | 2024-10-17 15:27:43.022033
6 | Join | done | 1.154148 | 2024-10-17 15:27:44.273276
8 | Prove | done | 3.096732 | 2024-10-17 15:27:44.992537
(10 rows)
Effective Hz:
hz | total_cycles | elapsed_sec
---------------------+--------------+-------------
399385.599822715909 | 8650752 | 21.660150
(1 row)
```
In the final table, the effective Hz is the primary metric for consideration. This represents the (number of cycles) / (elapsed wallclock time). In the example above, the effective Hz is roughly 400kHz.
In the `job_status.sh` output above, the Hz is only accurate if the job has completed with no error conditions. Failed and in-progress jobs will have an inflated Hz value.
## Finding the Maximum `SEGMENT_SIZE` for GPU VRAM
We will start by optimizing the GPU workers. This is because the bulk of the RISC Zero workload is executed by the `gpu-agent` and GPU resources are most often the performance bottleneck.
### What is the Segment Size?
An important concept to understand for testing is [RISC Zero's continuations](https://dev.risczero.com/api/recursion). Continuations are the key mechanism that allow RISC Zero's zkVM to scale to effectively handle arbitrarily large proofs.
The CPU first runs the workload in a pre-flight stage where it doesn't engage in proving, while doing so it divides the program trace into a series of [segments](https://dev.risczero.com/terminology#segment). In Bento ,these segments are then dispatched to various workers for proving, and are combined back together in the final stage to produce the proof.
The key tuning parameter of continuations is `SEGMENT_SIZE` in the `.env.broker` file. A proof is divided into `(2^SEGMENT_SIZE)` sized segments. The default value is 21, which means that a proof is composed of the number of required segments of approximately 1M cycles `(2^20 = 1048576)`.
`SEGMENT_SIZE has` some practical implications, related to GPU VRAM capacity. Below is a set of guidelines for setting `SEGMENT_SIZE` maximums:
| VRAM | `SEGMENT_SIZE` Max |
| ---- | ------------------ |
| 8GB | 19 |
| 16GB | 20 |
| 20GB | 21 |
| 40GB | 22 |
### Testing the GPU has enough memory to handle the Segment Size
Boundless should be restarted upon changing the `SEGMENT_SIZE` value, and verify that [Broker](/provers/broker#stopping-the-broker-service) is not running.
Once you have selected a `MAXIMUM` segment size you should verify that the GPU does in fact have enough memory to complete.
In the following test, an RTX 4060 with 16GB VRAM attempts to run with a `SEGMENT_SIZE` of 21, which is too large for the GPU to handle. In this test, it is necessary to monitor the `gpu-agent` Docker logs to determine the cause of the failure.
```bash Terminal theme={null}
RUST_LOG=info bento_cli -c 4096
```
```txt Terminal theme={null}
2024-10-17T15:58:15.205138Z INFO bento_cli: image_id: a0dfc25e54ebde808e4fd8c34b6549bbb91b4928edeea90ceb7d1d8e7e9096c7 | input_id: fe7f4251-25f4-436f-b782-f134d4c80538
2024-10-17T15:58:15.210646Z INFO bento_cli: STARK job_id: bbf442eb-40db-44fb-8df4-f13a8ce10bf2
2024-10-17T15:58:15.211686Z INFO bento_cli: STARK Job running....
....
```
We then examine the `gpu-agent` logs and see a series of out of memory errors:
```bash Terminal theme={null}
docker logs bento-gpu_prove_agent0-1
```
```log Terminal theme={null}
2024-10-17T15:57:43.667484Z INFO workflow::tasks::prove: Starting proof of idx: 6f95e238-d0be-4e94-9e81-fefdc0b7d8c4 - 1
thread 'main' panicked at /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/risc0-zkp-1.1.1/src/hal/cuda.rs:206:61:
called `Result::unwrap()` on an `Err` value: OutOfMemory
stack backtrace:
0: rust_begin_unwind
at ./rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/panicking.rs:652:5
1: core::panicking::panic_fmt
at ./rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/panicking.rs:72:14
2: core::result::unwrap_failed
at ./rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/result.rs:1654:5
```
Indicating that the GPU is out of memory. In this case, the `SEGMENT_SIZE` should be reduced.
On a multi-GPU host with mixed GPU models, the same `SEGMENT_SIZE` applies to every card, so set it to the lowest common denominator. To find that ceiling, benchmark the smallest-VRAM model in isolation, ideally on a single-GPU host of that model. This matches the canonical Boundless setup.
If a job fails to complete due to OOM, it may be resumed after Bento has been restarted. It's important to ensure that resumed jobs are not in progress during the test harness execution.
## How to Benchmark an Individual GPU's `SEGMENT_SIZE`
The `gpu_prove_agent` service auto-detects every GPU on the host and spawns one prove-agent process per GPU. For a clean per-GPU benchmark, use a host with only the GPU model you're testing; `nvidia-smi -L` reports exactly one card and the auto-detect entrypoint targets it directly.
```bash Terminal theme={null}
nvidia-smi -L
```
Should list exactly one GPU. If the host has multiple GPUs and you can't dedicate the box, run the benchmark on a separate single-GPU machine of the same model. This matches the canonical Boundless setup, which assumes the prover gets the whole host.
```bash Terminal theme={null}
RUST_LOG=info bento_cli -c 4096
```
```bash Terminal theme={null}
bash scripts/job_status.sh
```
```txt Example Results theme={null}
....
Effective Hz:
hz | total_cycles | elapsed_sec
---------------------+--------------+-------------
264892.074666431500 | 34603008 | 130.630590
(1 row)
```
Here we see that our single `gpu-agent` at max `SEGMENT_SIZE` is able to achieve an effective 264kHz.
## Multiple Agents and GPUs
We can incorporate multiple GPUs into a configuration. In this example, we have two 16GB GPUs as that proved to be optimal above.
No compose changes are required. `gpu_prove_agent` auto-detects every GPU on the host and spawns one prove-agent process per card. To use both 16GB GPUs, attach them to the prover host; the entrypoint picks them up automatically.
Here are the effective results on our example system:
```txt Terminal theme={null}
431375.207834042771 | 35127296 | 81.430957
```
In this case, we see that the effective Hz has increased to 431kHz, which is a significant improvement over the single GPU configuration; however we anticipated if the system was GPU limited we could expect 264Hz \* 2 = 528Hz.
This means that our example system is bound by some other factor such as bus bandwidth, memory, etc.
Some further suggestions:
* Reconfigure the system back to higher `PO2` with single agent per GPU and establish a new baseline performance level to compare against.
* For lower `SEGMENT_SIZE` configurations, experiment with `cpu_count` and `mem_limit` (removing, increasing, or decreasing) to see if the performance can be improved.
* In cases where bus contention is the limiting factor, running fewer agents at higher maximum `SEGMENT_SIZE` may be optimal. Systems in this configuration should avoid GPU expansion, and instead opt to expand into remote workers.
# Boundless Proving Stack
Source: https://docs.boundless.network/provers/proving-stack
A high-level explainer on the individual parts that make up the Boundless proving stack.
## Overview
At the core of the Boundless proving stack is *Bento*, which fundamentally builds upon the [RISC Zero zkVM](https://dev.risczero.com/api/zkvm/). The Boundless team shipped *Bento* after 2+ years of operating a highly parallelized and highly performant remote proving cluster, known as Bonsai (now deprecated - see [here](/developers/tutorials/bonsai#what-happened-to-bonsai)). Bento encapsulates this practical experience and learnings into a stack built for the Boundless market. Bento scales from single GPU machines to large clusters which makes it ideal for provers in the Boundless protocol.
## What is Bento?
For a deeper dive into Bento's technical design, components and configuration, please see [Bento Technical Design](/provers/bento).
Bento is a semi multi-tenant proving cluster for the [RISC Zero zkVM](https://dev.risczero.com/api/zkvm/). Concretely, Bento is a Docker compose stack which contains all the services needed to run a Bento cluster. It is highly configurable, and features:
* [multi GPU support](/provers/performance-optimization#multiple-agents-and-gpus).
* multi machine support.
* support for proofs of any size.
* safe cache/storage for all proof data.
* a robust retry system.
* an API for proof management.
The [Prover Quick Start](/provers/quick-start) has information about getting started with Bento, a recommended initial configuration and how to run basic sample proof workloads.
## What is the Broker?
For all provers, a collateral balance is necessary to be able to cover requirements for proof request lock-in. The Broker service provides a straightforward way to [deposit funds](/provers/broker#deposit-collateral-to-the-market) to the market.
The [Broker](/provers/broker) is responsible for market interactions (see steps **2b.** and **4a.** in *Figure 1*) including evaluating requests to assign a price, bidding on requests to lock them, issuing proving requests to the Bento proving cluster, and submitting proof fulfillments onchain.
Broker configuration is primarily managed through the [`broker.toml`](/provers/broker#settings-in-brokertoml) file in the Boundless directory.
To get started with the Broker, please see the [Broker page](/provers/broker).
## How are Bento and the Broker related?
Bento is responsible for:
* the API for managing proof requests.
* queuing and scheduling proof requests.
* executing guest programs.
* running proving using the RISC Zero zkVM.
* aggregating proofs into the right format for the Boundless Market.
Bento is **not** responsible for interaction with the Boundless market.
The recommended service for market interaction for provers is the [Broker](/provers/broker) service. The Broker is responsible for both interacting with the Boundless Market i.e., bidding on and fulfilling proof requests *and* sending job requests to the Bento proving cluster.
# Prover Quick Start
Source: https://docs.boundless.network/provers/quick-start
Run a prover on Boundless.
## Getting started with an AI agent
Paste this prompt into your AI coding agent (Claude Code, Cursor, Copilot, etc.) to get started:
```text theme={null}
Clone https://github.com/boundless-xyz/boundless, read the skill file at
.claude/skills/setup-prover/SKILL.md, and walk me through deploying a Boundless
prover to my GPU server.
```
The monorepo includes a [`setup-prover`](https://github.com/boundless-xyz/boundless/tree/main/.claude/skills/setup-prover) skill with step-by-step guidance for Ansible-based deployment, config tuning, and troubleshooting. See [AI Agents](/provers/ai-agents) for more.
***
## Requirements
A recommended minimum configuration for proving performance:
* CPU - 16 threads, reasonable single core boost performance (>3Ghz)
* RAM - 32 GB
* Disk - 200 GB of solid state storage, NVME / SSD preferred
* GPUs: at least one NVIDIA GPU with >= 8GB of VRAM.
* In testing, we've found the best performance on the following NVIDIA GPUs: 4090, and L4.
* While it's possible to use a single GPU, we recommend at least 10 GPUs to run a competitive prover.
* OS: Ubuntu 24.04 Full Virtual Machine or Bare Metal
The following tutorial will *not* work on standard dockerized cloud providers such as [Vast.ai](https://vast.ai) as the proving stack relies on Docker. We are in the process of working on some `supervisord` scripts and Docker images that would allow for a much cleaner setup in dockerized environments.
## Clone the Boundless Repo
We recommend using Ubuntu 24.04 for your proving node.
To get started, first clone the Boundless monorepo on your proving machine, and switch to the [latest release](https://github.com/boundless-xyz/boundless/releases):
```bash theme={null}
git clone https://github.com/boundless-xyz/boundless
cd boundless
git checkout release-2.0
```
## Install Dependencies
This stage can be skipped if you already have docker and docker-nvidia installed.
To run a Boundless prover, you'll need the following dependencies:
* [Docker compose](https://docs.docker.com/compose)
* [Docker Nvidia Support](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/index.html) (*Note: the install process requires enabling NVIDIA’s experimental packages*)
For a quick set up of Boundless dependencies on Ubuntu 24.04, please run:
```bash theme={null}
sudo ./scripts/setup.sh
```
### Setup Environment Variables
You'll need to set two environment variables:
```bash Terminal theme={null}
export PROVER_PRIVATE_KEY=""
export PROVER_RPC_URL=""
```
`PROVER_PRIVATE_KEY` is the wallet that represents your prover on the market; make sure it has funds.
For the RPC URL, recommendations differ by chain:
* **Base mainnet** (chain ID `8453`): [Alchemy](https://alchemy.com) or [Quicknode](https://www.quicknode.com/) paid plans.
* **Taiko Alethia** (chain ID `167000`): [dRPC](https://drpc.org/chainlist/taiko-mainnet-rpc) offers paid Taiko mainnet endpoints (Alchemy and Quicknode do not currently support Taiko). Taiko's public RPC at `https://rpc.mainnet.taiko.xyz` also works in practice.
To run a prover on multiple chains, use per-chain env vars instead of or alongside `PROVER_RPC_URL`:
```bash Terminal theme={null}
export PROVER_RPC_URL_8453="https://..." # Base mainnet
export PROVER_RPC_URL_167000="https://..." # Taiko Alethia
```
The broker only serves chains it discovers an RPC URL for; chains without one are silently skipped. After starting the stack, check `just prover logs` for a `Starting pipeline for chain` line per expected chain.
Free RPC tiers can be sufficient for low-throughput provers; `ChainMonitorV2` (the new default in v2.0) reduced per-block RPC load substantially. We still recommend paid plans for production to handle traffic spikes without hitting rate limits.
## Running a Test Proof
We make use of [just](https://github.com/casey/just?tab=readme-ov-file#just) to make running complex commands easier. To see available `just` commands for Boundless, run `just` within the root `boundless/` folder.
Boundless is comprised of two major components:
1. *Bento* is the local proving infrastructure. Bento will take requests, prove them and return the result.
2. The *Broker* interacts with the Boundless market. Broker can submit or request proves from the market.
To get started with a test proof on a new proving machine, you'll need to install the `bento_cli`:
```bash theme={null}
cargo install --locked --git https://github.com/boundless-xyz/boundless bento-client --branch release-2.0 --bin bento_cli
```
Once installed, you can run bento with:
```bash Terminal theme={null}
just bento
```
This will spin up bento without the broker. You can check the logs at any time with:
```bash Terminal theme={null}
just bento logs
```
To run the test proof:
```bash Terminal theme={null}
RUST_LOG=info bento_cli -c 32
```
If everything works, you should see something like the following:
## Running the Prover Setup Wizard
Once Bento is running successfully, it is time to configure the [broker](/provers/proving-stack#what-is-the-broker). The [Boundless CLI](/developers/tooling/cli) provides an interactive setup wizard (`boundless prover generate-config`) that derives optimised settings from your machine and writes them to [broker.toml](/provers/broker#settings-in-brokertoml) and the compose files.
The Boundless repo ships two compose files:
* [`prover-compose.yml`](https://github.com/boundless-xyz/boundless/blob/main/prover-compose.yml): the default prover stack (Redis-only, uses the prebuilt `prover-agent` image). `just bento` and `just prover` run this unless `PROVER_STACK=legacy` is set.
* [`compose.yml`](https://github.com/boundless-xyz/boundless/blob/main/compose.yml): the legacy/dev stack (Postgres + MinIO + Redis, builds the agent image locally).
The setup wizard writes the same derived settings (currently `exec_agent` replica count and `SEGMENT_SIZE`) to both files, so you only need to edit the one matching the stack you run.
### Install the Boundless CLI
First, install the Boundless CLI (the Boundless CLI is separate to the Bento CLI we installed earlier):
```bash theme={null}
cargo install --locked --git https://github.com/boundless-xyz/boundless boundless-cli --branch release-2.0 --bin boundless
```
Once installed, run `boundless` to check it has been installed properly:
### Run the Prover Setup Wizard
To run the prover setup wizard, run:
```bash theme={null}
boundless prover generate-config
```
and an interactive prompt will appear:
Answer the questions as required, based on your setup and requirements. Once finished, `broker.toml`, `prover-compose.yml`, and `compose.yml` will be created/edited with the same derived settings.
## Running the Broker
For technical support, please post your questions on the [Boundless Discussions Forum](https://github.com/boundless-xyz/boundless/discussions).
We have checked that bento successfully generated a test proof. We are now ready to run the broker so that we can start proving on Boundless.
### Deposit Collateral
*\$ZKC* is only on Ethereum mainnet.
Therefore, to use *$ZKC* as proving collateral, provers have to first [bridge *$ZKC*]\(/zkc/collateral) from Ethereum mainnet to Base mainnet.
With the [environment variables](/provers/quick-start#setup-environment-variables) set, you can now deposit *\$ZKC* tokens as collateral to your account balance:
```bash Terminal theme={null}
boundless prover deposit-collateral 50
```
### Start Prover
You can now start the prover (which runs both bento + broker i.e. the full proving stack!):
```bash Terminal theme={null}
just prover
```
To check the proving logs, you can use:
```bash Terminal theme={null}
just prover logs
```
### Stop Broker
To stop broker, you can run:
```bash Terminal theme={null}
just prover down
```
Or remove all volumes and data from the service:
```bash Terminal theme={null}
just prover clean
```
## Configuring Broker
### Custom Environment
Instead of passing environment variables for each shell session as we did above, you can set them in `.env.broker`. There is an [.env.broker-template](https://github.com/boundless-xyz/boundless/blob/main/.env.broker-template) available for you to get started:
```bash Terminal theme={null}
cp .env.broker-template .env.broker
```
After which, you can use a text editor to adjust the environment variables as required.
To run prover with a custom environment file:
```bash Terminal theme={null}
just prover up ./.env.broker
just prover down ./.env.broker
```
### Broker.toml
Broker can be configured using the [Broker.toml](https://github.com/boundless-xyz/boundless/blob/main/broker.toml) configuration file.
For example, to adjust the maximum number of proofs that can be processed at once, you can set:
```toml [boundless/Broker.toml] theme={null}
# Maximum number of concurrent proofs that can be processed at once
max_concurrent_proofs = 2 # change "2"
```
To see all Broker.toml configuration settings, please see [Broker Configuration & Operation/Settings in Broker.toml](/provers/broker#settings-in-brokertoml).
### Multi Host
Services can be run on other hosts, as long as the IP addresses for things link PostgreSQL / Redis / MinIO are updated on the remote host.
See the `.env.broker-template` HOST configuration options [here](https://github.com/boundless-xyz/boundless/blob/main/.env.broker-template) to adjust them.
## Configuring Bento
The compose file defines all services within Bento. By default, `just bento` and `just prover` use [`prover-compose.yml`](https://github.com/boundless-xyz/boundless/blob/main/prover-compose.yml); the legacy [`compose.yml`](https://github.com/boundless-xyz/boundless/blob/main/compose.yml) is used when `PROVER_STACK=legacy` is set. The examples below show `prover-compose.yml`; equivalent stanzas exist in `compose.yml`.
### Multi GPU
The `gpu_prove_agent` service auto-detects all GPUs visible to the container and spawns one prove agent per GPU; no manual per-GPU configuration is needed for the common case. To verify which GPUs Bento will use, run:
```bash Terminal theme={null}
nvidia-smi -L
```
which should output something like:
```bash theme={null}
GPU 0: NVIDIA GeForce RTX 3090 (UUID: GPU-abcde123-4567-8901-2345-abcdef678901)
GPU 1: NVIDIA GeForce RTX 3090 (UUID: GPU-fedcb987-6543-2109-8765-abcdef123456)
```
All listed GPUs are picked up automatically; no compose changes are required. To restrict Bento to a subset of GPUs on a host, configure visibility at the host level (e.g. dedicate the prover machine to Boundless, or use the NVIDIA driver or Docker daemon to control GPU visibility). The recommended Boundless setup is to give the prover a host with exactly the GPUs you want it to use.
### Segment Size
Larger `SEGMENT_SIZE` values also impact the proving systems conjectured security bits slightly (see [RISC Zero - Cryptographic Security Model](https://dev.risczero.com/api/security-model#how-secure-are-the-stark-provers)).
`SEGMENT_SIZE` is specified in powers-of-two (po2). Larger segment sizes are preferable for performance, but require more GPU VRAM. To pick the right `SEGMENT_SIZE` for your GPU VRAM, see [the performance optimization page](/provers/performance-optimization#finding-the-maximum-segment_size-for-gpu-vram).
#### Setting SEGMENT\_SIZE
The recommended way to change the segment size is to set the environment variable `SEGMENT_SIZE`, before running broker, to your specified value. This can be done through the [.env.broker](/provers/quick-start#custom-environment) file.
You can also configure `SEGMENT_SIZE` directly in `prover-compose.yml`; it lives in the `x-exec-agent-common` anchor that the `exec_agent` service inherits from, and defaults to 21:
```yml prover-compose.yml theme={null}
x-exec-agent-common: &exec-agent-common
<<: *agent-common
mem_limit: 4G
cpus: 3
environment:
<<: *base-environment
RISC0_KECCAK_PO2: ${RISC0_KECCAK_PO2:-17}
SEGMENT_SIZE: ${SEGMENT_SIZE:-21} # [!code hl]
REDIS_TTL: ${REDIS_TTL:-57600}
entrypoint:
[
"/bin/sh",
"-c",
"/app/agent -t exec --segment-po2 $$SEGMENT_SIZE --redis-ttl $$REDIS_TTL",
]
```
## What next?
For technical support, please post your questions on the [Boundless Discussions Forum](https://github.com/boundless-xyz/boundless/discussions).
Next, you'll need to tune your Broker's settings, please see [Broker Optimization](/provers/broker#broker-optimization).
If you'd like to learn more about the technical design of Bento, please see the [Bento Technical Design](/provers/bento).
To see your prover market statistics, check out the [provers](https://explorer.boundless.network/provers) page on the Boundless Explorer.
# $ZKC as Proving Collateral
Source: https://docs.boundless.network/zkc/collateral
Using $ZKC as proving collateral on the Boundless market.
* *\$ZKC* is deployed on Ethereum Mainnet and Base Mainnet.
* *\$ZKC* is also bridged to Taiko Alethia.
* To use *\$ZKC* in the Boundless market as [Proving Collateral](/provers/quick-start#deposit-collateral) on an L2, *\$ZKC* must be [bridged](/zkc/collateral#bridging-zkc) from Ethereum mainnet to that L2.
* Provers also need a small amount of native ETH on the destination L2 to pay for `depositCollateral` and ongoing market interactions.
Before accepting a proof request, provers must lock *\$ZKC* as collateral in the market, typically *at least* \~10x the request’s maximum fee. If the proof is not fulfilled on time, the prover is slashed; 50% of the collateral is burned permanently, and the remaining 50% is reassigned onchain as a bounty for another prover to complete the work. This system provides a stronger economic guarantee of proof delivery. At the same time, as the request volume grows, the total amount of *\$ZKC* locked increases by a multiple of \~10x, reducing the total circulating supply.
## Bridging \$ZKC
*\$ZKC* is deployed at:
* Ethereum Mainnet: `0x000006c2A22ff4A44ff1f5d0F2ed65F781F55555`
* (Bridged) Base: `0xAA61bB7777bD01B684347961918f1E07fBbCe7CF`
* (Bridged) Taiko Alethia: `0xC284A781072442cC1882a8Db4573990B7B49DaC4`
### Bridging using Base's Native Bridges
To bridge *\$ZKC* from Ethereum mainnet to Base mainnet, see the [Base documentation for bridging assets to Base](https://docs.base.org/base-chain/network-information/bridges-mainnet).
### Manual Bridging using `cast`
To start, make sure to have `cast` installed. This will require [installing Foundry](https://getfoundry.sh/introduction/installation/). Next, export the `PRIVATE_KEY` for the address holding the *\$ZKC* tokens:
```bash theme={null}
export PRIVATE_KEY=0x1234...5678
```
Set the `AMOUNT` and `ETH_MAINNET_RPC_URL` environment variables:
```bash theme={null}
export AMOUNT=10000000000000000000 # 10 ZKC
ETH_MAINNET_RPC_URL="https://"
```
with the amount to be bridged, and a valid RPC URL for Ethereum mainnet respectively.
Next, approve the Base bridge to spend \$AMOUNT tokens:
```bash theme={null}
cast send 0x000006c2A22ff4A44ff1f5d0F2ed65F781F55555 \
"approve(address,uint256)" \
0x3154Cf16ccdb4C6d922629664174b904d80F2C35 \
$AMOUNT \
--private-key $PRIVATE_KEY \
--rpc-url $ETH_MAINNET_RPC_URL
```
Finally, bridge \$AMOUNT tokens:
```bash theme={null}
cast send 0x3154Cf16ccdb4C6d922629664174b904d80F2C35 \
"bridgeERC20(address,address,uint256,uint32,bytes)" \
0x000006c2A22ff4A44ff1f5d0F2ed65F781F55555 \
0xAA61bB7777bD01B684347961918f1E07fBbCe7CF \
$AMOUNT \
300000 \
0x \
--private-key $PRIVATE_KEY \
--rpc-url $ETH_MAINNET_RPC_URL
```
### Bridging using Taiko's Native Bridge
Both *\$ZKC* and native ETH are bridged through the same flow. To bridge from Ethereum mainnet to Taiko Alethia, see [bridge.taiko.xyz](https://bridge.taiko.xyz) and [Taiko's contract documentation](https://docs.taiko.xyz/network/contract-addresses). The Taiko relayer mints the bridged tokens on L2 a few minutes after L1 confirmation.
### Manual Bridging to Taiko using `cast`
To start, make sure to have `cast` installed. This will require [installing Foundry](https://getfoundry.sh/introduction/installation/). Next, export the `PRIVATE_KEY` for the address holding the *\$ZKC* tokens:
```bash theme={null}
export PRIVATE_KEY=0x1234...5678
```
Set the `AMOUNT`, `RECIPIENT`, `FEE`, and `ETH_MAINNET_RPC_URL` environment variables:
```bash theme={null}
export AMOUNT=10000000000000000000 # 10 ZKC
export RECIPIENT=0xYour...Address # L2 recipient and refund address
export FEE=100000000000000 # 0.0001 ETH processing fee
ETH_MAINNET_RPC_URL="https://"
```
with the amount to be bridged, the L2 recipient address, the relayer's native ETH processing fee, and a valid RPC URL for Ethereum mainnet respectively.
Next, approve the Taiko `ERC20Vault` to spend \$AMOUNT tokens:
```bash theme={null}
cast send 0x000006c2A22ff4A44ff1f5d0F2ed65F781F55555 \
"approve(address,uint256)" \
0x996282cA11E5DEb6B5D122CC3B9A1FcAAD4415Ab \
$AMOUNT \
--private-key $PRIVATE_KEY \
--rpc-url $ETH_MAINNET_RPC_URL
```
Finally, bridge \$AMOUNT tokens via `ERC20Vault.sendToken`. The L2 `gasLimit` must be at least 1,000,000:
```bash theme={null}
cast send 0x996282cA11E5DEb6B5D122CC3B9A1FcAAD4415Ab \
"sendToken((uint64,address,address,uint64,address,uint32,uint256))" \
"(167000,$RECIPIENT,$RECIPIENT,$FEE,0x000006c2A22ff4A44ff1f5d0F2ed65F781F55555,1000000,$AMOUNT)" \
--value $FEE \
--private-key $PRIVATE_KEY \
--rpc-url $ETH_MAINNET_RPC_URL
```
# Claiming Rewards
Source: https://docs.boundless.network/zkc/mining/claiming-rewards
How to claim ZK Mining rewards.
## Mining Rewards
Claiming mining rewards requires *both* a valid archive node RPC endpoint and a Beacon Chain RPC endpoint.
In our testing, the Alchemy paid RPC plan in combination with the public beacon chain API URL (`https://ethereum-beacon-api.publicnode.com/`) works for claiming rewards. Quicknode works for the beacon chain endpoint, but Quicknode RPC endpoints are not recommended for `claim-mining-rewards` as it commonly errors with request rate limits.
After the reward epoch has finalized, mining rewards can be claimed with:
```bash theme={null}
boundless rewards claim-mining-rewards
```
This process is permissionless and DOES NOT require the private key for the address eligible for the rewards. The only requirement is that the stored `PRIVATE_KEY` has enough funds to cover gas costs on Ethereum mainnet. `claim-mining-rewards` uses Bento for proving (see [claim.rs](https://github.com/boundless-xyz/boundless/blob/040645c9f91bc4804ba63c9b4744a5a21df0c90c/crates/boundless-cli/src/commands/povw/claim.rs#L244-L267)); therefore, make sure to have Bento running locally or specify a valid Bento API URL endpoint via `--bento-api-url`.
## Staking Rewards
After the reward epoch has finalized, staking rewards can be claimed with:
```bash theme={null}
boundless rewards claim-staking-rewards
```
where `PRIVATE_KEY` is the private key of the address that has the [reward power](/zkc/mining/wallet-setup#stakes-zkc-and-receives-reward-power). This would be the private key for the rewards address when using the [recommended wallet setup](/zkc/mining/wallet-setup).
## Checking Details of Current Reward Epoch
If the reward epoch has not been finalized yet, provers can check details of the the current epoch:
```bash theme={null}
boundless rewards epoch
```
# Enabling ZK Mining
Source: https://docs.boundless.network/zkc/mining/enable
Best practices for wallets used in Staking, Proving and ZK Mining.
Provers will require NVIDIA GPUs and Ubuntu 24.04. For a full list of requirements, please see [Requirements](/provers/quick-start#requirements).
To enable PoVW in Bento, first make sure to stop Bento safely. This ensures that any in-progress proving jobs are completed before terminating the proving process. If Bento is backing the Broker, please carry out *Step 1* from [Safe Upgrade Steps](/provers/broker#safe-upgrade-steps) and then return to this page.
Bento and/or broker can be safely stopped with:
```bash theme={null}
just broker down
```
To install the latest release, and run through the setup wizard, provers can follow the instructions on the [Quick Start](/provers/quick-start) before continuing with the next step.
Follow the instructions [here](https://www.rust-lang.org/tools/install).
Provers use the Boundless CLI to stake *\$ZKC*, prepare work log updates, and submit them onchain. To install the Boundless CLI (and build with CUDA support), see the [installation instructions](/developers/tooling/cli#installation).
Some commands in this tutorial use [cast](https://getfoundry.sh/cast/overview) from the [Foundry toolkit](https://getfoundry.sh/). This is not necessary, but it will make some CLI commands more straightforward.
Follow the instructions [here](https://dev.risczero.com/api/zkvm/install#installation-for-x86-64-linux-and-arm64-macos).
Run the following:
```bash theme={null}
rzup install risc0-groth16
```
Provers can use their *\$ZKC* staking address as their Reward Address but this is not recommended. In practice, provers should use a separate wallet to stake *\$ZKC*. For more information, see [Wallet Setup](/zkc/mining/wallet-setup).
Before starting Bento again, provers need to enable the ZK mining feature. This requires a valid Ethereum address set to the `REWARD_ADDRESS` environment variable. It is recommended to set this directly in the compose file you run (`prover-compose.yml` by default, `compose.yml` for the legacy stack), under the `x-base-environment` block:
```yml prover-compose.yml theme={null}
x-base-environment: &base-environment
BENTO_API_URL: http://rest_api:8081
REDIS_URL: redis://${REDIS_HOST:-redis}:6379
RUST_LOG: ${RUST_LOG:-info}
RISC0_HOME: /usr/local/risc0
RUST_BACKTRACE: 1
REWARD_ADDRESS: "0x1234...5678" // [!code hl] [!code focus]
```
Follow the instructions [How to Delegate Rewards](/zkc/mining/wallet-setup#how-to-delegate-rewards) to delegate reward power from the staking wallet to the reward address. For more context, please see the [Wallet Setup](/zkc/mining/wallet-setup) tutorial
Once enabled, Bento can be run as normal with:
```bash theme={null}
just bento
```
Or if running the broker as well:
```bash theme={null}
just broker
```
will start both the broker and Bento.
Once Bento is running, provers can run proving jobs as normal. Bento will now create a work proof for every proving job; a work proof cryptographically proves how much work Bento has done (in [cycles](https://dev.risczero.com/terminology#clock-cycles)).
Using the Boundless CLI, provers aggregate these work proofs and submit the aggregated work proof *at least once* per [reward epoch](/zkc/quick-start#how-do-zkc-rewards-work) to be eligible for that epoch's mining rewards. This process is documented in [Mining + Claiming Rewards](/zkc/mining/claiming-rewards).
# Overview
Source: https://docs.boundless.network/zkc/mining/overview
Getting started with ZK Mining.
This page covers the high level concepts behind ZK mining.
If you're looking for technical tutorials on setting up ZK mining and claiming rewards, please see the following:
* [Enabling ZK Mining](/zkc/mining/enable)
* [Mining Walkthrough](/zkc/mining/walkthrough)
* [Claiming Rewards](/zkc/mining/claiming-rewards)
## What is ZK Mining?
Proof of Verifiable Work (PoVW), a novel invention within Boundless, is a permissionless incentive mechanism which enables provers to be rewarded in *\$ZKC* in exchange for their proving work on the Boundless marketplace or elsewhere. Provers generating zero-knowledge proofs are rewarded with *\$ZKC*; this process is known as *ZK Mining*.
### ZK Mining Lifecycle
Generating a ZK proof, with the "Reward Address" specified in Bento, will generate an associated "work proof". This work proof proves that the prover generated a valid ZK proof and tallies the amount of "work" done (measured in [cycles](https://dev.risczero.com/terminology#clock-cycles)).
The `prepare-mining` CLI command (`boundless rewards prepare-mining...`) compresses all local work proofs (stored in Bento) to reflect work done during any given epoch. This aggregated proof is stored in a local “state” file.
Towards the end of an epoch, the prover must verify their work done using the `submit-mining` CLI command (`boundless rewards submit-mining ...`).
Provers submit the aggregated work proof to the PoVW smart contract to prove their total work done during the current reward epoch. `submit-mining` uses the rewards address's private key for signing and posting the work proof onchain.
The prover waits for the current reward epoch to finalize. This allows all work log updates to be tallied, and all the respective rewards for each rewards address to be calculated.
To check details of the current epoch, please see [Checking Details of Current Reward Epoch](/zkc/mining/claiming-rewards#checking-details-of-current-reward-epoch).
Once the epoch has been finalized, the `claim-mining-rewards` CLI command can be used to claim any unclaimed mining rewards; claiming rewards does not have to be done every epoch; all unclaimed rewards from past epochs will be claimed automatically on the next valid `claim-mining-rewards` call.
With the start of the new epoch, all provers start with a work total of 0. Provers continue to generate ZK proofs, and submit an aggregated work proof *at least once* every epoch to be eligible for rewards (based on the total work done by all provers).
## Who can ZK mine?
* Provers must satisfy the [minimum hardware requirements](/provers/quick-start#requirements) to be able to run [Bento](/provers/proving-stack#what-is-bento).
* Provers must [stake \$ZKC](/zkc/quick-start#how-to-stake-zkc) to be eligible for PoVW rewards.
* Before staking, any proving work submitted *is not* eligible for rewards.
* After staking, any work submitted is eligible for rewards in the current reward epoch.
* The maximum mining rewards each prover can earn for each reward epoch is directly correlated with their total staked *\$ZKC*. It is currently `staked_amount/15` (please see [Mining Rewards](/zkc/quick-start#mining-rewards)).
* Provers must enable ZK mining in Bento by specifying the `REWARD_ADDRESS` environment variable.
* To learn how to enable ZK mining, see [Enabling ZK mining](/zkc/mining/enable).
# Mining Walkthrough
Source: https://docs.boundless.network/zkc/mining/walkthrough
Step-by-step guide walking through the entire ZK mining process.
## Step-by-step
Please see [Enabling ZK Mining](/zkc/mining/enable).
Provers are now ready to run proving workloads with ZK mining enabled; this can be done by [running a prover](/provers/quick-start) on the Boundless marketplace or elsewhere.
For each proving job, R0VM will automatically generate work proofs. These work proofs track the total amount of proving work done (in cycles). *Each epoch*, provers will need to submit their accumulated work to the PoVW accounting smart contract. If provers DO NOT submit work each epoch, they will not receive any rewards for that epoch.
To do this, provers must use the [Boundless CLI](/zkc/mining/enable#make-sure-to-have-the-boundless-cli-installed) and run the following three commands, `prepare-mining`, `submit-mining` and `claim-mining-rewards`.
Before running `prepare-mining`, provers must first create a state file using the interactive setup wizard:
```bash theme={null}
boundless rewards setup
```
The setup wizard will configure the rewards module (RPC URL, staking address, reward address) and create a new state file at a path you specify. It also stores the state file location in your config so that subsequent commands can find it automatically.
The resultant state file keeps track of all work done so far for one reward address. Each reward address should have *exactly* one state file associated with it. Submitting work for a rewards address requires all previously submitted receipts to be included in the state file. *The same state file must be used for submitting any additional work done by the associated rewards address.* If two state files have the same rewards address, they will conflict during `submit-mining` because they will have different Merkle tree states.
Please note that this state file is *very important* and it should be kept in a durable location.
Loss of this state file *will result* in the loss of all work that is not submitted for the respective rewards address, meaning rewards for any work done since the last `submit-mining` **will be lost**.
In this scenario, a new rewards address must be specified for Bento, and Bento should be restarted to allow further work to be recorded properly against the new rewards address. *To avoid this scenario, it is recommended to store the state file in a durable location.*
Once the state file is created via `setup`, provers can aggregate work proofs by running:
```bash theme={null}
boundless rewards prepare-mining
```
`prepare-mining` aggregates all work proofs from Bento and stores the combined proof to the state file. This uses Bento to generate the aggregated work proof; therefore, make sure to have Bento running locally or specify a valid Bento API URL endpoint via `--work-receipt-bento-api-url`. This process is entirely local (i.e. it does not send any transaction).
You can also pass `--state-file ${STATE_FILE_LOCATION}` explicitly if needed. If the state file location changes, make sure to update it by running `boundless rewards setup` again.
After running `prepare-mining`, provers are ready to submit onchain with:
```bash theme={null}
boundless rewards submit-mining
```
`submit-mining` also requires Bento for proving (see [submit.rs](https://github.com/boundless-xyz/boundless/blob/040645c9f91bc4804ba63c9b4744a5a21df0c90c/crates/boundless-cli/src/commands/povw/submit.rs#L170-L176)). Therefore, make sure to have Bento running locally or specify a valid Bento API URL endpoint via `--bento-api-url`.
# Wallet Setup
Source: https://docs.boundless.network/zkc/mining/wallet-setup
Best practices for wallets used in Staking, Proving and ZK Mining.
This page covers some best practices when it comes to wallets used for [staking *\$ZKC*](/zkc/quick-start#how-to-stake-zkc), [ZK mining](/zkc/mining/overview) and [running a Boundless prover](/provers/quick-start).
Please note that not every prover will need to separate every wallet from each other. Every additional wallet comes with the overhead of keeping another private key safe for prover operation. This guide assumes provers have a large active *\$ZKC* stake position which they want to keep separate from daily ZK mining and Boundless prover operations.
## Overview
The following diagram illustrates the recommended mining reward workflow:
The prover stakes *\$ZKC*, this gives [reward power](https://github.com/boundless-xyz/zkc?tab=readme-ov-file#power-calculations) to the staking address. This reward power is used, each reward epoch, to calculate the total amount of staking and mining rewards for the address with the reward power. This wallet has the most active *\$ZKC* staked, and so it is recommended that this wallet be a *cold* wallet. The address that receives all rewards every two days can be a separate *hot* wallet, and this is done via delegation.
The staking address can delegate the reward power to another address known as the *delegatee*. We recommend delegating reward power, as it allows for a hot wallet to receive rewards *and* to restake the rewards to increase the total reward power for compounding gains. Restaking directly with a cold wallet would be quite troublesome, as it would require restaking every epoch (\~2 days).
Separating the staking wallet from the reward address also means that a maximum of a single reward epoch's rewards are lost if the reward address private key is lost or compromised. This is because the staking address can simply re-delegate rewards to a new address and [enable ZK mining](/zkc/mining/enable) with that address specified. Otherwise, without separation between the staking address and the rewards address, the prover would lose *both* the total stake position and the total reward power.
The prover can now [run a Boundless prover](/provers/quick-start), or run proving jobs elsewhere, and [Bento](/provers/proving-stack#what-is-bento) will automatically store work proofs. For a full explanation of this process, please see the [ZK Mining Walkthrough](/zkc/mining/walkthrough).
Before the end of the reward epoch, the prover MUST submit *at least* one aggregated work proof (please see [here](/zkc/mining/walkthrough#submit-the-aggregated-work-proof-onchain) for technical details).
The rewards address can now [claim the staking rewards](/zkc/mining/claiming-rewards#staking-rewards) and [claim the mining rewards](/zkc/mining/claiming-rewards#mining-rewards).
After claiming staking + mining rewards, the *\$ZKC* rewards contract will carry out the necessary checks, and if successful, mint the rewards to the rewards address. For compounding gains, the prover can now transfer these *\$ZKC* rewards, from the rewards address to the staking address, and stake them to increase the total reward power for the rewards address.
## Recommendations
To [enable ZK mining](/zkc/mining/enable), the prover specifies the `REWARD_ADDRESS` environment variable which must be a valid Ethereum address. This address *does not* have to be the same as the *\$ZKC* staking wallet address or a Boundless prover address, though a single address can be used for all three purposes. It is NOT recommended to use a single address for all three purposes; if the private key is compromised or lost, this will lead to a loss of staked *\$ZKC*, loss of *\$ZKC* rewards, and the loss of boundless prover *\$ZKC* [collateral](/zkc/collateral) balance.
### Separate Staking Wallet and Reward Wallet
Please note that delegating rewards to any address will delegate all rewards to that address i.e. both staking and mining rewards will only be claimable by that delegatee address. For more information, please see the [token docs](https://github.com/boundless-xyz/zkc?tab=readme-ov-file#zkc).
It is recommended to separate the staking wallet from the rewards address. The rewards address will receive all *\$ZKC* staking and mining rewards and therefore, it can be seen as a *hot* wallet. Depending on the size of the prover's stake position, it may be wise to have the wallet that stakes *\$ZKC* configured as cold wallet. For this reason, it is possible to delegate rewards *from* the staking wallet *to* the rewards address wallet.
#### How to delegate rewards
Please see [Enabling ZK Mining](/zkc/mining/enable).
To stake from a separate wallet, provers may additionally specify:
```bash theme={null}
export ZKC_WALLET_ADDRESS="0x0000...0000"
export PRIVATE_KEY="..."
```
Provers can now follow the [staking instructions](/zkc/quick-start#how-to-stake-zkc) to stake \$ZKC from the `ZKC_WALLET_ADDRESS`.
Once staked, provers can delegate rewards i.e. associate the `REWARD_ADDRESS` with `ZKC_WALLET_ADDRESS`. This is done with the following command:
```bash theme={null}
boundless rewards delegate ${REWARD_ADDRESS}
```
This command uses the `PRIVATE_KEY` environment variable to send a transaction onchain, therefore funds are required on `ZKC_WALLET_ADDRESS`.
Once the rewards are delegated, the prover can start proving as normal.
### Separating Boundless Prover from Staking + Reward Wallets
Since many Boundless provers run clusters on hosted cloud providers, and because the private key must be in plaintext when starting the broker, the security of this wallet depends on your trust in the cloud provider. Therefore, for provers running large proving/ZK-mining clusters with large expected *\$ZKC* reward amounts, it is not recommended to use your Boundless proving address as the staking wallet or the rewards address. If the key is compromised, this can lead to a loss of stake position and reward power and this will require a new wallet setup to resume proving.
We recommend that provers separate their Boundless prover address from their staking wallets and reward address wallets, such that there are three distinct addresses:
This setup allows the proving cluster to prove orders on the Boundless market and generate and [submit aggregated work proofs onchain](/zkc/mining/walkthrough#submit-the-aggregated-work-proof-onchain) to be eligible for rewards. It also means that after running `claim-mining-rewards`, the reward \$ZKC is transferred to the reward address. Claiming rewards is a permissionless process and does not require access to the private key for the rewards address.
### Separating Staking Wallet and Value Recipient
The recipient specified below will ONLY receive the mining rewards, NOT the staking rewards. There is currently no feature available to specify a recipient for the staking rewards; this is being considered on our roadmap. If you would like to see this feature, please feel free to post in the [Boundless Discussions Forum](https://github.com/boundless-xyz/boundless/discussions).
If provers would like all *mining* rewards to be sent to a safer address (i.e. another cold wallet like the *staking wallet*), they can specify another address when running `submit-mining`:
```bash theme={null}
boundless rewards submit-mining --recipient
```
This is a separate Ethereum address which will receive only the mining *\$ZKC* rewards for that *specific* submit call. If the submit does NOT specify a recipient, then the command will default to the stored rewards address from the rewards module config (run `boundless rewards config` to see the stored value).
# Quick Start
Source: https://docs.boundless.network/zkc/quick-start
Getting started with *\$ZKC*.
*\$ZKC* is the native token of Boundless and the gateway to participation in the protocol.
## It all starts with *Staking*
By staking *\$ZKC*, token holders signal their commitment to the long term success of the Boundless protocol. The protocol rewards stakers with staking rewards, governance voting power, and *most importantly* eligibility for mining rewards.
### Staking *\$ZKC* to earn Mining Rewards
Proof-of-Verifiable-Work (PoVW), a novel invention within Boundless, is a permissionless incentive mechanism which enables provers to be rewarded in *\$ZKC* in exchange for their proving work on the Boundless marketplace or elsewhere. This process is known as *ZK Mining*; mining rewards are ONLY available to those who stake *\$ZKC*.
To learn about ZK Mining, see the [ZK Mining Overview](/zkc/mining/overview).
## How to stake *\$ZKC*?
* *\$ZKC* is staked on Ethereum Mainnet ONLY.
* The only official staking portal is [https://staking.boundless.network](https://staking.boundless.network).
* The official contract address is [0x000006c2A22ff4A44ff1f5d0F2ed65F781F55555](https://etherscan.io/address/0x000006c2A22ff4A44ff1f5d0F2ed65F781F55555).
### Via the Browser (recommended)
Staking is available via the [Boundless Staking Portal](https://staking.boundless.network). After connecting a wallet, specify an amount to stake and press "Stake".
To claim available staking rewards, click "Claim" in the Rewards section of the staking portal. Rewards are unlocked at the start of the next epoch.
### Via the CLI
The [Boundless CLI](/developers/tooling/cli#installation) has a suite of commands that deal with staking in the Boundless CLI's [rewards module](/developers/tooling/cli#rewards). Before continuing, it is recommended to read through the [Boundless CLI](/developers/tooling/cli#installation)) documentation to go over installation and module setup to store all relevant secrets.
To stake `$AMOUNT` *\$ZKC*, run:
```bash theme={null}
boundless rewards stake-zkc $AMOUNT
```
To claim staking rewards, run:
```bash theme={null}
boundless rewards claim-staking-rewards
```
## How do *\$ZKC* rewards work?
### Overview
*\$ZKC* rewards are earned by token holders who have staked *\$ZKC*. Rewards are distributed every reward epoch. Each epoch lasts \~2 days, resulting in \~182.5 epochs per year.
There are two types of *\$ZKC* rewards:
* Staking Rewards
* 25% of each epoch's total *\$ZKC* rewards are distributed as *Staking Rewards* to each active staker.
* Mining Rewards
* 75% of each epoch's total *\$ZKC* rewards are distributed as *Mining Rewards* to each active staker that ALSO submits valid work during the epoch.
Each reward epoch has a set amount of rewards to distribute. This can be easily calculated:
* The initial supply of *\$ZKC* is 1 billion.
* The first year’s emission rate is 7% (see [Emission Rate](https://github.com/boundless-xyz/zkc?tab=readme-ov-file#zkc-1)).
* Therefore, up to 70 million *\$ZKC* will be emitted as rewards in the first year.
* Annual *\$ZKC* reward emission is split evenly across all the reward epochs (each epoch is \~2 days). Therefore, each epoch has a set reward emission.
* Roughly: \~182 epochs a year => 70M *\$ZKC*/182 => \~385k reward *\$ZKC* available every epoch in the first year.
### Staking Rewards
The easiest way to understand staking rewards is to walk through a concrete example and a relevant reward calculation:
* 25% of an epoch's total reward *\$ZKC* is distributed to stakers as *staking rewards*.
* This 25% is split proportionately to each staker based on *their percentage of the total staked \$ZKC* in the given epoch.
* For example, if an epoch has a total of 1M staked *\$ZKC*, a staker with 5000 *\$ZKC* staked represents 0.5% of the total stake. Therefore, this staker will receive 0.5% of all staking rewards.
* Specifically, the staker will receive 0.5% of 25% of the epoch's reward *\$ZKC*; this is equal to 0.125% of the total reward \$ZKC available for the epoch.
* Therefore, the staker will receive 0.125% of \~385k *\$ZKC*; this is approximately \~480 *\$ZKC*.
### Mining Rewards
If you're interested in earning mining rewards, after reading the section below, please head over to [ZK Mining Quick Start](/zkc/mining/overview).
For the purposes of this section, "miner" will be used to refer to somebody who stakes *\$ZKC* and submits valid work regularly. This term could be used interchangeably with prover, which is used commonly throughout the Boundless docs.
The easiest way to understand mining rewards is to walk through a concrete example and a relevant reward calculation:
* 75% of an epoch's total reward *\$ZKC* is distributed to eligible miners as *Mining Rewards*.
* To be eligible for mining rewards, a miner must *both* stake \$ZKC during a given epoch *AND* submit valid work before the end of the epoch.
* This 75% is split proportionately to each miner based on *their percentage of the total work submitted* during the given epoch.
* Each miner has a *mining reward cap* every epoch. This is equal to:
```bash theme={null}
mining_reward_cap = staked_amount / 15
```
* Therefore, if a miner stakes 5000 *\$ZKC*, they will have a mining reward cap of `5000 / 15 = 333.33 $ZKC` each epoch.
* Miners carry out work using [Bento](/provers/proving-stack#what-is-bento). Each proving job in Bento will generate a work proof. These cryptographically verify the amount of work done for each job.
* These work proofs are submitted onchain; each miner MUST submit their work proofs onchain at least once every epoch IF they want to be eligible for that epoch's rewards. Miners are free to submit previous work done during any later epochs; this will just delay any reward eligibility, for the work done, to the submission epoch.
* If there are 1 trillion cycles of work submitted for the current epoch, and a miner submits 1 billion cycles worth of work proofs, their work represent 0.1% of the total work in the epoch.
* Therefore, the miner will receive 0.1% of 75% of the total reward *\$ZKC* for the epoch.
* Specifically, the miner will receive `0.075% of ~385k` *\$ZKC* which equals \~289 `$ZKC`.
* The miner has a reward cap of \~333 *\$ZKC*. Since the miner has already submitted enough work for \~289 *\$ZKC*, they have \~45 *\$ZKC* worth of reward cap left. If they submit enough work before the end of the epoch to cover the \~45 *\$ZKC* remaining, they will receive the full amount their stake position entitles them to.
## How does delegating rewards work?
Multiple stake positions, across different wallets, can delegate their rewards to the same delegatee address.
A crucial part of *\$ZKC* is the ability to delegate rewards, which delegates BOTH staking and mining rewards, to another address (known as the *delegatee*). In practice, this means that *only* the delegatee is able to claim staking and mining rewards on behalf of the total of all active stake positions delegated to that address.
For mining rewards, delegation is a recommended practice as it allows for separation between the wallet that stakes *\$ZKC* and the wallet specified in Bento in work proofs as the `REWARD_ADDRESS`. For more information, please see [ZK Mining Wallet Setup](/zkc/mining/wallet-setup).
To delegate rewards, the recommended way is to use the Boundless CLI, specifically:
```bash theme={null}
boundless rewards delegate
```
For mining rewards, it is recommended to set `` as the address set to `REWARD_ADDRESS` in Bento. The private key should denote the address that you want to delegate *from* i.e. the wallet with an active stake position.
## How does governance work?
The Boundless governance contracts have been deployed by Aragon: [Boundless DAO](https://app.aragon.org/dao/ethereum-mainnet/boundless.dao.eth).
### Voting Power
To be eligible to vote in the Boundless DAO, token holders must stake *\$ZKC*; voting power is relative to the percentage of total staked *\$ZKC* i.e. an address that stakes 1% of the total staked *\$ZKC* will possess 1% of the total voting power.
### What does Governance control?
With the launch of *\$ZKC*, the DAO is going through a set of test proposals to ensure that each governance process is fully functional. Once these are successful, the DAO will be given full ownership of the protocol.
Initially, governance will control five main processes:
* *Market upgrades*: Upgrades to the logic of the Boundless market and adjacent onchain infrastructure such as verifier contracts.
* *Token upgrades*: Upgrades to core token variables such as emission schedules and permissions to distribute mining rewards.
* *Governance upgrades*: Upgrades to the governance processes themselves.
* *Grants*: Proposals for \$ZKC token grants to community members, ecosystem partners and any other parties deemed to be contributing to the success of Boundless.
* *Emergency upgrades*: Upgrades to address time sensitive issues that impact the safety of funds or the safety of protocols using Boundless.
You can find more details on how the DAO is structured at Aragon’s portal for [Boundless Governance](https://app.aragon.org/dao/ethereum-mainnet/boundless.dao.eth).