Rand Protocol
Documentation
Core concepts

Fees

What a transaction pays the chain in RAND - the flat floors per action, the gas and byte prices a call pays on chain 21, where the fee goes and what chain 21 burns, why gas here is a price and not a bound on execution, and what the sender pays with its own machine instead.

Use this page to set the fee on a transaction, to estimate what a deploy or a call costs, and to understand what a validator is paid. Fees are floors in RAND. Every action but a call pays a flat one. Since chain 18 a call pays for the gas limit its proof declares and for its bytes, at prices that move with how full blocks are and, since chain 20, stop at a ceiling. On chain 21 (fullnode v0.8.0, live since 2026-10-12) the floor itself is burned and the proposer keeps only the tip (below).

The floors

Fees are in units of 10⁻⁹ RAND. Constants live in randprotocol_core::gas:

transactionfloortoday
any bundle: a transfer of RAND or of any RPL token, a bond, a bridge deposit (BridgeAttest, paid by its relayer), a TokenMint, a SetAuthority, a TokenBurn, a ListBackingBUNDLE_BASE = 1,000,000 units0.001 RAND
DeployBUNDLE_BASE + DEPLOY_PER_WORD (100,000) × (program words + public words)a 256-word program: 0.0266 RAND; a 4,096-word program: 0.4106 RAND; the 65,096-word SPL Token image with its 27,151-word public input: 9.2257 RAND (deployed on chain 13, 2026-09-19). Since chain 16 (hardening_v6, chain 21 included) a deploy no call can hold is refused, as ProgramUncallable: more than 8,180 words for a program without a public input, fewer beside one. That image would be refused on chain 21
Callon a chain whose genesis has a gas section (chain 21, as chains 18 to 20): BUNDLE_BASE + gas_price × the gas limit the call proof declares + byte_price × each KiB (a partial KiB counting as whole) of call proof plus input envelope, from byte 0. See Gas on chain 18. On a chain without the section: BUNDLE_BASE + call_fee(tier), where call_fee = CALL_BASE (1,000,000) + 100,000 per two tiers above 10, plus 1,000 units per KiB (a partial KiB counting as whole) of call proof plus input envelope above the free allowance of 2,097,152 + 18,432 byteschain 21, at the starting prices of 100 and 800 units (chain 20’s): a tier-14 call declaring the tier’s ceiling of 20,479 gas with 1,350,000 bytes of proof and envelope has a floor of 0.0041 RAND; the wallet’s default pays more (below). Without a gas section, as on chain 15: tier 10: 0.002; tier 14: 0.0022 RAND, under the allowance. Since v0.5.6 a call above tier 14 is refused whatever it pays (MAX_CALL_TIER). The ERC-20 approve call made on chain 13 on 2026-09-19 (tier 16, a 3,412,405-byte proof) paid 0.00357 RAND and would be refused on any chain since v0.5.6
Invoke (RPL-2, since chain 20)a call’s floor, plus the genesis program_state.cell_fee for every cell the transition creates (a cell written non-zero where none was stored); rewriting or deleting a cell adds nothingthe cell_fee is 0.01 RAND on chain 21, as on chain 20: the tier-14 call above, creating two cells, has a floor of 0.0241 RAND
BridgeBurnBRIDGE_BURN_FEE = 10 × BUNDLE_BASE: the base plus the bridge’s own charge, both paid from the one bundle a burn carries0.01 RAND; since chain 20 the bridge also keeps 10 bps of the burned zUSD (below)
RegisterToken, RegisterBridgedTokenBUNDLE_BASE + the token registry’s registration_fee (a genesis parameter)chain 21, as chain 20: 0.001 + 1 RAND = 1.001 RAND, of which the 1 RAND is burned
Mint (faucet), Unbond0; no bundle, validator-signed—
PauseMints, UnpauseMints0; no bundle, authorised by the pause key or a PQ guardian quorum, so a pause works from a wallet holding no RAND—
Withdraw0 on the wire; the 0.001 RAND base is taken out of the amount withdrawna withdraw of 1,000 RAND creates a note worth 999.999

The fee is a public word of the bundle and is always RAND. The hidden-asset bundle guest binds it into the RAND group’s balance relation in₂ + in₃ = out₂ + out₃ + fee + burn_r (slots 2–3), so it is paid out of hidden notes without revealing which; a token moves through slots 0–1 of the same bundle under its own relation, in₀ + in₁ = out₀ + out₁ + burn_a. One proof therefore moves a token and pays its RAND fee, and a wallet holding a token but no spendable RAND is refused before proving.

