Cosmic Bull

Realm on pearl-1

subscriptions

gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/subscriptions

realmfactory-applicationbilling

RenderedSourceCall builderState

curated

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.

Identity

Import pathgno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/subscriptions
Kindrealm (/r/)
Chainpearl-1
Namespaceg1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3
Realm addressg1fpqpq59lgy57w6083ryd56utc4ksvdhw38lezl confirmed against the realm itself

Provenance

chain-attested
Deployed at height639,086
Deploy transaction60c23cd2d863e0e49553f936c6efcc52b62aa4b50f452b5feea4768528fd0cad look it up on the RPC
Deployerg1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3
Gas used49,524,549
Storage52,706 bytes, deposit 5270600ugnot
Files on chaingnomod.toml subscriptions.gno
Deployed bytessubscriptions.gno — 28,949 bytes
sha25661192802637fb55f5bb8293234c0e753c9fa1844c7ab5ed8c14a3c95772339d3

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

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

Expected: 61192802637fb55f5bb8293234c0e753c9fa1844c7ab5ed8c14a3c95772339d3 — 28,949 bytes. This was checked for all 21 packages while building this site's architecture record; every one matched. Use curl: pearl's edge answers Python's default user-agent with HTTP 403.

API

chain-derived 28 exported functions.

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

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

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

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

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 as AcceptAdmin(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 as Cancel(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 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 payment.

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.

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 as CreatePlan(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 as Expire(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 as Renew(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 as RetirePlan(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 as SetFeeBps(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 as SetFeeRecipient(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 as Subscribe(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 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 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 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/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, strconv
First-party dependenciesgno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger
Used bynone

Known limitations

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

curated

Source and records

Source filepearl/r/subscriptions/subscriptions.gno at commit 6a510c665a53 in the project repository (not public — the digest command above is the check that needs no repository)
Matches the deployed bytesyes — byte-identical
Recordscatalog/applications.md#subscriptions
pearl/DEPLOYMENT.md