Off-chain computation worker for confidential operations in the Nox Protocol.
The Runner is the off-chain computation layer of the Nox Protocol. It subscribes to a NATS JetStream fed by nox-ingestor, executes confidential operations over encrypted values, and publishes results back to nox-handle-gateway. Plaintext values are never persisted and exist only transiently in memory during a computation.
Receiving a computation request: The runner consumes TransactionMessage objects from a NATS JetStream pull consumer. Each message represents a single on-chain transaction and contains one or more ordered TransactionEvent objects, each carrying a typed Operator variant. On startup, the runner reads the KMS public key and the Handle Gateway signer address from the NoxCompute contract on-chain to bootstrap its crypto context.
Executing computations: For each event in the transaction, the runner fetches the encrypted operand handles from the Handle Gateway via GET /v0/compute/operands, decrypts them locally with ECIES (secp256k1 ECDH + HKDF-SHA256 + AES-256-GCM), performs the computation on plaintext Solidity-typed values, and re-encrypts each result under the KMS public key.
Operand caching: Within a single transaction, decrypted values are kept in an in-memory cache. If a result produced by one event is needed as an operand by a subsequent event in the same transaction, it is served from cache without an additional gateway round-trip. The cache is cleared after each transaction.
Publishing results: Once all events in a transaction are processed, all result handles are submitted to the Handle Gateway in a single POST /v0/compute/results call. This preserves transaction atomicity: results are either all published or none are.
Supported types: The runner operates on Solidity-compatible types encoded as 32-byte big-endian values — bool, uint16, uint256, int16, and int256.
- Rust >= 1.85 (edition 2024)
- Access to an Ethereum RPC endpoint with a deployed NoxCompute contract
- A running nox-handle-gateway instance
- A running NATS server (or cluster) with JetStream enabled and a stream populated by nox-ingestor. mTLS client credentials are required unless TLS is disabled (see Environment Variables)
git clone https://github.com/iExec-Nox/nox-runner.git
cd nox-runner
# Set required environment variables
export NOX_RUNNER_WALLET_KEY="0x..."
export NOX_RUNNER_CHAINS__<CHAIN_ID>__RPC_URL="https://..."
export NOX_RUNNER_CHAINS__<CHAIN_ID>__NOX_COMPUTE_CONTRACT_ADDRESS="0x..."
export NOX_RUNNER_HANDLE_GATEWAY_URL="https://..."
export NOX_RUNNER_NATS__URLS="nats://...,nats://..."
# NATS mTLS is enabled by default. Either provide the client certificate
# material as PEM content...
export NOX_RUNNER_NATS__TLS__CA="$(cat ca.pem)"
export NOX_RUNNER_NATS__TLS__CERT="$(cat client-cert.pem)"
export NOX_RUNNER_NATS__TLS__KEY="$(cat client-key.pem)"
# ...or disable TLS to connect to a plain NATS server (dev only):
# export NOX_RUNNER_NATS__TLS__ENABLED="false"
# Build and run
cargo run --releaseImportant
<CHAIN_ID> represents the chain ID (421614 for Arbitrum Sepolia) of the target blockchain network
where the NoxCompute smart contract has been deployed. The Runner will be able to compute events
from transactions emitted from this NoxCompute smart contract deployment.
Configuration is loaded from environment variables with the NOX_RUNNER_ prefix. Nested properties use double underscore (__) as separator.
| Variable | Description | Required | Default |
|---|---|---|---|
NOX_RUNNER_SERVER__HOST |
Bind address for the HTTP server | No | 127.0.0.1 |
NOX_RUNNER_SERVER__PORT |
Port for the HTTP server | No | 8080 |
NOX_RUNNER_CHAINS__<CHAIN_ID>__CALL_TIMEOUT |
Total request timeout applied to an RPC call | No | 8s |
NOX_RUNNER_CHAINS__<CHAIN_ID>__CONNECT_TIMEOUT |
Timeout for the connect phase against the Ethereum RPC endpoint | No | 5s |
NOX_RUNNER_CHAINS__<CHAIN_ID>__NOX_COMPUTE_CONTRACT_ADDRESS |
NoxCompute contract address |
No | 0x0000...0000 |
NOX_RUNNER_CHAINS__<CHAIN_ID>__RPC_URL |
Ethereum RPC endpoint URL | No | http://localhost:8545 |
NOX_RUNNER_NATS__URLS |
NATS server URLs (comma-separated for a cluster) | Yes | — |
NOX_RUNNER_NATS__TLS__ENABLED |
Enable mTLS to NATS. Set false for a plain dev server |
No | true |
NOX_RUNNER_NATS__TLS__CA |
CA certificate PEM content. Required when TLS enabled | If TLS | — |
NOX_RUNNER_NATS__TLS__CERT |
Client certificate PEM content. Required when TLS enabled | If TLS | — |
NOX_RUNNER_NATS__TLS__KEY |
Client private key PEM content. Required when TLS enabled | If TLS | — |
NOX_RUNNER_NATS__STREAM_NAME |
Name of the JetStream stream to consume | No | nox_ingestor |
NOX_RUNNER_NATS__CONSUMER_NAME |
Durable consumer name | No | nox_ingestor_consumer |
NOX_RUNNER_NATS__CONSUMER_MAX_DELIVER |
Maximum redelivery attempts per message | No | 10 |
NOX_RUNNER_NATS__MAX_ACK_PENDING |
Buffer size of unacknowledged messages | No | 10 |
NOX_RUNNER_NATS__MAX_BATCH |
Maximum number of messages the runner can pull from the stream | No | 10 |
NOX_RUNNER_HANDLE_GATEWAY__URL |
Handle Gateway base URL | No | http://localhost:3000 |
NOX_RUNNER_HANDLE_GATEWAY__CONNECT_TIMEOUT |
Timeout for the connect phase against the Handle Gateway | No | 3s |
NOX_RUNNER_HANDLE_GATEWAY__TIMEOUT |
Total request timeout against the Handle Gateway | No | 15s |
NOX_RUNNER_WALLET_KEY |
Private key used to sign Handle Gateway requests (hex, with or without 0x prefix) |
Yes | — |
Logging level is controlled via the RUST_LOG environment variable:
RUST_LOG=info # Default
RUST_LOG=debug # Verbose loggingEvery endpoint below is subject to a fixed 5s request timeout. A request that exceeds it is
abandoned and answered 408 Request Timeout with {"error": "Request timed out"}.
Returns basic service information.
Response:
{
"service": "Runner",
"timestamp": "2026-02-25T10:30:00.000Z"
}Health check endpoint for monitoring and orchestration.
Response:
{
"status": "ok"
}Prometheus metrics endpoint for observability.
Response: Prometheus text format metrics.
The following Nox Runner metrics are available:
| Metric | Description |
|---|---|
nox_runner.transaction.received |
Counter to count each required transaction computation. |
nox_runner.transaction.block_number |
Blockchain block number of the last transaction to compute. |
nox_runner.operation |
Counter to count each operation. An operator label allows to distinguish all operators. |
nox_runner.transaction.result |
Counter to observe computation results following 3 statuses (SUCCESS, FAILURE, NOT_ACK). |
The runner consumes messages from a NATS JetStream pull consumer. It does not produce any NATS messages. Messages are published to the stream by nox-ingestor.
Each message payload is a JSON-encoded TransactionMessage:
{
"chainId": 421614,
"blockNumber": 12345678,
"caller": "0x...",
"transactionHash": "0x...",
"events": [
{
"logIndex": 0,
"caller": "0x...",
"type": "add",
"leftHandOperand": "0x...",
"rightHandOperand": "0x...",
"result": "0x..."
}
]
}| Field | Description |
|---|---|
chainId |
Chain ID where the on-chain events were emitted |
blockNumber |
Block number of the transaction |
caller |
Address of the account that sent the transaction |
transactionHash |
Hash of the on-chain transaction |
events |
Ordered list of confidential operations to execute, one per emitted event log |
Each entry in events carries a type discriminant that selects the operator, plus the handle fields required for that operator. Events are processed in logIndex order.
| Type | Description | Fields |
|---|---|---|
wrap_as_public_handle |
Same as plaintext_to_encrypted for handles marked as publicly decryptable |
value, teeType, handle |
| Type | Description | Fields |
|---|---|---|
add |
Addition | leftHandOperand, rightHandOperand, result |
sub |
Subtraction | leftHandOperand, rightHandOperand, result |
mul |
Multiplication | leftHandOperand, rightHandOperand, result |
div |
Division (returns MAX on division by zero) |
leftHandOperand, rightHandOperand, result |
safe_add |
Addition with overflow detection | leftHandOperand, rightHandOperand, success, result |
safe_sub |
Subtraction with underflow detection | leftHandOperand, rightHandOperand, success, result |
safe_mul |
Multiplication with overflow detection | leftHandOperand, rightHandOperand, success, result |
safe_div |
Division with division-by-zero detection | leftHandOperand, rightHandOperand, success, result |
Safe variants produce two result handles: success (a bool indicating whether the operation did not overflow) and result (the computed value).
| Type | Description | Fields |
|---|---|---|
eq |
Equal | leftHandOperand, rightHandOperand, result |
ne |
Not equal | leftHandOperand, rightHandOperand, result |
ge |
Greater than or equal | leftHandOperand, rightHandOperand, result |
gt |
Greater than | leftHandOperand, rightHandOperand, result |
le |
Less than or equal | leftHandOperand, rightHandOperand, result |
lt |
Less than | leftHandOperand, rightHandOperand, result |
All comparison operators produce a single bool result handle.
| Type | Description | Fields |
|---|---|---|
select |
Ternary selection — returns ifTrue if condition is non-zero, ifFalse otherwise |
condition, ifTrue, ifFalse, result |
| Type | Description | Fields |
|---|---|---|
transfer |
ERC-20-equivalent transfer between two encrypted balances | balanceFrom, balanceTo, amount, success, newBalanceFrom, newBalanceTo |
mint |
ERC-20-equivalent mint into an encrypted balance | balanceTo, amount, totalSupply, success, newBalanceTo, newTotalSupply |
burn |
ERC-20-equivalent burn from an encrypted balance | balanceFrom, amount, totalSupply, success, newBalanceFrom, newTotalSupply |
Token operations produce a success bool handle plus updated balance handle(s). A transfer fails (success = false) when balanceFrom < amount. A mint or burn fails when it would cause the total supply to overflow or underflow respectively.
| Repository | Role |
|---|---|
| nox-ingestor | Event ingestor — listens for on-chain NoxCompute events and publishes TransactionMessage objects to the NATS stream |
| nox-handle-gateway | Handle Gateway — provides encrypted operands to the runner and stores result handles |
| nox-protocol-contracts | On-chain contracts — the NoxCompute contract exposes the KMS public key and gateway address read by the runner on startup |
The Nox Protocol source code is released under the Business Source License 1.1 (BUSL-1.1).
The license will automatically convert to the MIT License under the conditions described in the LICENSE file.
The full text of the MIT License is provided in the LICENSE-MIT file.