The bridge’s charge falls on the burn because that is the one bridge transaction whose sender is sure to hold RAND; a deposit pays only the base, since the depositor has no RAND yet. The registration fee is a ledger fact rather than part of fee_floor, so it is checked with the action’s own rules (RegistrationFeeTooLow), the way a call’s gas or tier part is charged once its proof is decoded. A call’s floor is checked twice, because what it declares is only trusted once its proof has verified. On a gas-metered chain (chain 21, as chains 18 to 20) the first check, before any verification, prices the call’s bytes and a declared limit of 1 (an Invoke also owes its cell fee there); the second prices the limit the verified proof declares. On a chain without a gas section the two are BUNDLE_BASE + CALL_BASE and the tier-exact floor.

Ask the node rather than guessing:

rand fee bundle          # 0.001 RAND
rand fee deploy 256      # 0.0266 RAND
rand fee call 14 --gas 20479 --bytes 1350000   # 0.0041 RAND at chain 21's starting prices (chain 20's)
→ {"method":"rand_estimateFee","params":[{"kind":"call","tier":14,"gas":20479,"bytes":1350000}]}
← "4103100"

That answer is 1,000,000 + 100 × 20,479 + 800 × 1,319 at the starting prices; the node prices whatever the tip’s prices are when it is asked. Since chain 20 the call spec’s gas field is required over JSON-RPC (-32602 without it). rand fee call <tier> without --gas sends the tier’s ceiling, and without --bytes prices no bytes. An invoke is priced the same way, with "kind":"invoke" and "created_cells": c adding c cell fees (24103100 for the example above with two cells, asked of chain 20 on 2026-10-01). On chain 15, which had no gas section, rand fee call 14 answered 0.0022 RAND.

The wallet’s defaults are exactly the floors, except for a call on a gas-metered chain (chain 21), whose default adds headroom (below); rand send --fee <RAND> and the --fee flag on bond, call, bridge-mint, bridge-burn (default 0.01) and the token subcommands raise them. Anything above the floor is a tip.

Gas on chain 18

Chain 18, live from 2026-09-29 to 2026-10-01, was the first gas-metered chain, and chain 19, its re-genesis on the same build, carried the same gas section. Chain 20 (v0.6.8) kept every rule of this section and added two price ceilings and a byte load counted only on the bytes that pay (below). Chain 21 (v0.8.0, 2026-10-12) carries chain 20’s gas section unchanged but for the block’s byte target, which follows its smaller block cap. A chain without a gas section keeps the tier schedule in the table above.

What is counted. One gas is one row of the zkVM’s cpu table, so an ordinary instruction costs 1. The syscalls are the only other costs:

operationgas
any instruction, HALT, WRITE_OUTPUT, READ_INPUT, READ_PUBLIC1
POSEIDON2 over n words3 + 3 × ⌈n/4⌉ (each absorb row costs 3)
SHA256, one compression64
KECCAK, one permutation192

The declared limit. A call proof carries one public value more than before, GAS_LIMIT, and the circuit (constraint set 8) proves that the run’s gas is at or under it. The exact gas stays private. A limit above the ceiling of the proof’s own header is refused before any verifier key is built:

gas_max = (2ᵗ − 1) + 2^(t−2) + 191 × (keccak rows / 32) + 63 × (sha256 rows / 64)

That is 1,279 at tier 10, 5,119 at tier 12 and 20,479 at tier 14 for a call that uses neither hash table.

The floor. The ledger charges the declared limit and the bytes, at the prices in force when the block starts:

fee ≥ BUNDLE_BASE + gas_price × gas_limit + byte_price × ⌈bytes / 1024⌉

bytes is the call proof plus the input envelope. The tier schedule and its free allowance do not apply on such a chain. The pool demands exactly what a block does, and refuses a cheaper call as FeeTooLow.

Bundles and the authorisation proof declare pinned limits. Every bundle proof must declare exactly the genesis bundle_gas_limit, 20,479 (the tier-14 ceiling), or the transaction is refused as BundleGasLimit. Chain 21, like every chain since 17, runs split authorisation, so every bundle carries a tier-10 authorisation proof, which must declare 1,279 (AuthGasLimit); a bundle authorised by a private multisig policy, new on chain 21, carries a tier-18 proof of the second auth guest instead (Transactions). Neither is a charge: a transfer, a bond or a deploy still pays its flat floor from the table above. The wallet declares both values itself.

