PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/subscriptions

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
subscriptions
Namespace
g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39
Files
3 (gnomod.toml)
Exported functions
34
Module
gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/subscriptions
gno
0.9

Files (3)

  • gnomod.tomltoml
  • subscriptions.gnogno
  • subscriptions_test.gnogno
subscriptions.gnogno
1// Package subscriptions is a multi-provider subscription hub: providers2// publish plans priced per billing period; subscribers pay per period,3// with deterministic renewal, grace, expiration and cancellation rules;4// other realms and off-chain services gate access on Entitled /5// EntitledFor, which is the integration surface this realm exists to6// provide.7//8// THE BILLING MODEL, COMPLETELY:9//10//	Subscribe (payable, exact price)  : creates the subscription, pays11//	                                    period 1. paidThrough = height12//	                                    + periodBlocks.13//	Renew (payable, exact price)      : extends paidThrough by exactly14//	                                    one periodBlocks, FROM15//	                                    paidThrough — never from the16//	                                    current height — so period17//	                                    boundaries are fixed at18//	                                    Subscribe time and never drift.19//	Entitlement                       : height < paidThrough. Nothing20//	                                    else. Status does not enter21//	                                    into it: a cancelled22//	                                    subscription stays entitled to23//	                                    what it already paid for.24//	Renewal window                    : a renewal is accepted iff BOTH25//	                    paidThrough - height <= periodBlocks   (early bound)26//	                    height < paidThrough + periodBlocks    (late bound)27//	                                    The early bound caps prepayment28//	                                    at one full unstarted period —29//	                                    a second Renew straight after a30//	                                    first is refused, which is what31//	                                    makes an accidental duplicate32//	                                    payment structurally impossible33//	                                    rather than merely unlikely.34//	                                    The late bound is the grace35//	                                    window: renewing after lapse36//	                                    extends from paidThrough, so it37//	                                    back-pays the lapsed span to38//	                                    keep the original schedule and39//	                                    buys paidThrough + periodBlocks40//	                                    - height further blocks — always41//	                                    at least one, because the bound42//	                                    is exclusive (audit finding Y2).43//	                                    A lapsed subscriber who prefers44//	                                    a fresh full period may Cancel45//	                                    and Subscribe again at the same46//	                                    total price; Subscribe's refusal47//	                                    message states both options.48//	Expire (permissionless valve)     : once height >= paidThrough +49//	                                    periodBlocks, anyone may mark50//	                                    the subscription Expired. No51//	                                    funds move — every payment52//	                                    settled when it was made. The53//	                                    valve exists so the54//	                                    plan|subscriber slot frees55//	                                    without depending on either56//	                                    party, and Subscribe itself57//	                                    collapses an expired incumbent,58//	                                    so a fresh start never depends59//	                                    on housekeeping having run. The60//	                                    renewable and expirable height61//	                                    sets partition exactly: no62//	                                    height is in both or neither.63//	Cancel (subscriber only)          : Active -> Cancelled. Terminal.64//	                                    No refund — payments settle to65//	                                    the provider at payment time,66//	                                    and what was bought (entitlement67//	                                    through paidThrough) stays68//	                                    bought. What cancellation ends69//	                                    is the OBLIGATION: a Cancelled70//	                                    subscription can never be71//	                                    renewed, by the subscriber or72//	                                    anyone else.73//	RetirePlan (provider only)        : no new Subscribes, no renewals.74//	                                    Existing entitlements run to75//	                                    paidThrough untouched. Refusing76//	                                    renewals on a retired plan is77//	                                    subscriber protection: nobody78//	                                    can keep paying for a service79//	                                    whose provider announced its80//	                                    end.81//82// The obligation is therefore explicit on chain at every moment: a83// subscription owes nothing (there is no pull payment and no debt — a84// lapse simply ends entitlement), and the realm owes the subscriber85// exactly `paidThrough - height` blocks of entitlement, queryable by86// anyone via PaidThrough / Entitled / EntitledFor.87//88// WHO PAYS WHOM. Payments settle immediately: price - fee is credited89// to the provider's claimable balance, fee to the protocol pot, both90// inside the same feeledger the sibling realms use. There is no escrow:91// H == U + F at all times (plus out-of-band surplus, recoverable by92// SweepDenom above the Liabilities reserve). The renewal caller must be93// the subscriber — a third party cannot extend someone else's94// subscription, which closes both a consent problem (an unwanted gift95// re-arms a lapsing obligation) and a griefing edge (spending pennies96// to keep a victim's slot occupied).97//98// FEES follow the house pattern exactly: a compile-time MaxFeeBps99// ceiling, the current fee snapshotted into the PLAN at CreatePlan100// (provider consents via its own maxFeeBps argument), copied into the101// subscription at Subscribe, and charged at every payment from the102// PROVIDER's side. A later SetFeeBps touches only plans created103// afterwards; no existing plan or subscription can have its fee moved104// by anyone.105//106// REALM-CALLER CAVEAT, inherited from the siblings verbatim: coinio's107// receipt guard admits only EOA payers, so subscribers are EOAs;108// assertNoSend reads the ORIGIN envelope, so every non-payable function109// refuses any transaction that attached coins anywhere. Providers may110// be EOAs or realms, but a realm provider must expose its own crossing111// path to Claim, or what it earns is stranded (see RegisterService's112// caveat in service_market — the same three obligations apply).113package subscriptions114115import (116	"chain"117	"chain/runtime"118	"chain/runtime/unsafe"119	"strconv"120121	"gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio"122	"gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger"123	"gno.land/p/nt/avl/v0"124	"gno.land/p/nt/markdown/sanitize/v0"125)126127// Denom is the only asset this realm accepts.128const Denom = "ugnot"129130// MaxFeeBps is the hard protocol-fee cap: 1000 bps = 10%.131const MaxFeeBps = int64(1000)132133// Plan status values.134const (135	PlanActive  = "active"136	PlanRetired = "retired"137)138139// Subscription status values. Cancelled and Expired are terminal.140const (141	SubActive    = "active"142	SubCancelled = "cancelled"143	SubExpired   = "expired"144)145146// Input bounds.147const (148	MaxTitleLen = 80149	MaxDescLen  = 2000150	MinPrice    = int64(1)151152	// MinPeriodBlocks/MaxPeriodBlocks bound a plan's billing period:153	// ~40 seconds to ~1 year at pearl's observed ~4.2s blocks. The154	// floor keeps a hostile plan from turning renewal into a155	// per-block treadmill; the ceiling keeps paidThrough arithmetic156	// far from overflow even at maximum prepayment.157	MinPeriodBlocks = int64(10)158	MaxPeriodBlocks = int64(7500000)159160	// MaxPlansPerProvider bounds catalog monopolization by a single161	// address — the finding that was RED in permission_registry and162	// service_registry, carried from the start here.163	MaxPlansPerProvider = 20164165	// MaxSubsPerSubscriber bounds one account's open-subscription166	// state. Terminal subscriptions free their slot.167	MaxSubsPerSubscriber = 100168169	// RenderLimit bounds every rendered list — an unbounded Render170	// was YELLOW in three prior audits.171	RenderLimit = 20172)173174type plan struct {175	id           int64176	provider     address177	title        string178	description  string179	price        int64180	periodBlocks int64181	feeBps       int64 // snapshotted at creation, charged at every payment182	status       string183	subs         int64 // lifetime count, never decremented184}185186type sub struct {187	id           int64188	planID       int64189	subscriber   address190	provider     address // copied at Subscribe; never re-read from the plan191	price        int64   // copied at Subscribe; a plan is immutable anyway192	periodBlocks int64   // copied at Subscribe193	feeBps       int64   // copied at Subscribe from the plan's snapshot194	status       string195	paidThrough  int64 // absolute height; entitlement = height < paidThrough196	periods      int64 // lifetime paid-period count197}198199var (200	admin        address // may set fee, fee recipient, stage successor201	pendingAdmin address // staged by TransferAdmin, completes via AcceptAdmin202	feeRecipient address // may withdraw fees and sweep surplus203	feeBps       int64   // fee snapshotted into NEW plans204205	self     address // this realm's address, captured at deploy206	nextPlan int64207	nextSub  int64208209	plans       = avl.NewTree() // padID(id)                 -> *plan210	subs        = avl.NewTree() // padID(id)                 -> *sub211	activeByKey = avl.NewTree() // padID(planID)|subscriber  -> int64 sub id, while Active212	latestByKey = avl.NewTree() // padID(planID)|subscriber  -> most recent sub id, never removed213	providerNum = avl.NewTree() // address -> *int64, live plans per provider214	subNum      = avl.NewTree() // address -> *int64, Active subs per subscriber215	ledger      = feeledger.MustNew(MaxFeeBps)216)217218func init() {219	admin = unsafe.OriginCaller()220	feeRecipient = admin221	self = unsafe.CurrentRealm().Address()222}223224// --- provider side ---225226// CreatePlan publishes a subscription plan and returns its id. No coins227// are accepted; the storage deposit the caller pays is the anti-spam.228// The current protocol fee is snapshotted into the plan and must not229// exceed maxFeeBps, the ceiling the provider signed for — pass230// MaxFeeBps to accept any legal fee. Plans are immutable once created:231// price and period changes are a new plan, so nothing a subscriber232// agreed to can move underneath them.233func CreatePlan(cur realm, title, description string, price, periodBlocks, maxFeeBps int64) int64 {234	assertNoSend()235	provider := cur.Previous().Address()236237	if title == "" || len(title) > MaxTitleLen {238		panic("title must be 1.." + itoa(int64(MaxTitleLen)) + " bytes")239	}240	if len(description) > MaxDescLen {241		panic("description too long")242	}243	if price < MinPrice {244		panic("price must be at least " + itoa(MinPrice) + Denom)245	}246	if periodBlocks < MinPeriodBlocks || periodBlocks > MaxPeriodBlocks {247		panic("periodBlocks must be in [" + itoa(MinPeriodBlocks) + ", " +248			itoa(MaxPeriodBlocks) + "]")249	}250	if feeBps > maxFeeBps {251		panic("current fee " + itoa(feeBps) + " bps exceeds the caller's maximum " +252			itoa(maxFeeBps))253	}254	n := counter(providerNum, provider)255	if *n >= MaxPlansPerProvider {256		panic("per-provider plan limit reached")257	}258259	id := nextPlan260	nextPlan++261	plans.Set(padID(id), &plan{262		id:           id,263		provider:     provider,264		title:        title,265		description:  description,266		price:        price,267		periodBlocks: periodBlocks,268		feeBps:       feeBps,269		status:       PlanActive,270	})271	*n++272273	chain.Emit("PlanCreated",274		"planId", itoa(id),275		"provider", provider.String(),276		"price", itoa(price),277		"periodBlocks", itoa(periodBlocks),278		"feeBps", itoa(feeBps),279	)280	return id281}282283// RetirePlan takes a plan off the market: no new subscriptions and no284// renewals. Provider only. Existing entitlements run to their285// paidThrough untouched; refusing renewals is subscriber protection —286// nobody keeps paying for a service whose provider announced its end.287// The provider's plan-quota slot frees.288func RetirePlan(cur realm, planID int64) {289	assertNoSend()290	caller := cur.Previous().Address()291	p := mustGetPlan(planID)292	if caller != p.provider {293		panic("only the provider may retire a plan")294	}295	if p.status != PlanActive {296		panic("plan is not active")297	}298	p.status = PlanRetired299	*counter(providerNum, p.provider)--300	chain.Emit("PlanRetired", "planId", itoa(planID), "provider", p.provider.String())301}302303// --- subscriber side ---304305// Subscribe pays for the first billing period of a plan and returns the306// new subscription id. The transaction must attach EXACTLY the plan's307// price in ugnot — over- and underpayment are both refused, so a308// mistaken double-attach cannot silently become a donation. One309// subscriber holds at most one live subscription per plan: if an Active310// one exists the call is refused (the renewal path is Renew, never a311// second Subscribe — that is the duplicate-payment guard at the312// identity level); an incumbent past its grace window is collapsed to313// Expired in place, so a fresh start never waits on housekeeping.314//315// The payment settles immediately: price minus the plan's snapshotted316// fee to the provider's claimable balance, fee to the protocol pot.317// Entitlement runs from this block: paidThrough = height + periodBlocks.318func Subscribe(cur realm, planID int64) int64 {319	subscriber, amount := coinio.Receive(0, cur, Denom)320	p := mustGetPlan(planID)321	if p.status != PlanActive {322		panic("plan is not active")323	}324	if amount != p.price {325		panic("send exactly the plan price: " + itoa(p.price) + Denom)326	}327328	key := padID(planID) + "|" + subscriber.String()329	if v := activeByKey.Get(key); v != nil {330		inc := mustGetSub(v.(int64))331		if runtime.ChainHeight() >= inc.paidThrough+inc.periodBlocks {332			expireInPlace(inc)333		} else if runtime.ChainHeight() >= inc.paidThrough {334			panic("subscription " + itoa(inc.id) + " to this plan is lapsed but renewable: " +335				"Renew back-pays the lapsed span and keeps the schedule; " +336				"Cancel then Subscribe restarts fresh at the same price")337		} else {338			panic("an active subscription to this plan already exists: renew it instead")339		}340	}341	n := counter(subNum, subscriber)342	if *n >= MaxSubsPerSubscriber {343		panic("per-subscriber subscription limit reached")344	}345346	ledger.MustDeposit(p.provider.String(), amount, p.feeBps)347348	id := nextSub349	nextSub++350	subs.Set(padID(id), &sub{351		id:           id,352		planID:       planID,353		subscriber:   subscriber,354		provider:     p.provider,355		price:        p.price,356		periodBlocks: p.periodBlocks,357		feeBps:       p.feeBps,358		status:       SubActive,359		paidThrough:  runtime.ChainHeight() + p.periodBlocks,360		periods:      1,361	})362	activeByKey.Set(key, id)363	latestByKey.Set(key, id)364	*n++365	p.subs++366367	chain.Emit("Subscribed",368		"subId", itoa(id),369		"planId", itoa(planID),370		"subscriber", subscriber.String(),371		"provider", p.provider.String(),372		"amount", itoa(amount),373		"paidThrough", itoa(runtime.ChainHeight()+p.periodBlocks),374	)375	return id376}377378// Renew pays for the next billing period of the caller's own379// subscription. The transaction must attach exactly the subscription's380// price. The renewal window is deterministic and stated in the header:381// accepted iff paidThrough - height <= periodBlocks (at most one full382// unstarted period prepaid — the duplicate-payment bound) and height <383// paidThrough + periodBlocks (the grace bound, exclusive — a renewal384// always buys at least one block). Extension is always385// FROM paidThrough, so period boundaries never drift, and a renewal386// inside grace covers the lapsed span — that is the price of keeping387// the original schedule, and it is the documented, deterministic388// choice.389func Renew(cur realm, subID int64) {390	payer, amount := coinio.Receive(0, cur, Denom)391	s := mustGetSub(subID)392	if payer != s.subscriber {393		panic("only the subscriber may renew")394	}395	if s.status != SubActive {396		panic("subscription is " + s.status)397	}398	p := mustGetPlan(s.planID)399	if p.status != PlanActive {400		panic("plan is retired; the paid period runs to its end but cannot be renewed")401	}402	if amount != s.price {403		panic("send exactly the subscription price: " + itoa(s.price) + Denom)404	}405	h := runtime.ChainHeight()406	if s.paidThrough-h > s.periodBlocks {407		panic("too early: at most one unstarted period may be prepaid; renewable from height " +408			itoa(s.paidThrough-s.periodBlocks))409	}410	if h >= s.paidThrough+s.periodBlocks {411		panic("grace window over; the subscription is expirable, subscribe afresh")412	}413414	ledger.MustDeposit(s.provider.String(), amount, s.feeBps)415	s.paidThrough += s.periodBlocks416	s.periods++417418	chain.Emit("Renewed",419		"subId", itoa(subID),420		"subscriber", s.subscriber.String(),421		"amount", itoa(amount),422		"paidThrough", itoa(s.paidThrough),423	)424}425426// Cancel ends the caller's own subscription. Terminal: it can never be427// renewed afterwards, by anyone. No refund and no funds move — every428// payment settled when it was made, and the entitlement already bought429// (height < paidThrough) remains until it runs out. The plan slot and430// the subscriber's quota slot free immediately.431func Cancel(cur realm, subID int64) {432	assertNoSend()433	caller := cur.Previous().Address()434	s := mustGetSub(subID)435	if caller != s.subscriber {436		panic("only the subscriber may cancel")437	}438	if s.status != SubActive {439		panic("subscription is " + s.status)440	}441	s.status = SubCancelled442	releaseSlots(s)443	chain.Emit("Cancelled",444		"subId", itoa(subID),445		"subscriber", s.subscriber.String(),446		"paidThrough", itoa(s.paidThrough),447	)448}449450// Expire marks a lapsed subscription Expired once its grace window is451// over: height >= paidThrough + periodBlocks. Permissionless by design —452// like the sibling realms' valves, no slot's liveness may depend on453// either party showing up. No funds move.454func Expire(cur realm, subID int64) {455	assertNoSend()456	s := mustGetSub(subID)457	if s.status != SubActive {458		panic("subscription is " + s.status)459	}460	if runtime.ChainHeight() < s.paidThrough+s.periodBlocks {461		panic("not expirable: grace runs through height " +462			itoa(s.paidThrough+s.periodBlocks-1))463	}464	expireInPlace(s)465}466467// --- payouts ---468469// Claim sends amount ugnot of the caller's claimable balance back to470// the caller. Providers earn into this balance at every payment.471func Claim(cur realm, amount int64) {472	assertNoSend()473	caller := cur.Previous().Address()474	if err := ledger.Withdraw(caller.String(), amount); err != nil {475		panic(err)476	}477	coinio.Payout(0, cur, caller, Denom, amount)478	chain.Emit("Claim", "to", caller.String(), "amount", itoa(amount))479}480481// ClaimAll sends the caller's entire claimable balance back to the482// caller. Fails if there is nothing to claim.483func ClaimAll(cur realm) {484	assertNoSend()485	caller := cur.Previous().Address()486	amount, err := ledger.WithdrawAll(caller.String())487	if err != nil {488		panic(err)489	}490	if amount == 0 {491		panic("nothing to claim")492	}493	coinio.Payout(0, cur, caller, Denom, amount)494	chain.Emit("Claim", "to", caller.String(), "amount", itoa(amount))495}496497// WithdrawFees sends the accrued fee pot to the fee recipient. Only the498// fee recipient may call it, and only the pot moves.499func WithdrawFees(cur realm) {500	assertNoSend()501	if cur.Previous().Address() != feeRecipient {502		panic("fee recipient only")503	}504	amount := ledger.WithdrawFees()505	if amount == 0 {506		panic("no fees accrued")507	}508	coinio.Payout(0, cur, feeRecipient, Denom, amount)509	chain.Emit("FeesWithdrawn", "to", feeRecipient.String(), "amount", itoa(amount))510}511512// SweepDenom recovers out-of-band coins (sent by raw bank transfer,513// outside any entrypoint) to the fee recipient. For the ledger denom514// the reserve is Liabilities() — user balances and the fee pot are515// structurally unreachable. Fee recipient only.516func SweepDenom(cur realm, denom string) {517	assertNoSend()518	if cur.Previous().Address() != feeRecipient {519		panic("fee recipient only")520	}521	reserve := int64(0)522	if denom == Denom {523		reserve = ledger.Liabilities()524	}525	// coinio.Sweep itself aborts when nothing sits above the reserve.526	swept := coinio.Sweep(0, cur, feeRecipient, denom, reserve)527	chain.Emit("SurplusSwept", "to", feeRecipient.String(), "coins", itoa(swept)+denom)528}529530// --- administration ---531532// SetFeeBps sets the protocol fee snapshotted into FUTURE plans.533// Bounded by MaxFeeBps; existing plans and subscriptions are untouched534// — their fee was fixed the moment the provider consented to it.535func SetFeeBps(cur realm, bps int64) {536	assertNoSend()537	assertAdmin(cur.Previous().Address())538	if bps < 0 || bps > MaxFeeBps {539		panic("fee must be in [0, " + itoa(MaxFeeBps) + "] bps")540	}541	old := feeBps542	feeBps = bps543	chain.Emit("FeeChanged", "oldBps", itoa(old), "newBps", itoa(bps))544}545546// SetFeeRecipient points future fee withdrawals and sweeps at a new547// address. Admin only. The zero address is refused — it would strand548// the pot.549func SetFeeRecipient(cur realm, recipient address) {550	assertNoSend()551	assertAdmin(cur.Previous().Address())552	var zero address553	if recipient == zero {554		panic("fee recipient must not be the zero address")555	}556	old := feeRecipient557	feeRecipient = recipient558	chain.Emit("FeeRecipientChanged", "old", old.String(), "new", recipient.String())559}560561// TransferAdmin stages a two-step admin handover. The successor holds562// nothing until AcceptAdmin.563func TransferAdmin(cur realm, successor address) {564	assertNoSend()565	assertAdmin(cur.Previous().Address())566	var zero address567	if successor == zero {568		panic("successor must not be the zero address")569	}570	pendingAdmin = successor571	chain.Emit("AdminTransferStaged", "from", admin.String(), "to", successor.String())572}573574// AcceptAdmin completes the handover; only the staged successor may.575func AcceptAdmin(cur realm) {576	assertNoSend()577	caller := cur.Previous().Address()578	if caller != pendingAdmin {579		panic("only the staged successor may accept")580	}581	old := admin582	admin = caller583	var zero address584	pendingAdmin = zero585	chain.Emit("AdminTransferred", "from", old.String(), "to", admin.String())586}587588// --- views ---589590// Entitled reports whether the subscription's paid entitlement covers591// the current block: height < paidThrough. Status deliberately does not592// enter into it — a cancelled subscriber keeps what they paid for, and593// an expirable-but-unexpired one has already lapsed here.594func Entitled(subID int64) bool {595	s := mustGetSub(subID)596	return runtime.ChainHeight() < s.paidThrough597}598599// EntitledFor reports whether subscriber currently holds paid600// entitlement to planID, through their MOST RECENT subscription to it.601// This is the one-call integration surface for other realms and602// services, and it honors the entitlement contract across status: a603// cancelled subscription keeps answering true until its paidThrough —604// what was bought stays bought (audit finding Y1). One self-inflicted605// edge is fail-closed: cancelling a prepaid subscription and606// re-subscribing at once points this surface at the NEW, earlier607// paidThrough; the old subscription's remaining span stays queryable608// per-id via Entitled.609func EntitledFor(planID int64, subscriber address) bool {610	v := latestByKey.Get(padID(planID) + "|" + subscriber.String())611	if v == nil {612		return false613	}614	return Entitled(v.(int64))615}616617// ActiveSubID returns the caller-facing id of subscriber's live618// subscription to planID, or (0, false) if none is Active.619func ActiveSubID(planID int64, subscriber address) (int64, bool) {620	v := activeByKey.Get(padID(planID) + "|" + subscriber.String())621	if v == nil {622		return 0, false623	}624	return v.(int64), true625}626627// PlanInfo returns a plan's public fields.628func PlanInfo(planID int64) (provider address, title string, price, periodBlocks, planFeeBps int64, status string, lifetimeSubs int64) {629	p := mustGetPlan(planID)630	return p.provider, p.title, p.price, p.periodBlocks, p.feeBps, p.status, p.subs631}632633// SubInfo returns a subscription's public fields.634func SubInfo(subID int64) (planID int64, subscriber, provider address, price, paidThrough, periods int64, status string) {635	s := mustGetSub(subID)636	return s.planID, s.subscriber, s.provider, s.price, s.paidThrough, s.periods, s.status637}638639// PaidThrough returns the absolute height a subscription is paid to.640func PaidThrough(subID int64) int64 { return mustGetSub(subID).paidThrough }641642// RenewableFrom returns the earliest height at which Renew will accept643// a payment for this subscription, and the last height at which it644// still will (inclusive) — the deterministic window, precomputed for645// integrators. From until+1 the subscription is expirable instead; the646// two sets partition exactly.647func RenewableFrom(subID int64) (from, until int64) {648	s := mustGetSub(subID)649	return s.paidThrough - s.periodBlocks, s.paidThrough + s.periodBlocks - 1650}651652func Admin() address            { return admin }653func PendingAdmin() address     { return pendingAdmin }654func FeeRecipient() address     { return feeRecipient }655func FeeBps() int64             { return feeBps }656func NumPlans() int64           { return nextPlan }657func NumSubs() int64            { return nextSub }658func UsersTotal() int64         { return ledger.UsersTotal() }659func FeesAccrued() int64        { return ledger.FeesAccrued() }660func Liabilities() int64        { return ledger.Liabilities() }661func BalanceOf(a address) int64 { return ledger.BalanceOf(a.String()) }662func Address() address          { return self }663func Held() int64               { return coinio.HeldAt(self, Denom) }664665// --- render ---666667func Render(path string) string {668	if path == "" {669		return renderHome()670	}671	return "unknown page; try the realm root"672}673674func renderHome() string {675	out := "# subscriptions\n\n"676	out += "Provider-published plans, per-period payment, deterministic " +677		"renewal, grace, expiration and cancellation. Entitlement is " +678		"`height < paidThrough`, queryable by anyone.\n\n"679	out += "- plans: " + itoa(nextPlan) + "\n"680	out += "- subscriptions: " + itoa(nextSub) + "\n"681	out += "- provider balances: " + itoa(ledger.UsersTotal()) + Denom + "\n"682	out += "- fees accrued: " + itoa(ledger.FeesAccrued()) + Denom + "\n"683	out += "- current fee for new plans: " + itoa(feeBps) + " bps (cap " +684		itoa(MaxFeeBps) + ")\n\n"685686	if nextPlan == 0 {687		return out + "No plans yet.\n"688	}689	out += "## newest plans\n\n"690	shown := int64(0)691	for id := nextPlan - 1; id >= 0 && shown < RenderLimit; id-- {692		p := mustGetPlan(id)693		out += "- #" + itoa(p.id) + " **" + sanitize.InlineText(p.title) +694			"** — " + itoa(p.price) + Denom + " / " + itoa(p.periodBlocks) +695			" blocks, " + p.status + ", provider `" + p.provider.String() +696			"`, " + itoa(p.subs) + " lifetime subs\n"697		shown++698	}699	if nextPlan > shown {700		out += "\n(" + itoa(nextPlan-shown) + " older plans not shown)\n"701	}702	return out703}704705// --- internals ---706707// expireInPlace flips an Active subscription to Expired and frees its708// slots. Callers have already established expirability.709func expireInPlace(s *sub) {710	s.status = SubExpired711	releaseSlots(s)712	chain.Emit("SubscriptionExpired",713		"subId", itoa(s.id),714		"subscriber", s.subscriber.String(),715		"paidThrough", itoa(s.paidThrough),716	)717}718719// releaseSlots removes the plan|subscriber activity index entry and720// decrements the subscriber's quota counter. Exactly once per terminal721// transition, which both terminal paths guarantee by requiring722// SubActive first.723func releaseSlots(s *sub) {724	activeByKey.Remove(padID(s.planID) + "|" + s.subscriber.String())725	*counter(subNum, s.subscriber)--726}727728func assertNoSend() {729	if len(unsafe.OriginSend()) != 0 {730		panic("this function does not accept coins")731	}732}733734func assertAdmin(caller address) {735	if caller != admin {736		panic("admin only")737	}738}739740func mustGetPlan(id int64) *plan {741	v := plans.Get(padID(id))742	if v == nil {743		panic("unknown plan id")744	}745	return v.(*plan)746}747748func mustGetSub(id int64) *sub {749	v := subs.Get(padID(id))750	if v == nil {751		panic("unknown subscription id")752	}753	return v.(*sub)754}755756// counter returns the persistent per-address counter in tree,757// allocating a zero on first use.758func counter(tree *avl.Tree, a address) *int64 {759	k := a.String()760	if v := tree.Get(k); v != nil {761		return v.(*int64)762	}763	n := new(int64)764	tree.Set(k, n)765	return n766}767768func padID(id int64) string {769	s := strconv.FormatInt(id, 10)770	for len(s) < 12 {771		s = "0" + s772	}773	return s774}775776func itoa(n int64) string { return strconv.FormatInt(n, 10) }777

