Data objects live in an ordinary database. Batches of them are hashed into one merkle root, and each root is written to a permissioned chain by a single hash-carrying transaction; a merkle path proves any single object against that root later. Bigger batches mean fewer (expensive, replicated) on-chain writes but deeper proofs and a larger hash index — so total cost has a floor somewhere in the middle. Every price here is a parameter you set, not a measured market figure.
Everything is expressed in cost units, where 1 unit is whatever it costs to keep 1 MB in the
off-chain database. That keeps the model free of invented prices: the only price is the ratio slider.
With N objects, batch size B, V validators, ratio R,
object size s, hash size h, tx overhead t, reads and
verifications per object k_read and k_verify, and write-vs-read price
w — all sizes converted to MB:
Three of the six bands are paid once, when the data lands: Raw Write,
Index Write and Blockchain. The other three are paid again on every
query: Raw Read, Index Read and Proof Size. Only the
read side responds to k_read, k_verify and w, so those three
sliders move the read half of the stack against a fixed write half — which is what decides whether
batching deeper is worth the extra proof depth it charges on every read. Because the unit is the
written MB, w divides the read side: at w = 10× a stored byte costs ten read
bytes, so a read byte is a tenth of a unit per MB.
N·s. Flat: the objects are stored once, off-chain, whatever the batching.⌈N/B⌉·(h+t) · R · V. One transaction per batch carrying one root,
priced at the on-chain ratio and paid once per validator that replicates it. Falls as 1/B.2N·h·(1 − 2⁻ᴸ), the stored hash nodes, where L is
how many levels you keep counting up from the leaves. Level i of a batch holds
B/2ⁱ nodes, so the halving series makes L=1 cost one hash per object,
L=2 cost 1.5, and the full tree (L ≥ log₂B + 1) cost two — never more, whatever
the batch size. Per batch the index is linear in B; there are N/B batches, and the
two cancel. That is why the band is a hairline: at full depth it costs 2h/s of the raw data,
6.25% for a 32-byte hash over a 1 KB object. Shrink the object to 100 B and the same index is 64% of raw.N·k_verify·(hashes read)·h / w, the index I/O to assemble a proof,
as opposed to the bytes the proof itself costs on the wire. A sibling sitting on a stored level is one read;
a sibling above the stored ceiling has to be rebuilt from its 2^(j−L+1) descendants on the
topmost stored level, so the count is min(L, d) + 2^(d−L+1) − 2 for depth d. Keep
the whole tree and it collapses to d reads — one sibling per level, the cheapest a proof can
be. Keep only the leaves and it is B−1; keep two levels and it is B/2. At
L = 0 there is no index at all and a proof re-hashes the entire batch out of the object store,
B·s bytes. This is the term that punishes a shallow index at a large batch size — it grows
linearly in B, not logarithmically, which is why the floor moves sharply left as you
cut levels.N·k_verify·(⌈log₂B⌉+1)·h / w. A merkle path is one sibling
hash per level plus the root, so this grows with log₂B — the price of batching.N·k_read·s / w. Shipping the objects themselves to whoever asks. Flat.
With the whole tree kept, two terms grow with log₂B — the proof on the wire and the index reads
that assemble it — against the on-chain term falling as 1/B, so the floor sits at
B* ≈ ln2 · (h+t) · R · V · w / (2 · k_verify · h) — larger fleets, pricier chains and cheaper
reads push the optimum toward bigger batches, more verification per object pulls it back. Cut the index below the batch
depth and the read term stops being logarithmic and turns linear in B, which drags the floor
sharply left; with nothing stored it lands within a couple of objects per root. Because proof depth is
⌈log₂B⌉ and every other term is non-increasing in B, the minimum always lands on a
power of two, which is why only powers of two are candidates. It is often not much of a minimum: once the
on-chain term is amortised away the tail goes flat, and every further doubling halves a saving that was
already invisible. The marker therefore reports the smallest batch within the
flat-floor tolerance of the floor — 0.3% by default, editable in the assumptions — giving up
a fraction of a percent for shallower proofs and less waiting for a batch to fill, and the tile names the
strict minimum and the gap whenever the two differ. Set the tolerance to 0 and the strict minimum is
reported instead; widen it and the reported batch walks left, one doubling at a time, because near a smooth
minimum the curve is quadratic and a wide band covers several doublings. Everything quoted as × cheapest is
measured against that strict minimum, not against the reported batch, so no row on the page reads as
cheaper than the batch being recommended. Batches beyond N are meaningless,
so the x-axis ends at the total object count, and its ticks are powers of two like the candidates
themselves — which makes K and M on that axis binary, 1K being
1024 objects per root.
Total object count sits in the assumptions rather than on a slider: all five terms are linear in
N, so it scales the whole stack without moving the floor by a single object — it only decides
where the x-axis has to stop. Object size is the parameter that changes the shape, because it
prices the two size-driven bands (raw data, query traffic) against the three hash-driven ones. At 1 KB an
object dwarfs its two hashes and the index is a hairline; at 32 B the same index is twice the size of the data
it protects, and the optimal batch stops being about the chain at all.
The whole view travels in the address bar, and Copy link hands you a link to exactly what is on
screen — opening it beats whatever the reader's own browser had remembered. It is packed to be short rather
than legible: one capital letter per setting, its value in base 36 immediately after it, no separators, and
anything still at its default left out entirely. So a link that only moves the validator count to 77 is
#V25, and the longest possible one is around thirty characters. Base 36 is why
V25 is 77 and not 25. Unrecognised letters are ignored rather than fatal, and
#0 means "the defaults, deliberately".
Not modeled: consensus and execution cost per transaction beyond its bytes, the CPU of hashing (which is what a shallow index really trades storage for), I/O operation counts as distinct from bytes moved, the latency of waiting for a batch to fill, root re-anchoring or checkpointing, and the off-chain writes themselves.