Cosmic Bull

meters

gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/meters on onyx-1 testnet

realmapplicationbillinglive on onyx-1

Bill for metered usage, with discounts as volume grows.

Usage is recorded as it is served and each period settles from a prepaid deposit, so neither side has to trust an invoice.

In detail curated

USAGE-BASED BILLING WITH GRADUATED MARGINAL PRICING -- the usage-based complement to r/subscriptions' flat recurring plans and the one billing shape this portfolio lacked (the finding of its own discovery gate, which took candidate E after the receivables and payslips gates deferred it twice as 'a larger product than a primitive-consumer'). A provider publishes a METER: a unit of service, an ugnot list price per unit, a billing period in blocks, and a p/tiers schedule of marginal volume discounts over the ugnot BASE (units x unitPrice). A consumer ENROLLS and prepays a deposit; the provider RECORDS usage as it serves it; each period's accumulated usage settles lazily from the consumer's deposit into the provider's claimable balance, the protocol fee splitting at settlement from the enrollment's snapshotted bps. tiers' rate cap makes Charge(base) <= base an ARITHMETIC FACT, so a schedule discounts list and can never surcharge it; a flat price is the one-rate degenerate schedule and a free allowance is a 0-rate first tier, both first-class. THREE TRUST STATEMENTS GOVERN IT. (1) USAGE IS PROVIDER-ASSERTED AND THE DECLARATION IS THE TRUST BOUNDARY -- the boundary r/books draws at owner-posted entries and r/cashflow at Policy.Cash: the realm cannot verify off-chain consumption, it makes the assertion attributable (Record is provider-only, against an enrollment to that provider's meter), bounded, and auditable (every unit is an event and a readable row). (2) LIABILITY IS BOUNDED BY DEPOSIT, FAIL-CLOSED: Record REFUSES -- never clips -- any posting whose end-of-period charge would exceed the deposited balance, WithdrawDeposit holds back the pending charge, and no arrears exist anywhere, so a consumer's maximum exposure to a hostile or buggy provider is exactly what they deposited, by construction. (3) SETTLEMENT IS LAZY AND EXACT: usage belongs to the one period it was recorded in, the first touch after a boundary settles it, silent periods owe zero, and a permissionless Settle valve means neither party's absence wedges the other's funds. Enrollment terms (price, schedule, period, fee bps) are COPIED AT ENROLL and never re-read; meters are immutable, so nothing a consumer agreed to can move underneath them. Funds move on pull paths only through the coinio guard-trio over a feeledger whose conservation invariant (Held == Liabilities == deposits + claimables + fee pot) was checked LIVE at a nonzero balance. THE AUDIT'S RED IS THE DESIGN'S BEST SENTENCE: the zero-charge settlement (a free tier's, or a positive rate floor-rounding to zero) originally called MustWithdraw(0), which feeledger refuses, wedging the enrollment and stranding the deposit forever through every funds path -- fixed by guarding the funds move, never by refusing free usage, both routes pinned by tests a fresh-context adversarial pass proved non-vacuous, and the zero-charge Exit was then exercised ON CHAIN with a full refund.

Use it

A realm holds state. Reading it is a free query; changing it is a transaction you sign yourself.

Read it — no key, no account, no GNOT

gnokey query vm/qeval \
  --data 'gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/meters.FeeBps()' \
  --remote https://rpc.onyx.testnets.gno.land:443

FeeBps is read-only, so this is a query rather than a transaction: it costs nothing, signs nothing and needs no funded account. Every exported function is listed under the API below; swap the name and the arguments to read any of the others.

Change its state

Every state change is a transaction you sign with your own key — this site never asks for one and holds none. You do not have to hand-write the transaction: gnoweb generates the exact gnokey maketx call command for any of this realm's 16 crossing functions, with the argument names filled in.

Read the known limitations further down this page first. They record what was not verified on chain, which is usually the part that matters.

Import it from another realm

import "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/meters"

The path is the address: there is no package manager, nothing to add to a manifest and no version to pin, so the package you compile against is the one the chain runs. It differs per chain because the namespace in it is the deploying account's.

Deployments

onyx-1 testnet onyx-1

RenderedSourceCall builderState

chain-attested
Import pathgno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/meters
Namespaceg1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3
Deployed at height481,166
Deploy transactionf861becf4ca8cb467607f629f1bc9289923d4e0271b4c3425d3df054a3b9ec9d look it up on the RPC
Deployerg1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3
Gas used45,913,168
Storagenot attested per package on this chain — see the limitations below
Files on chaingnomod.toml meters.gno
Deployed bytesmeters.gno — 33,970 bytes
sha256a7ee325b90367f78137695efe142bafbb0a52c062d4b34fc476710d8bef5a49e
Realm addressg1upuageltru7n0gzj3txmc8qm0wzpcj76jchvq7

Do not take the hash above on trust. $download returns the bytes onyx-1 is actually running; this command fetches them and prints their digest, which should equal the one in the table:

curl -sS 'https://onyx.testnets.gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/meters$download&file=meters.gno' | shasum -a 256

Expected: a7ee325b90367f78137695efe142bafbb0a52c062d4b34fc476710d8bef5a49e — 33,970 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.

API

chain-derived 33 exported functions.

Every function below deep-links to gnoweb's call builder, which generates a ready-to-run gnokey maketx call for it:

AcceptAdminActiveEnrollIDBalanceOfChargeHeadroomOfClaimClaimAllClaimableOfCreateMeterCurrentPeriodOfDepositEnrollExitFeeBpsFeesAccruedHeightHeldLiabilitiesNumEnrollmentsNumMetersPendingChargeOfPriceOfRecordRenderRetireMeterScheduleOfSetFeeBpsSetFeeRecipientSettleSweepDenomTransferAdminUsersTotalWithdrawDepositWithdrawFees

Overview

Realm meters is usage-based billing with graduated marginal pricing: a provider publishes a METER (a unit of service, a list price per unit, a billing period, and a tiers.Schedule of marginal volume discounts), a consumer ENROLLS and prepays a deposit, the provider RECORDS usage as it is served, and each period's accumulated usage settles from the consumer's deposit into the provider's claimable balance.

It is the usage-based complement to r/subscriptions' flat recurring plans — the one billing shape this portfolio lacked (its discovery gate's whole finding). The pricing identity is p/tiers':

base = units × unitPrice (checked int64 multiplication) due = Schedule.Charge(base) (≤ base — marginal discount off list)

tiers validates every rate into [0, RateScale], so Charge(base) ≤ base is an arithmetic fact: a schedule can DISCOUNT list price and can never surcharge it. A flat price is the one-rate schedule (no edges, rates "10000") — the degenerate case, not a second code path.

The three trust statements, in order of who bears what

Funds move on pull paths only: providers Claim, consumers WithdrawDeposit or Exit, the fee recipient WithdrawFees. Periods are fixed-length block windows anchored at Enroll height — runtime.ChainHeight() throughout, never a caller-supplied time (r/books' obligation, inherited verbatim).

REALM-CALLER CAVEAT, inherited from the siblings verbatim: coinio's receipt guard admits only EOA payers, so enrolling consumers are EOAs; assertNoSend reads the ORIGIN envelope, so every non-payable function refuses any transaction that attached coins anywhere. Providers may be EOAs or realms, but a realm provider must expose its own crossing path to Claim, or what it earns is stranded.

Imports

Constants and variables

Meter status values.

const (
	MeterActive  = "active"
	MeterRetired = "retired"
)

Enrollment status values. Exited is terminal.

const (
	EnrollActive = "active"
	EnrollExited = "exited"
)

Input bounds.

const (
	MaxTitleLen = 80
	MaxUnitLen  = 24

	// MinUnitPrice keeps a meter from being free-by-rounding; a provider
	// who wants a free tier declares a 0-rate first tier instead, which
	// keeps the arithmetic visible in the schedule rather than implicit
	// in a zero price.
	MinUnitPrice = int64(1)
	// MaxUnitPrice keeps base = units × unitPrice far from int64
	// overflow for any usage a deposit could plausibly cover; the
	// checked multiplication is still there and still refuses.
	MaxUnitPrice = int64(1_000_000_000) // 1000 GNOT per unit

	// MinPeriodBlocks/MaxPeriodBlocks bound a meter's billing period —
	// the same bounds and reasoning as r/subscriptions: the floor keeps
	// settlement from becoming a per-block treadmill, the ceiling keeps
	// period arithmetic far from overflow.
	MinPeriodBlocks = int64(10)
	MaxPeriodBlocks = int64(7500000)

	// MaxMetersPerProvider bounds catalog monopolization by a single
	// address — the finding that was RED in permission_registry and
	// service_registry, carried from the start here.
	MaxMetersPerProvider = 20

	// MaxEnrollmentsPerConsumer bounds one account's open-enrollment
	// state. Exited enrollments free their slot.
	MaxEnrollmentsPerConsumer = 100

	// RenderLimit bounds every rendered list — an unbounded Render was
	// YELLOW in three prior audits.
	RenderLimit = 20
)

Denom is the only asset this realm accepts.

const Denom = "ugnot"

MaxFeeBps is the hard protocol-fee cap: 1000 bps = 10%.

const MaxFeeBps = int64(1000)

Functions

AcceptAdmin

func AcceptAdmin(cur realm)

Crossing function. Callable from a transaction via MsgCall, and from another realm as AcceptAdmin(cross(cur), ...).

AcceptAdmin completes a staged admin transfer. Pending admin only.

ActiveEnrollID

func ActiveEnrollID(meterID int64, consumer address) (int64, bool)

ActiveEnrollID resolves the caller-free (meter, consumer) index.

BalanceOf

func BalanceOf(enrollID int64) int64

BalanceOf is an enrollment's deposited balance as the LEDGER holds it — including a not-yet-settled elapsed period's charge; net view: subtract PendingChargeOf.

ChargeHeadroomOf

func ChargeHeadroomOf(enrollID int64) int64

ChargeHeadroomOf is how much MORE charge the current period could absorb before Record's deposit bound refuses: balance − pending charge.

Claim

func Claim(cur realm, amount int64)

Crossing function. Callable from a transaction via MsgCall, and from another realm as Claim(cross(cur), ...).

Claim sends amount ugnot of the caller's claimable balance back to the caller. Providers earn into this balance at every settlement.

ClaimAll

func ClaimAll(cur realm)

Crossing function. Callable from a transaction via MsgCall, and from another realm as ClaimAll(cross(cur), ...).

ClaimAll sends the caller's entire claimable balance back to the caller. Fails if there is nothing to claim.

ClaimableOf

func ClaimableOf(a address) int64

CreateMeter

func CreateMeter(cur realm, title, unit string, unitPrice, periodBlocks int64, edges, rates string, maxFeeBps int64) int64

Crossing function. Callable from a transaction via MsgCall, and from another realm as CreateMeter(cross(cur), ...).

CreateMeter publishes a meter and returns its id. No coins are accepted; the storage deposit the caller pays is the anti-spam. edges and rates are comma-separated int64 lists building the tiers.Schedule over the ugnot BASE (units × unitPrice): len(rates) == len(edges)+1, each rate in [0, 10000] ten-thousandths of list price. "" edges with rates "10000" is a flat meter. The current protocol fee is snapshotted in and must not exceed maxFeeBps, the ceiling the provider signed for — pass MaxFeeBps to accept any legal fee. Meters are immutable once created: price, period or schedule changes are a new meter, so nothing a consumer agreed to can move underneath them.

CurrentPeriodOf

func CurrentPeriodOf(enrollID int64) int64

CurrentPeriodOf is the period index now open for an enrollment.

Deposit

func Deposit(cur realm, enrollID int64)

Crossing function. Callable from a transaction via MsgCall, and from another realm as Deposit(cross(cur), ...).

Deposit adds the attached coins to the caller's own enrollment deposit. Elapsed usage settles first, so a top-up lands on a current balance.

Enroll

func Enroll(cur realm, meterID int64) int64

Crossing function. Callable from a transaction via MsgCall, and from another realm as Enroll(cross(cur), ...).

Enroll opens an enrollment on a meter for the caller, with any attached coins credited as the initial deposit. One active enrollment per (meter, consumer); Exit frees the slot. Terms (price, schedule, period, fee) are copied in at this moment and never re-read.

Exit

func Exit(cur realm, enrollID int64)

Crossing function. Callable from a transaction via MsgCall, and from another realm as Exit(cross(cur), ...).

Exit closes the caller's own enrollment: elapsed periods settle, the CURRENT period's recorded usage settles immediately (the provider served it; exiting is not an escape from it), the remaining deposit refunds in full, and the slot frees. Terminal.

FeeBps

func FeeBps() int64

FeesAccrued

func FeesAccrued() int64

Height

func Height() int64

Held

func Held() int64

Liabilities

func Liabilities() int64

NumEnrollments

func NumEnrollments() int64

NumMeters

func NumMeters() int64

PendingChargeOf

func PendingChargeOf(enrollID int64) int64

PendingChargeOf is the charge the enrollment's current recorded usage would settle at — what WithdrawDeposit holds back.

PriceOf

func PriceOf(meterID, units int64) int64

PriceOf prices units of meter meterID at its schedule: the charge a period closing at exactly that usage would settle. The quote every consumer can take before enrolling and every provider can show beside an invoice.

Record

func Record(cur realm, enrollID, units int64)

Crossing function. Callable from a transaction via MsgCall, and from another realm as Record(cross(cur), ...).

Record asserts that the enrollment's consumer used units more units of the meter's service in the CURRENT period. Provider only. Fail-closed on the deposit bound: if charging the period's new total at the enrollment's schedule would exceed the deposited balance, the whole posting is refused — never clipped, because a clipped assertion is a wrong assertion with the same signature as a right one. Any elapsed period settles first, so the bound is always measured against the balance that will actually back this period.

Render

func Render(path string) string

Render shows the catalog at "", a meter at "meter/<id>", an enrollment at "enroll/<id>". Every user string is sanitized; every list is bounded by RenderLimit with the bound stated.

RetireMeter

func RetireMeter(cur realm, meterID int64)

Crossing function. Callable from a transaction via MsgCall, and from another realm as RetireMeter(cross(cur), ...).

RetireMeter stops NEW enrollments and NEW usage on the caller's own meter. Existing enrollments keep their funds paths whole: pending usage still settles, deposits stay withdrawable, Exit still refunds. The provider's meter slot frees. Terminal.

ScheduleOf

func ScheduleOf(meterID int64) (edges, rates []int64)

ScheduleOf returns a meter's schedule as its edge and rate slices.

SetFeeBps

func SetFeeBps(cur realm, bps int64)

Crossing function. Callable from a transaction via MsgCall, and from another realm as SetFeeBps(cross(cur), ...).

SetFeeBps sets the protocol fee snapshotted into NEW meters. Admin only. No existing meter or enrollment moves.

SetFeeRecipient

func SetFeeRecipient(cur realm, recipient address)

Crossing function. Callable from a transaction via MsgCall, and from another realm as SetFeeRecipient(cross(cur), ...).

SetFeeRecipient points fee withdrawal and sweeps at recipient. Admin only.

Settle

func Settle(cur realm, enrollID int64)

Crossing function. Callable from a transaction via MsgCall, and from another realm as Settle(cross(cur), ...).

Settle settles any fully-elapsed period of an enrollment. Permissionless by design — like the sibling realms' valves, no settlement's liveness may depend on either party showing up. No coins may ride on it, and calling it on an enrollment with nothing elapsed is a refused no-op so the event stream never carries empty settlements.

SweepDenom

func SweepDenom(cur realm, denom string)

Crossing function. Callable from a transaction via MsgCall, and from another realm as SweepDenom(cross(cur), ...).

SweepDenom recovers out-of-band coins (sent by raw bank transfer, outside any entrypoint) to the fee recipient. For the ledger denom the reserve is Liabilities() — user balances and the fee pot are structurally unreachable. Fee recipient only.

TransferAdmin

func TransferAdmin(cur realm, successor address)

Crossing function. Callable from a transaction via MsgCall, and from another realm as TransferAdmin(cross(cur), ...).

TransferAdmin stages successor as the next admin; AcceptAdmin completes it. Two-step with consent — a one-step transfer bricked resources in two prior audits (service_registry Y4, permission_registry Y4).

UsersTotal

func UsersTotal() int64

WithdrawDeposit

func WithdrawDeposit(cur realm, enrollID, amount int64)

Crossing function. Callable from a transaction via MsgCall, and from another realm as WithdrawDeposit(cross(cur), ...).

WithdrawDeposit returns amount of the caller's own enrollment deposit. The current period's pending charge stays covered: withdrawable is balance − Charge(current usage), so a provider's already-recorded usage can never be defunded. Elapsed usage settles first.

WithdrawFees

func WithdrawFees(cur realm)

Crossing function. Callable from a transaction via MsgCall, and from another realm as WithdrawFees(cross(cur), ...).

WithdrawFees sends the accrued fee pot to the fee recipient. Only the fee recipient may call it, and only the pot moves.


Doc text is reproduced as vm/qdoc returns it. The node markdown-escapes doc comments, so a bracket or angle bracket may carry a backslash the committed source does not have. The source itself is at source and in this repository.

Dependencies

chain-attested
Importschain, chain/runtime, chain/runtime/unsafe, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/tiers, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, strconv
First-party dependenciesgno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/tiers

Known limitations

Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.

curated

Source

Source fileonyx/r/meters/meters.gno at commit c2e10d17ffbf in the project repository (not public — the digest command above is the check that needs no repository)
Matches the deployed bytesyes — byte-identical