Functions

  • AcceptAdmin(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})

  • ActiveSubID(planID int64, subscriber string) (int64, bool)

  • Address() string

  • Admin() string

  • BalanceOf(a string) int64

  • Cancel(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, subID int64)

  • Claim(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, amount int64)

  • ClaimAll(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})

  • CreatePlan(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, title string, description string, price int64, periodBlocks int64, maxFeeBps int64) int64

  • Entitled(subID int64) bool

  • EntitledFor(planID int64, subscriber string) bool

  • Expire(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, subID int64)

  • FeeBps() int64

  • FeeRecipient() string

  • FeesAccrued() int64

  • Held() int64

  • Liabilities() int64

  • NumPlans() int64

  • NumSubs() int64

  • PaidThrough(subID int64) int64

  • PendingAdmin() string

  • PlanInfo(planID int64) (provider string, title string, price int64, periodBlocks int64, planFeeBps int64, status string, lifetimeSubs int64)

  • Render(path string) string

  • Renew(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, subID int64)

  • RenewableFrom(subID int64) (from int64, until int64)

  • RetirePlan(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, planID int64)

  • SetFeeBps(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, bps int64)

  • SetFeeRecipient(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, recipient string)

  • SubInfo(subID int64) (planID int64, subscriber string, provider string, price int64, paidThrough int64, periods int64, status string)

  • Subscribe(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, planID int64) int64

  • SweepDenom(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, denom string)

  • TransferAdmin(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, successor string)

  • UsersTotal() int64

  • WithdrawFees(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.