README.md
gno.land/p/moul/x/storagecost/v0
Is it worth paying gas to delete on-chain state?
On gno.land every byte of realm state locks GNOT, and the lock is refunded to whoever signs the transaction that frees the byte. Deleting state is therefore paid work. Whether a particular deletion pays depends on two prices that move independently: the storage price, a chain parameter, and the gas price of the day. A contract cannot know the second one, so it cannot decide on its own behalf whether to compact, reindex or reap. It can only publish the size of the prize and let a caller do the arithmetic.
This package is that arithmetic: pure integer maths, no chain imports, so a
realm can call it inside a Render and an off-chain bot can reuse the identical
formula.
The one number to remember
One byte freed refunds 100 ugnot. At the lowest gas price mainnet has actually accepted, one ugnot buys 1000 gas. So a byte is worth 100,000 gas, and any deletion costing less than that per byte pays for itself.
1q := storagecost.EvaluateAtFloor(17835, 5_000_000)
2q.Refund // 1783500 ugnot
3q.Fee // 5000 ugnot
4q.Net // 1778500 ugnot
5q.Worth() // true
6q.String() // "17835 bytes, refunds 1.7835 GNOT against 0.005 GNOT of gas, break-even 50 bytes: worth 1.7785 GNOT"
What is in here
Refund · BreakEvenBytes · Net |
the core arithmetic. BreakEvenBytes rounds up, so a quoted threshold always covers the fee |
GasFee · FloorGasFee |
a fee from a gas ceiling and a rational gas price. The fee tracks gas_wanted, not gas_used, so unused headroom is paid for |
Evaluate · EvaluateAtFloor · Quote |
a whole verdict for one candidate cleanup, with a String() fit for a Render |
EstimateBytes |
what a payload really costs once a realm has wrapped it in an object, at the measured 1.85x |
FormatGNOT |
ugnot as readable GNOT, because gno has no floats and a bounty quoted in ugnot is unreadable |
Two honesty notes
DefaultStoragePrice is a default, not a fact. vm:p:storage_price is
governance settable. Read it from the chain when real money depends on the
answer; the constant is for sizing and display.
EstimateBytes is an estimate. It exists so a bounty shown on a page is
within a factor of two instead of reporting raw payload length. No stdlib call
exposes a realm's real locked storage, so the authoritative numbers are the
chain's: the vm/qstorage query, or the StorageDepositEvent and
StorageUnlockEvent every transaction emits. Never settle an accounting
question with a guess.
Demo realm: r/moul/x/reaper.
Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.
Dependency graph:

🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.