The prices move. Since chain 18 the gas section has a dynamic controller, so gas_price and byte_price are ledger state, committed in the state root. Once per block, after its transactions, each moves by how full the block was against a target:

price' = min(max_price, max(min_price, price + ⌊price × adjust_bps × (used − target) / (10,000 × target)⌋))

used is capped at twice the target, so adjust_bps is the largest move one block can make in either direction. max_price is the genesis ceiling; chains 18 and 19 had none. byte_price follows the block’s paying bytes (below); gas_price follows the sum of its calls’ and invokes’ declared limits plus 20,479 for every bundle.

chain 21 gas section (chain 20’s, but for the byte target)value
gas_price, byte_price at genesis100 units per gas; 800 units per KiB
min_gas_price, min_byte_price100; 800, so the starting prices are also the floors
max_gas_price, max_byte_price10,000; 80,000: the ceilings, 100 times the floors (new on chain 20)
byte_loadpaying (new on chain 20)
target_block_bytes9,961,472 on chain 21, half its 19 MiB block cap (10,485,760 on chain 20, half of 20 MiB)
target_block_gas262,144
adjust_bps1,250: at most 12.5% a block
bundle_gas_limit20,479
meteringcircuit

A block under its targets moves the prices down, so on a quiet chain they sit at the floors. rand_getLimits serves the tip’s current gas_price and byte_price with gas_metering: "circuit", bundle_gas_limit, adjust_bps, and since v0.6.8 max_gas_price, max_byte_price and byte_load (null on a chain without them); rand_status reports the same two prices as gas_prices.

Ceilings and the paying byte load

Both are genesis fields of gas.dynamic, new in v0.6.8 (audit v6, POOL-2), and chain 20 is the first chain to set them. They fix a fault chains 18 and 19 had. There, byte_price moved with every transaction’s encoded length, while only a call pays it. Seven transfers (about 2.85 MB each, flat fee) fill a block to about 1.9 times the byte target and lift the price about 11% a block, with no ceiling: about 65 such blocks take it to 1,000 times its start, and a call then costs about 1 RAND instead of about 0.004.

  • Ceilings. max_gas_price and max_byte_price are prices the controller never lifts past. The step is the same and then clamped (gas::next_price_capped), so a price at its ceiling stays there through any run of full blocks and falls on the first block under target. A genesis must set each at or above its starting price. At these ceilings (chain 21 keeps chain 20’s) the tier-14 call above, 20,479 gas and 1,319 KiB, has a floor of 0.3113 RAND, the highest its floor can go.
  • The paying byte load. Under byte_load: "paying" the block’s bytes_used counts only the bytes the byte price is charged on: each Call’s and Invoke’s proof plus input envelope (gas::call_bytes), nothing of a transfer, a bond or a burn. A block of transfers moves no byte price. Ledger::block_usage computes the figure for both the proposer and the replica, so the two agree.

Under the paying load a block full of transfers gives a call no price signal, so an honest proposer also reserves room for calls. This is node policy, not a validity rule: while a call or an invoke is pooled and pays its current floor, flat-fee transactions take at most three quarters of the block’s bytes in the first pass, and what the calls leave of the last quarter is then filled with the transfers held back (Mempool).

What the wallet pays for a call

rand call runs the program in the emulator first and prints gas: <exact> (declaring <limit>, tier <t>). By default it declares the exact gas rounded up to a multiple of 2^(tier − 2); --gas-limit N declares an exact figure and --gas-limit max the header’s ceiling.

Two things make the default fee higher than the floor:

  • The proof is priced at the cap. Since chain 16 (hardening_v6, chain 21 included) a call proof is bound to its own transaction, so the fee is fixed before the proof exists. The wallet prices the chain’s proof cap — 6 MiB on chain 21, 4 MiB on chains 18 to 20 — in place of the real proof size (wallet::hardened_call_quote_bytes). That can only overpay.
  • Two price steps of headroom. rand_getLimits serves the committed head’s prices and a call lands two or three blocks later, so the default multiplies the floor by (1 + adjust_bps / 10,000)², which is 1.125² on chain 21, as on chain 20.

