subscriptions
gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/subscriptions on gno.land mainnetgno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/subscriptions on onyx-1 testnet
realmfactory-applicationrecurring-paymentslive on mainnetlive on onyx-1
Run paid subscriptions and check who is entitled.
Other realms and services gate access on a single entitlement question, with renewal, grace and expiry handled here.
In detail curated
gno.land mainnet
Multi-provider subscription hub: immutable per-period plans, exact-price Subscribe/Renew, deterministic renewal window with one-period prepay bound and exclusive grace, terminal refund-free Cancel honoring paid entitlement, permissionless Expire; EntitledFor is the one-call integration surface. Composes coinio + feeledger + avl/v0 + sanitize/v0. Application-factory build #5.
onyx-1 testnet
Multi-provider subscription hub: providers publish plans priced per billing period; subscribers pay per period with deterministic renewal, grace, expiration and cancellation. Entitlement is exactly `height < paidThrough` and nothing else -- status does not enter into it, so a cancelled subscriber keeps what they already paid for. Renewal extends FROM paidThrough rather than from the current height, so period boundaries are fixed at Subscribe time and never drift; the renewal window's early bound caps prepayment at one unstarted period, which makes an accidental duplicate payment structurally impossible rather than merely unlikely, and its late bound is the grace window. The renewable and expirable height sets partition exactly. Payments settle immediately through the same feeledger the sibling realms use (price - fee to the provider's claimable balance, fee to the protocol pot), so there is no escrow and H == U + F at all times. Entitled/EntitledFor is the one-call integration surface this realm exists to provide. A BYTE-IDENTICAL port of the pearl-1 application: zero source changes, so pearl's audit transfers to this deployment.
One description per chain, because this application is deployed on more than one chain and each account was written for its own deployment. The programs differ only where the chain's namespace appears in the source — proved, not asserted, under Deployments below — so where the accounts differ they differ in emphasis and in which chain's history they describe, not in what the code does.
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
on gno.land mainnet
gnokey query vm/qeval \
--data 'gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/subscriptions.FeeBps()' \
--remote https://rpc.gno.land:443on onyx-1 testnet
gnokey query vm/qeval \
--data 'gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/subscriptions.FeeBps()' \
--remote https://rpc.onyx.testnets.gno.land:443FeeBps 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 14 crossing functions, with the argument names filled in.
The gno.land mainnet builder spends real GNOT. Start with the read above, and size -gas-fee from a simulation rather than from any example.
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
on gno.land mainnet
import "gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/subscriptions"on onyx-1 testnet
import "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/subscriptions"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.
Called from real code
Not an example written for this page. These lines are quoted from revshare, a deployed package in this portfolio that imports this one as subs, out of the committed file whose sha256 gno.land mainnet attested. They compile because they are compiled.
id := subs.CreatePlan(cross(cur), title, description, price, periodBlocks, maxFeeBps)subs.RetirePlan(cross(cur), planID)subs.ClaimAll(cross(cur))Deployments
Deployed on 2 chains, and the bytes are NOT identical — each block below carries its own digest. The difference is the namespace: an import path contains the address that deployed the package it names, so the same application built for two chains differs on the 2 lines where that address appears, and nowhere else. The build proves that rather than asserting it — it substitutes each chain's own namespace out of its committed source and requires what remains to be byte-equal, and refuses to publish a shared page if it is not. Differing lines: 121, 122.
gno.land mainnet gnoland-1
RenderedSourceCall builderState
chain-attested| Import path | gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/subscriptions |
|---|---|
| Namespace | g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39 |
| Deployed at height | 336,731 |
| Deploy transaction | b4292a90f6d00c756679f2069bd07c070b4610ae281790904e59cfc20429b994 look it up on the RPC |
| Deployer | g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39 |
| Gas used | 75,577,311 |
| Storage | not attested per package on this chain — see the limitations below |
| Files on chain | gnomod.toml subscriptions.gno subscriptions_test.gno |
| Deployed bytes | subscriptions.gno — 28,949 bytes |
| sha256 | 19ce83e647b3741a1c2d8ef5f6fe7c0897b2023b323d7acd05e2e2e9a20c0cae |
| Realm address | g1df8a5dscun654sk2astsqejdrec4ldk3y95pxk derived, never confirmed against the realm |
Do not take the hash above on trust. $download returns the bytes gnoland-1 is actually running; this command fetches them and prints their digest, which should equal the one in the table:
curl -sS 'https://gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/subscriptions$download&file=subscriptions.gno' | shasum -a 256Expected: 19ce83e647b3741a1c2d8ef5f6fe7c0897b2023b323d7acd05e2e2e9a20c0cae — 28,949 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.
onyx-1 testnet onyx-1
RenderedSourceCall builderState
chain-attested| Import path | gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/subscriptions |
|---|---|
| Namespace | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Deployed at height | 169,037 |
| Deploy transaction | 10f921f7fb6ce0ed2d78d80529fb4b4ae20d7a7689471c31b82a8573ee8c0682 look it up on the RPC |
| Deployer | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Gas used | 39,510,826 |
| Storage | not attested per package on this chain — see the limitations below |
| Files on chain | gnomod.toml subscriptions.gno |
| Deployed bytes | subscriptions.gno — 28,949 bytes |
| sha256 | 61192802637fb55f5bb8293234c0e753c9fa1844c7ab5ed8c14a3c95772339d3 |
| Realm address | g1fpqpq59lgy57w6083ryd56utc4ksvdhw38lezl confirmed against the realm itself |
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/subscriptions$download&file=subscriptions.gno' | shasum -a 256Expected: 61192802637fb55f5bb8293234c0e753c9fa1844c7ab5ed8c14a3c95772339d3 — 28,949 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.
API
chain-derived 28 exported functions.
Read from vm/qdoc on gnoland-1. It describes the onyx-1 testnet deployment as well: the bytes are NOT identical, and the reason they still answer for each other is narrower and worth stating. The 2 deployments differ only where the chain's namespace appears in an import path, which this build proves by substitution; and none of the differing lines declares an exported symbol, which it checks separately — refusing outright where the namespace reaches a signature or a symbol's name, and disclosing each chain's own value where it reaches an exported constant. On this package it does neither. An import path is not part of a package's public surface, so the signatures, types and doc comments below are the same on both — but the digests above are not, and each chain's own is printed with it. The API is the only thing that carries across; every provenance fact above is per-chain.
Every function below deep-links to gnoweb's call builder, which generates a ready-to-run gnokey maketx call for it — against the chain you pick. The two are separate deployments with separate state, and a call to one does nothing to the other:
gno.land mainnet gnoland-1
AcceptAdminActiveSubIDBalanceOfCancelClaimClaimAllCreatePlanEntitledEntitledForExpireFeeBpsFeesAccruedHeldLiabilitiesNumPlansNumSubsPaidThroughRenderRenewRenewableFromRetirePlanSetFeeBpsSetFeeRecipientSubscribeSweepDenomTransferAdminUsersTotalWithdrawFees
onyx-1 testnet onyx-1
AcceptAdminActiveSubIDBalanceOfCancelClaimClaimAllCreatePlanEntitledEntitledForExpireFeeBpsFeesAccruedHeldLiabilitiesNumPlansNumSubsPaidThroughRenderRenewRenewableFromRetirePlanSetFeeBpsSetFeeRecipientSubscribeSweepDenomTransferAdminUsersTotalWithdrawFees
Overview
Package subscriptions is a multi-provider subscription hub: providers publish plans priced per billing period; subscribers pay per period, with deterministic renewal, grace, expiration and cancellation rules; other realms and off-chain services gate access on Entitled / EntitledFor, which is the integration surface this realm exists to provide.
THE BILLING MODEL, COMPLETELY:
Subscribe (payable, exact price) : creates the subscription, pays period 1. paidThrough = height
- periodBlocks.
Renew (payable, exact price) : extends paidThrough by exactly one periodBlocks, FROM paidThrough — never from the current height — so period boundaries are fixed at Subscribe time and never drift. Entitlement : height < paidThrough. Nothing else. Status does not enter into it: a cancelled subscription stays entitled to what it already paid for. Renewal window : a renewal is accepted iff BOTH paidThrough - height <= periodBlocks (early bound) height < paidThrough + periodBlocks (late bound) The early bound caps prepayment at one full unstarted period — a second Renew straight after a first is refused, which is what makes an accidental duplicate payment structurally impossible rather than merely unlikely. The late bound is the grace window: renewing after lapse extends from paidThrough, so it back-pays the lapsed span to keep the original schedule and buys paidThrough + periodBlocks
- height further blocks — always
at least one, because the bound is exclusive (audit finding Y2). A lapsed subscriber who prefers a fresh full period may Cancel and Subscribe again at the same total price; Subscribe's refusal message states both options. Expire (permissionless valve) : once height >= paidThrough + periodBlocks, anyone may mark the subscription Expired. No funds move — every payment settled when it was made. The valve exists so the plan|subscriber slot frees without depending on either party, and Subscribe itself collapses an expired incumbent, so a fresh start never depends on housekeeping having run. The renewable and expirable height sets partition exactly: no height is in both or neither. Cancel (subscriber only) : Active -> Cancelled. Terminal. No refund — payments settle to the provider at payment time, and what was bought (entitlement through paidThrough) stays bought. What cancellation ends is the OBLIGATION: a Cancelled subscription can never be renewed, by the subscriber or anyone else. RetirePlan (provider only) : no new Subscribes, no renewals. Existing entitlements run to paidThrough untouched. Refusing renewals on a retired plan is subscriber protection: nobody can keep paying for a service whose provider announced its end.
The obligation is therefore explicit on chain at every moment: a subscription owes nothing (there is no pull payment and no debt — a lapse simply ends entitlement), and the realm owes the subscriber exactly `paidThrough - height` blocks of entitlement, queryable by anyone via PaidThrough / Entitled / EntitledFor.
WHO PAYS WHOM. Payments settle immediately: price - fee is credited to the provider's claimable balance, fee to the protocol pot, both inside the same feeledger the sibling realms use. There is no escrow: H == U + F at all times (plus out-of-band surplus, recoverable by SweepDenom above the Liabilities reserve). The renewal caller must be the subscriber — a third party cannot extend someone else's subscription, which closes both a consent problem (an unwanted gift re-arms a lapsing obligation) and a griefing edge (spending pennies to keep a victim's slot occupied).
FEES follow the house pattern exactly: a compile-time MaxFeeBps ceiling, the current fee snapshotted into the PLAN at CreatePlan (provider consents via its own maxFeeBps argument), copied into the subscription at Subscribe, and charged at every payment from the PROVIDER's side. A later SetFeeBps touches only plans created afterwards; no existing plan or subscription can have its fee moved by anyone.
REALM-CALLER CAVEAT, inherited from the siblings verbatim: coinio's receipt guard admits only EOA payers, so subscribers 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 (see RegisterService's caveat in service_market — the same three obligations apply).
Imports
chainchain/runtimechain/runtime/unsafegno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coiniogno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledgergno.land/p/nt/avl/v0gno.land/p/nt/markdown/sanitize/v0strconv
Constants and variables
Plan status values.
const (
PlanActive = "active"
PlanRetired = "retired"
)
Subscription status values. Cancelled and Expired are terminal.
const (
SubActive = "active"
SubCancelled = "cancelled"
SubExpired = "expired"
)
Input bounds.
const (
MaxTitleLen = 80
MaxDescLen = 2000
MinPrice = int64(1)
// MinPeriodBlocks/MaxPeriodBlocks bound a plan's billing period:
// ~40 seconds to ~1 year at pearl's observed ~4.2s blocks. The
// floor keeps a hostile plan from turning renewal into a
// per-block treadmill; the ceiling keeps paidThrough arithmetic
// far from overflow even at maximum prepayment.
MinPeriodBlocks = int64(10)
MaxPeriodBlocks = int64(7500000)
// MaxPlansPerProvider bounds catalog monopolization by a single
// address — the finding that was RED in permission_registry and
// service_registry, carried from the start here.
MaxPlansPerProvider = 20
// MaxSubsPerSubscriber bounds one account's open-subscription
// state. Terminal subscriptions free their slot.
MaxSubsPerSubscriber = 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 asAcceptAdmin(cross(cur), ...).
AcceptAdmin completes the handover; only the staged successor may.
ActiveSubID
func ActiveSubID(planID int64, subscriber address) (int64, bool)
ActiveSubID returns the caller-facing id of subscriber's live subscription to planID, or (0, false) if none is Active.
BalanceOf
func BalanceOf(a address) int64
Cancel
func Cancel(cur realm, subID int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asCancel(cross(cur), ...).
Cancel ends the caller's own subscription. Terminal: it can never be renewed afterwards, by anyone. No refund and no funds move — every payment settled when it was made, and the entitlement already bought (height < paidThrough) remains until it runs out. The plan slot and the subscriber's quota slot free immediately.
Claim
func Claim(cur realm, amount int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asClaim(cross(cur), ...).
Claim sends amount ugnot of the caller's claimable balance back to the caller. Providers earn into this balance at every payment.
ClaimAll
func ClaimAll(cur realm)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asClaimAll(cross(cur), ...).
ClaimAll sends the caller's entire claimable balance back to the caller. Fails if there is nothing to claim.
CreatePlan
func CreatePlan(cur realm, title, description string, price, periodBlocks, maxFeeBps int64) int64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asCreatePlan(cross(cur), ...).
CreatePlan publishes a subscription plan and returns its id. No coins are accepted; the storage deposit the caller pays is the anti-spam. The current protocol fee is snapshotted into the plan and must not exceed maxFeeBps, the ceiling the provider signed for — pass MaxFeeBps to accept any legal fee. Plans are immutable once created: price and period changes are a new plan, so nothing a subscriber agreed to can move underneath them.
Entitled
func Entitled(subID int64) bool
Entitled reports whether the subscription's paid entitlement covers the current block: height < paidThrough. Status deliberately does not enter into it — a cancelled subscriber keeps what they paid for, and an expirable-but-unexpired one has already lapsed here.
EntitledFor
func EntitledFor(planID int64, subscriber address) bool
EntitledFor reports whether subscriber currently holds paid entitlement to planID, through their MOST RECENT subscription to it. This is the one-call integration surface for other realms and services, and it honors the entitlement contract across status: a cancelled subscription keeps answering true until its paidThrough — what was bought stays bought (audit finding Y1). One self-inflicted edge is fail-closed: cancelling a prepaid subscription and re-subscribing at once points this surface at the NEW, earlier paidThrough; the old subscription's remaining span stays queryable per-id via Entitled.
Expire
func Expire(cur realm, subID int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asExpire(cross(cur), ...).
Expire marks a lapsed subscription Expired once its grace window is over: height >= paidThrough + periodBlocks. Permissionless by design — like the sibling realms' valves, no slot's liveness may depend on either party showing up. No funds move.
FeeBps
func FeeBps() int64
FeesAccrued
func FeesAccrued() int64
Held
func Held() int64
Liabilities
func Liabilities() int64
NumPlans
func NumPlans() int64
NumSubs
func NumSubs() int64
PaidThrough
func PaidThrough(subID int64) int64
PaidThrough returns the absolute height a subscription is paid to.
Render
func Render(path string) string
Renew
func Renew(cur realm, subID int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asRenew(cross(cur), ...).
Renew pays for the next billing period of the caller's own subscription. The transaction must attach exactly the subscription's price. The renewal window is deterministic and stated in the header: accepted iff paidThrough - height <= periodBlocks (at most one full unstarted period prepaid — the duplicate-payment bound) and height < paidThrough + periodBlocks (the grace bound, exclusive — a renewal always buys at least one block). Extension is always FROM paidThrough, so period boundaries never drift, and a renewal inside grace covers the lapsed span — that is the price of keeping the original schedule, and it is the documented, deterministic choice.
RenewableFrom
func RenewableFrom(subID int64) (from, until int64)
RenewableFrom returns the earliest height at which Renew will accept a payment for this subscription, and the last height at which it still will (inclusive) — the deterministic window, precomputed for integrators. From until+1 the subscription is expirable instead; the two sets partition exactly.
RetirePlan
func RetirePlan(cur realm, planID int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asRetirePlan(cross(cur), ...).
RetirePlan takes a plan off the market: no new subscriptions and no renewals. Provider only. Existing entitlements run to their paidThrough untouched; refusing renewals is subscriber protection — nobody keeps paying for a service whose provider announced its end. The provider's plan-quota slot frees.
SetFeeBps
func SetFeeBps(cur realm, bps int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSetFeeBps(cross(cur), ...).
SetFeeBps sets the protocol fee snapshotted into FUTURE plans. Bounded by MaxFeeBps; existing plans and subscriptions are untouched — their fee was fixed the moment the provider consented to it.
SetFeeRecipient
func SetFeeRecipient(cur realm, recipient address)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSetFeeRecipient(cross(cur), ...).
SetFeeRecipient points future fee withdrawals and sweeps at a new address. Admin only. The zero address is refused — it would strand the pot.
Subscribe
func Subscribe(cur realm, planID int64) int64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSubscribe(cross(cur), ...).
Subscribe pays for the first billing period of a plan and returns the new subscription id. The transaction must attach EXACTLY the plan's price in ugnot — over- and underpayment are both refused, so a mistaken double-attach cannot silently become a donation. One subscriber holds at most one live subscription per plan: if an Active one exists the call is refused (the renewal path is Renew, never a second Subscribe — that is the duplicate-payment guard at the identity level); an incumbent past its grace window is collapsed to Expired in place, so a fresh start never waits on housekeeping.
The payment settles immediately: price minus the plan's snapshotted fee to the provider's claimable balance, fee to the protocol pot. Entitlement runs from this block: paidThrough = height + periodBlocks.
SweepDenom
func SweepDenom(cur realm, denom string)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSweepDenom(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 asTransferAdmin(cross(cur), ...).
TransferAdmin stages a two-step admin handover. The successor holds nothing until AcceptAdmin.
UsersTotal
func UsersTotal() int64
WithdrawFees
func WithdrawFees(cur realm)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asWithdrawFees(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
A first-party dependency is imported from the SAME chain's namespace, so each deployment above depends on that chain's copy — the paths differ even where the code does not.
chain-attested| Imports | gnoland-1: chain, chain/runtime, chain/runtime/unsafe, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, strconvonyx-1: chain, chain/runtime, chain/runtime/unsafe, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, strconv |
|---|---|
| First-party dependencies | gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger |
Known limitations
Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.
curatedgno.land mainnet
- No state-changing call was made against this realm on gnoland-1. The live evidence is byte custody plus a successful vm/qrender; every state transition rests on the committed test suite and the pearl-1 audit, not on mainnet behaviour ([an unpublished record], Live verification).
- storage_bytes and storage_deposit are null because gnoland-1 emits no /tm.StorageDepositEvent ON ADDPKG -- a scope that is load-bearing, because it DOES emit one on MsgCall (measured 2026-10-17, AL62); see the chain's storage_deposit_events field. The deposit actually charged was 5,270,600 ugnot, measured from the signer's balance and recorded in [an unpublished record], Cost; it is not chain-attested per package by this manifest.
- The deployed file set includes subscriptions_test.gno. The pearl-1 deployment of the same realm carried only the source and gnomod.toml, so the mainnet package is three files where pearl's is two -- the test file is stored on chain and paid for in the deposit. It declares no crossing function and so adds no callable surface, but it is part of the immutable package.
- The fee path's live success legs (SetFeeBps, accrual, WithdrawFees payout) were not exercised on chain: the live run executed at the deployed 0-bps default after a harness permission denial, reported rather than routed around; unit tests cover snapshot immutability, consent ceiling, accrual and withdrawal (DEPLOYMENT.md section 17).
- SweepDenom's success leg, Subscribe's expired-incumbent collapse, the admin two-step handover and both quota ceilings are unit-tested but were not exercised live; each is correlated to its committed test in the deployment record.
- latestByKey entries are permanent tombstone-class state, one per (plan, subscriber) pair ever, self-paid via storage deposits.
onyx-1 testnet
- The live lifecycle WAS exercised on 2026-10-04 -- 8 transactions at h169711-h170159 and 6 adversarial refusals, recorded in [an unpublished record] as Amendment 1 to Group 7. Measured on chain: a nonzero 1% fee splitting exactly (H == U + F as 300,000 == 297,000 + 3,000, the leg pearl-1 never ran), the grace-branch renewal extending from paidThrough (169726+10 = 169736) and not from height (169740), permissionless Expire by a non-subscriber flipping Entitled to false, a plan keeping its 100-bps snapshot after the admin set the realm fee back to 0, a refusal raised inside the composed feeledger package rather than by the realm, and a drain to Held() == 0 with 600,000 in == 594,000 + 6,000 out. STILL TESTS-ONLY: Cancel (mutually exclusive with the Expire that was chosen), long-period arithmetic (a 10-block MinPeriodBlocks plan was used so grace and expiry fit in one session), multi-subscriber interleaving, and every aggregate bound. ALSO NOT ADVERSARIALLY SEPARATED: provider, admin and fee recipient are one address on onyx, so ClaimAll and WithdrawFees succeeding is the happy path -- the access-control evidence is funder1 being refused both.
- subscriptions.gno:153 reads '~40 seconds to ~1 year at pearl's observed ~4.2s blocks' -- a pearl-era block-time reference in a package now live on onyx-1. KEPT UNEDITED on purpose: byte-identity to the audited pearl source is what transfers that audit to this deployment, and editing even a comment would have made this a divergent port requiring its own DIVERGENCE.json. The numeric bounds themselves (MinPeriodBlocks 10, MaxPeriodBlocks 7,500,000) are block counts and are unaffected by block time; only the human-readable gloss is stale.
- MaxPlansPerProvider is 20 and MaxSubsPerSubscriber is 100 -- per-address bounds, not global ones, so the plan catalog and the subscription table are both unbounded in aggregate. Render is capped at RenderLimit 20 newest plans.
Source
| Source file (gnoland-1) | mainnet/r/subscriptions/subscriptions.gno at commit c2e10d17ffbf in the project repository (not public — the digest command above is the check that needs no repository) |
|---|---|
| Matches the deployed bytes (gnoland-1) | yes — byte-identical |
| Source file (onyx-1) | onyx/r/subscriptions/subscriptions.gno at commit c2e10d17ffbf in the project repository (not public — the digest command above is the check that needs no repository) |
| Matches the deployed bytes (onyx-1) | yes — byte-identical |