Measured on chain 18 on 2026-09-30, four calls cost between 0.0057 and 0.0075 RAND each at these defaults (Example programs). --fee overrides the default; the wallet re-prices the real proof before it submits and refuses a fee under its floor.

Where the fee goes

On chain 21 the floor is burned and the proposer keeps the tip. Chain 21’s genesis carries a fees section with burn_base and burn_floor set (fullnode v0.8.0; rand_getLimits.fee_rules serves it as {"burn_base": true, "burn_floor": true, "subsidy_net_of_fees": false, "proposer_share_bps": null, "prove_base": null, "usd_subsidy": null}). In every bundle’s fee split the ledger’s own floor for that bundle — BUNDLE_BASE for a transfer, a bond, a token action or an attestation; BUNDLE_BASE plus the per-word term for a Deploy; the tier-exact call floor at the block’s prices for a Call, plus the cell fee for an Invoke; BRIDGE_BURN_FEE for a BridgeBurn, so the bridge’s own charge is destroyed with the base — is burned (burned = min(fee, floor)), and the block proposer’s rewards in the validator register, the one place on this chain that stores an amount in the clear, grow by the tip, fee − floor, alone. rand_getSupply counts the burn in base_fees_burned, on the right of the supply identity beside registration_fees_burned (Supply), and fees_paid moves by what the proposer keeps. Nothing a sender pays changes: the floors and the wallet’s defaults are the numbers above, only where the floor goes differs. The faithful EIP-1559 form was chosen over burning the base alone so that a proposer gains nothing from a block that lifts a price. A registration’s registration_fee is not part of the floor; it burns under its own flag, as before.

On chains 15 to 20 all of it — floor and tip — was credited to the proposer, with one exception. On a genesis with tokens.burn_registration_fee (chain 21, as every chain since 15), a RegisterToken’s or RegisterBridgedToken’s registration_fee is burned and the proposer keeps fee − registration_fee; rand_getSupply counts it in registration_fees_burned. On chain 14, which lacked the flag, the proposer kept the whole fee. A validator turns accumulated rewards into a note with rand-node withdraw (Staking). There are no block rewards and no inflation on chain 21: tips are the whole of a validator’s income. A Withdraw’s base is paid register-side, out of the amount withdrawn, and never passes the bundle fee split, so it is not burned.

One fee since chain 20 is not RAND and goes to no validator. Under the genesis bridge.fees (new in v0.6.8, carried into chain 21) the bridge keeps 10 bps of every zUSD deposit and of every zUSD burn, rounded down to a whole release unit of the backing, as a zUSD note to a treasury address the genesis names. A deposit credits the depositor the gross less the fee; a burn releases amount − fee on the source chain. The fee note stays issued and backed. See Bridge and RPL tokens.

The mempool orders candidates by what they pay above their floor per KiB, then by fee (Mempool), so a higher fee only matters under congestion. A transfer is about 2.85 MB (measured on chain 18): a 1.49 MB bundle proof and a 1.36 MB authorisation proof, so about seven fit chain 20’s 20 MiB block and six chain 21’s 19 MiB one at that size (constraint set 9’s proof sizes on chain 21 are not measured here yet). The fullnode’s own note was that a fee market on bytes — not on opcodes — is what the floor should become before mainnet. Since chain 20 the byte price is that market for calls, moved only by the bytes calls pay for; a transfer’s floor is still flat, and since chain 21 it is burned.

Why gas is a price here, not a bound

Ethereum meters every opcode because every node executes every transaction and execution is open-ended. On Rand no node executes anything: the sender runs the program once, off chain, and hands the chain a proof. A node’s work per transaction is a fixed sequence — decode the public fields, run the free checks, verify one STARK — and that verification costs about 16 ms with a warm verifier key whatever the program did. So the three jobs of gas fall apart:

  • Termination is enforced by the tier: a run that does not halt within 2ᵗ − 1 cycles cannot be proved at all, and the prover absorbs the failure.
  • Pricing the work is what the gas floor does, since chain 18. The network’s cost per instruction is near zero, but the sender’s proving work is real, and a flat floor priced a million-cycle call like a thousand-cycle one. Until chain 18 the floor was flat per tier.
  • Block sizing is a byte budget (MAX_BLOCK_BYTES = 4 MiB by default, the genesis max_block_bytes otherwise) and a transaction count (2,000).

Two caveats the fullnode states plainly: bytes do vary (a proof is over a megabyte, so block space, not compute, is the scarce resource), and state growth — the tree, the nullifier set, deployed program bytes — is priced only by the per-word deploy fee and the flat base, which is adequate for a testnet and worth revisiting when state size matters.

What the sender pays instead: proving

The one cost that varies is producing the STARK proof, and only the sender’s machine pays it. In order of effect:

  1. The tier t ∈ {10, 12, 14, 16, 18, 20} (a call above 14 is refused on chain since v0.5.6; a bundle is pinned to 14, and since chain 17 carries a tier-10 authorisation proof beside it, about 7 s more): the cpu table is padded to 2ᵗ rows and each step roughly quadruples the padded trace. Measured on the development laptop: private_payment at tier 10 in about 7 s; the hidden-asset bundle guest at tier 14 in about 100 s, whichever asset it moves.
  2. Cycles executed, because they decide the tier: instructions run, not instructions written.
  3. Program size, weakly: one digest row per four words at the start of the trace.
  4. Private-input size, the same way.
  5. Which tables are present: a keccak or sha256 table costs its columns’ share only when the guest used it.
  6. The FRI profile: 80 queries instead of 27 triples proof size but barely changes proving time.

Proving does not depend on the amounts, on how many notes the sender owns, or on the chain’s size: membership is proved against a fixed-depth tree, so a transfer costs the same at block ten and block ten million.

Reference

constantvalue
BUNDLE_BASE1,000,000 units
BRIDGE_BURN_FEE10,000,000 units (10 × BUNDLE_BASE)
registration_fee (genesis tokens section)1,000,000,000 units on chain 21, as on chain 20, burned (burn_registration_fee)
DEPLOY_PER_WORD100,000 units
CALL_BASE, CALL_PER_TIER_STEP1,000,000; 100,000 units; the tier schedule of a chain without a gas section
GAS_PRICE_DEFAULT, BYTE_PRICE_DEFAULT100 units per gas; 800 units per KiB. Chain 21’s starting prices and its price floors, as chain 20’s
KECCAK_GAS, SHA256_GAS, POSEIDON2_ABSORB_GAS192; 64; 3 gas per row
max_gas_price, max_byte_price (genesis gas.dynamic)10,000 units per gas; 80,000 units per KiB on chains 20 and 21; no ceiling when absent (chains 18 and 19)
byte_load (genesis gas.dynamic)paying on chains 20 and 21: only call and invoke proof and envelope bytes move byte_price; absent, every transaction’s bytes
cell_fee (genesis program_state)10,000,000 units (0.01 RAND) per created cell on chains 20 and 21; at most 1,000 RAND
bundle_gas_limit (genesis gas section)20,479 on chain 21, as since chain 18, gas_max(14, 0, 0); an authorisation proof declares 1,279, gas_max(10, 0, 0)
adjust_bps (genesis gas.dynamic)1,250 on chain 21, as since chain 18; a genesis may set 1 to 5,000
target_block_bytes, target_block_gas (genesis gas.dynamic)9,961,472 bytes on chain 21 (10,485,760 on chain 20); 262,144 gas
fees section (genesis, chain 21)burn_base and burn_floor true: every bundle’s settled floor is burned and counted in rand_getSupply.base_fees_burned; the proposer keeps fee − floor. subsidy_net_of_fees, proposer_share_bps, prove_base and usd_subsidy are unset: they concern aggregating chains, and chain 21 has no aggregation section
MAX_PROGRAM_WORDS4,096, unless the genesis sets max_program_words (at most 65,535; chains 20 and 21 set 65,535, and hardening_v6 refuses a program over 8,180 words that no call could hold)
MAX_PROOF_BYTES2 MiB, unless the genesis sets max_proof_bytes (6 MiB on chain 21, room for the tier-18 private-multisig authorisation proof; 4 MiB on chains 18 to 20; 8 MiB on chains 13 to 15)
MAX_BLOCK_BYTES, MAX_BLOCK_TXS4 MiB (unless the genesis sets max_block_bytes; 19 MiB on chain 21, 20 MiB on chain 20), 2,000
tiers10, 12, 14, 16, 18, 20; cycles ≤ 2ᵗ − 1
MAX_CALL_TIER14: a call proof above it is refused at admission and in a block (since v0.5.6)

Last verified against fullnode commit c0878c3f (chain 21) on 2026-10-12. Where the code and this page disagree, the code is right; tell us.