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/revshare

Realm
Open in gnoweb ↗

Overview

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

Files (3)

  • gnomod.tomltoml
  • revshare.gnogno
  • revshare_test.gnogno
revshare.gnogno
1// Package revshare is a team revenue realm that IS a subscription2// provider. The live subscriptions realm supports realm providers but3// warns, in its own frozen header, that a realm provider "must expose4// its own crossing path to Claim, or what it earns is stranded."5// revshare is that crossing path, plus the one thing a team needs on6// top of it: pulled revenue is split among weighted members into7// pull-claimable balances.8//9// THE ECONOMIC COMPOSITION, PRECISELY:10//11//	components            : subscriptions (live realm — the revenue12//	                        machine and the upstream custodian),13//	                        feeledger (member balance accounting,14//	                        fee cap 0), coinio (payouts, held-balance15//	                        reads, reserve-protected sweep).16//	value boundaries      : B1 subscriber EOA -> subscriptions (plan17//	                        price, origin envelope; governed by18//	                        subscriptions' own H == U + F). B219//	                        subscriptions -> revshare (banker send:20//	                        ClaimAll pays the caller, and the caller21//	                        is this realm). B3 revshare -> member EOA22//	                        (coinio.Payout on Claim/ClaimAll).23//	who owns which state  : subscriptions owns plans, subs and the24//	                        provider's claimable balance (revshare's25//	                        RECEIVABLE); revshare owns the member26//	                        table, weights, and the member balances in27//	                        its own ledger. No state is shared; the28//	                        only coupling is the crossing calls and29//	                        the coins that move over B2.30//	who controls funds    : upstream, only a claim by this realm can31//	                        move its receivable (provider-keyed32//	                        ledger). Here, member balances move only33//	                        to their owner (pull claims); the admin34//	                        can NEVER touch earned balances — sweep35//	                        reserves them, weight changes affect only36//	                        FUTURE distributions.37//	identity propagation  : downstream sees cur.Previous() = THIS38//	                        realm on every crossing call, so the plan39//	                        provider and the claim beneficiary are the40//	                        realm address by construction — no admin41//	                        or member identity ever reaches the42//	                        downstream realm.43//	conservation          : Held == UsersTotal + S (surplus above the44//	                        Liabilities() reserve, recoverable only by45//	                        SweepDenom). Distribution is exact: shares46//	                        are floor(amount*w/W) via the47//	                        overflow-free split (A/W)*w + ((A%W)*w)/W,48//	                        and the remainder goes to the49//	                        highest-weight member (ties: lowest50//	                        address) — fee_split's deterministic dust51//	                        policy, so no residual pool exists.52//	                        Cross-boundary: lifetime Pulled equals the53//	                        sum of all distributions, and the54//	                        downstream receivable is NOT part of Held.55//	ordering              : Pull refuses BEFORE the downstream call56//	                        (no members configured = refuse), claims57//	                        downstream, MEASURES the arrival as a58//	                        held-balance delta, then distributes59//	                        exactly what arrived. State-after-call:60//	                        the only local mutations happen after the61//	                        boundary, on measured coins.62//	downstream abort      : "nothing to claim" (or any downstream63//	                        panic) aborts the whole Pull — no local64//	                        state exists yet to corrupt, by ordering65//	                        AND by VM atomicity. There is no recover66//	                        anywhere in this realm, and none may be67//	                        added.68//	replay                : a second Pull finds a zero downstream69//	                        balance and aborts there. Distribution70//	                        credits are driven by the measured delta,71//	                        so a replayed Pull cannot double-count72//	                        even in principle.73//	trust                 : revshare does not trust the downstream74//	                        reply beyond "it did not abort" — it75//	                        distributes the measured balance delta,76//	                        not a reported amount. Downstream77//	                        validates nothing about this caller; the78//	                        provider ledger is keyed by address.79//	terminal states       : plans retire downstream (provider-only,80//	                        exposed here admin-gated); members can be81//	                        removed (earned balances survive removal82//	                        and stay claimable); the realm itself has83//	                        no terminal state — a team that walks away84//	                        leaves only pull-claimable balances.85//	adversarial callers   : Pull is permissionless — it can only move86//	                        the receivable into member balances at the87//	                        fixed weights, so a stranger's Pull is a88//	                        free favor. Plan creation/retirement is89//	                        admin-gated (the downstream per-provider90//	                        plan quota is a griefable resource).91//	                        Members trust the admin for FUTURE weights92//	                        only, never for earned balances. All93//	                        entrypoints refuse coin-carrying94//	                        transactions (the downstream assertNoSend95//	                        reads the origin envelope; ours matches).96//97// One team per deploy: subscriptions keeps ONE claimable balance per98// provider address, so a multi-team router behind one realm address99// could not attribute revenue at the boundary. This is a measured100// constraint of the downstream API, not a choice.101package revshare102103import (104	"chain"105	"chain/runtime/unsafe"106	"strconv"107108	"gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio"109	"gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger"110111	subs "gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/subscriptions"112)113114// Denom is the only asset this realm accounts.115const Denom = "ugnot"116117// MaxMembers bounds the distribution loop; MaxWeight bounds a single118// weight. Together they cap totalWeight at 200,000, which makes the119// remainder step of the split ((A%W)*w) provably overflow-free for120// any int64 coin amount — the conservation math cannot trap.121const (122	MaxMembers = 20123	MaxWeight  = int64(10000)124)125126type member struct {127	addr   address128	weight int64129}130131var (132	admin        address // manages members, plans; stages a successor133	pendingAdmin address // staged by TransferAdmin, completes via AcceptAdmin134	sweeper      address // may sweep out-of-band surplus135136	self address // this realm's address, captured at deploy137138	members     []member // insertion-ordered; bounded by MaxMembers139	totalWeight int64140141	ledger = feeledger.MustNew(0)142143	planIDs []int64 // plans created through this realm (ops/Render)144	pulled  int64   // lifetime revenue pulled over boundary B2145)146147func init() {148	admin = unsafe.OriginCaller()149	sweeper = admin150	self = unsafe.CurrentRealm().Address()151}152153// --- membership (admin-gated; future distributions only) ---154155// AddMember adds a weighted member. Weight changes never touch earned156// balances; they shape future pulls only.157func AddMember(cur realm, a address, weight int64) {158	assertNoSend()159	assertAdmin(cur.Previous().Address())160	var zero address161	if a == zero {162		panic("member must not be the zero address")163	}164	if weight < 1 || weight > MaxWeight {165		panic("weight must be in [1, " + itoa(MaxWeight) + "]")166	}167	if len(members) >= MaxMembers {168		panic("member table is full (" + itoa(int64(MaxMembers)) + ")")169	}170	if indexOf(a) >= 0 {171		panic("already a member")172	}173	members = append(members, member{addr: a, weight: weight})174	totalWeight += weight175	chain.Emit("MemberAdded", "addr", a.String(), "weight", itoa(weight))176}177178// SetWeight changes a member's weight for future distributions.179func SetWeight(cur realm, a address, weight int64) {180	assertNoSend()181	assertAdmin(cur.Previous().Address())182	if weight < 1 || weight > MaxWeight {183		panic("weight must be in [1, " + itoa(MaxWeight) + "]")184	}185	i := indexOf(a)186	if i < 0 {187		panic("not a member")188	}189	old := members[i].weight190	totalWeight += weight - old191	members[i].weight = weight192	chain.Emit("WeightSet", "addr", a.String(), "old", itoa(old), "new", itoa(weight))193}194195// RemoveMember removes a member from future distributions. The196// member's earned balance is untouched and stays claimable forever.197func RemoveMember(cur realm, a address) {198	assertNoSend()199	assertAdmin(cur.Previous().Address())200	i := indexOf(a)201	if i < 0 {202		panic("not a member")203	}204	totalWeight -= members[i].weight205	members = append(members[:i], members[i+1:]...)206	chain.Emit("MemberRemoved", "addr", a.String())207}208209// --- the provider surface: crossing paths into subscriptions ---210211// CreatePlan creates a subscription plan THROUGH this realm, making212// the realm the plan's provider downstream. Admin only: the213// per-provider plan quota downstream is a griefable resource.214func CreatePlan(cur realm, title, description string, price, periodBlocks, maxFeeBps int64) int64 {215	assertNoSend()216	assertAdmin(cur.Previous().Address())217	id := subs.CreatePlan(cross(cur), title, description, price, periodBlocks, maxFeeBps)218	planIDs = append(planIDs, id)219	chain.Emit("PlanCreated", "planId", itoa(id), "price", itoa(price),220		"periodBlocks", itoa(periodBlocks))221	return id222}223224// RetirePlan retires one of this realm's plans downstream. Admin only.225func RetirePlan(cur realm, planID int64) {226	assertNoSend()227	assertAdmin(cur.Previous().Address())228	subs.RetirePlan(cross(cur), planID)229	chain.Emit("PlanRetired", "planId", itoa(planID))230}231232// Pull claims this realm's entire accrued provider balance from the233// subscriptions realm and distributes it to the members by weight.234// Permissionless: pulling can only move the receivable into member235// balances at the fixed weights, so anyone may crank it. Refuses236// BEFORE the downstream call when no member could receive the funds.237func Pull(cur realm) int64 {238	assertNoSend()239	if totalWeight == 0 {240		panic("no members configured to receive revenue")241	}242243	before := coinio.HeldAt(self, Denom)244	subs.ClaimAll(cross(cur))245	amount := coinio.HeldAt(self, Denom) - before246	if amount <= 0 {247		panic("downstream claim delivered nothing")248	}249250	distribute(amount)251	pulled += amount252	chain.Emit("Pulled", "amount", itoa(amount), "pulledTotal", itoa(pulled))253	return amount254}255256// distribute splits amount by member weight. Shares are computed as257// (A/W)*w + ((A%W)*w)/W — floor(A*w/W) without any multiplication258// that can overflow. The remainder (< number of members) goes to the259// highest-weight member, lowest address on ties: deterministic, and260// it leaves the realm with no undistributed pool at rest.261func distribute(amount int64) {262	q := amount / totalWeight263	r := amount % totalWeight264265	distributed := int64(0)266	for _, m := range members {267		share := q*m.weight + (r*m.weight)/totalWeight268		if share > 0 {269			ledger.MustDeposit(m.addr.String(), share, 0)270			distributed += share271		}272	}273	if dust := amount - distributed; dust > 0 {274		ledger.MustDeposit(dustRecipient().String(), dust, 0)275	}276}277278func dustRecipient() address {279	best := members[0]280	for _, m := range members[1:] {281		if m.weight > best.weight ||282			(m.weight == best.weight && m.addr.String() < best.addr.String()) {283			best = m284		}285	}286	return best.addr287}288289// --- member claims ---290291// Claim sends amount ugnot of the caller's earned balance to the292// caller.293func Claim(cur realm, amount int64) {294	assertNoSend()295	caller := cur.Previous().Address()296	if err := ledger.Withdraw(caller.String(), amount); err != nil {297		panic(err)298	}299	coinio.Payout(0, cur, caller, Denom, amount)300	chain.Emit("Claim", "to", caller.String(), "amount", itoa(amount))301}302303// ClaimAll sends the caller's entire earned balance to the caller.304func ClaimAll(cur realm) {305	assertNoSend()306	caller := cur.Previous().Address()307	amount, err := ledger.WithdrawAll(caller.String())308	if err != nil {309		panic(err)310	}311	if amount == 0 {312		panic("nothing to claim")313	}314	coinio.Payout(0, cur, caller, Denom, amount)315	chain.Emit("Claim", "to", caller.String(), "amount", itoa(amount))316}317318// --- administration ---319320// SweepDenom recovers out-of-band coins to the sweeper. For the321// accounting denom the reserve is the full ledger liability — member322// balances are structurally unsweepable. Sweeper only.323func SweepDenom(cur realm, denom string) {324	assertNoSend()325	if cur.Previous().Address() != sweeper {326		panic("sweeper only")327	}328	reserve := int64(0)329	if denom == Denom {330		reserve = ledger.Liabilities()331	}332	swept := coinio.Sweep(0, cur, sweeper, denom, reserve)333	chain.Emit("SurplusSwept", "to", sweeper.String(), "coins", itoa(swept)+denom)334}335336// TransferAdmin stages a two-step admin handover.337func TransferAdmin(cur realm, successor address) {338	assertNoSend()339	assertAdmin(cur.Previous().Address())340	var zero address341	if successor == zero {342		panic("successor must not be the zero address")343	}344	pendingAdmin = successor345	chain.Emit("AdminTransferStaged", "from", admin.String(), "to", successor.String())346}347348// AcceptAdmin completes the handover; only the staged successor may.349// The sweeper role moves with the admin.350func AcceptAdmin(cur realm) {351	assertNoSend()352	caller := cur.Previous().Address()353	if caller != pendingAdmin {354		panic("only the staged successor may accept")355	}356	old := admin357	admin = caller358	sweeper = caller359	var zero address360	pendingAdmin = zero361	chain.Emit("AdminTransferred", "from", old.String(), "to", admin.String())362}363364// --- views ---365366func Admin() address        { return admin }367func PendingAdmin() address { return pendingAdmin }368func MemberCount() int64    { return int64(len(members)) }369func TotalWeight() int64    { return totalWeight }370func MemberWeight(a address) int64 {371	i := indexOf(a)372	if i < 0 {373		panic("not a member")374	}375	return members[i].weight376}377func BalanceOf(a address) int64 { return ledger.BalanceOf(a.String()) }378func UsersTotal() int64         { return ledger.UsersTotal() }379func Pulled() int64             { return pulled }380func NumPlans() int64           { return int64(len(planIDs)) }381func PlanID(i int64) int64 {382	if i < 0 || i >= int64(len(planIDs)) {383		panic("plan index out of range")384	}385	return planIDs[i]386}387func Address() address { return self }388func Held() int64      { return coinio.HeldAt(self, Denom) }389390// Receivable reads this realm's accrued, not-yet-pulled provider391// balance inside the subscriptions realm — the other side of value392// boundary B2. It is deliberately NOT part of Held or of the local393// conservation equation.394func Receivable() int64 { return subs.BalanceOf(self) }395396// --- render ---397398func Render(path string) string {399	if path != "" {400		return "unknown page; try the realm root"401	}402	out := "# revshare\n\n"403	out += "A team revenue realm: it is the PROVIDER of its " +404		"subscription plans, pulls accrued revenue across the realm " +405		"boundary, and splits it among weighted members into " +406		"pull-claimable balances.\n\n"407	out += "- receivable (in subscriptions): " + itoa(Receivable()) + Denom + "\n"408	out += "- held here: " + itoa(Held()) + Denom + "\n"409	out += "- earned, unclaimed: " + itoa(ledger.UsersTotal()) + Denom + "\n"410	out += "- lifetime pulled: " + itoa(pulled) + Denom + "\n"411	out += "- plans created here: " + itoa(int64(len(planIDs))) + "\n\n"412	out += "## members\n\n"413	if len(members) == 0 {414		out += "(none configured)\n"415		return out416	}417	out += "| member | weight | unclaimed |\n|---|---|---|\n"418	for _, m := range members {419		out += "| " + m.addr.String() + " | " + itoa(m.weight) + " | " +420			itoa(ledger.BalanceOf(m.addr.String())) + Denom + " |\n"421	}422	out += "\n(total weight " + itoa(totalWeight) + "; rounding dust " +423		"goes to the highest-weight member)\n"424	return out425}426427// --- internals ---428429func indexOf(a address) int {430	for i, m := range members {431		if m.addr == a {432			return i433		}434	}435	return -1436}437438func assertNoSend() {439	if len(unsafe.OriginSend()) != 0 {440		panic("this function does not accept coins")441	}442}443444func assertAdmin(caller address) {445	if caller != admin {446		panic("admin only")447	}448}449450func itoa(n int64) string { return strconv.FormatInt(n, 10) }451

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})

  • AddMember(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}, a string, weight int64)

  • Address() string

  • Admin() string

  • BalanceOf(a string) 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

  • Held() int64

  • MemberCount() int64

  • MemberWeight(a string) int64

  • NumPlans() int64

  • PendingAdmin() string

  • PlanID(i int64) int64

  • Pull(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}) int64

  • Pulled() int64

  • Receivable() int64

  • RemoveMember(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}, a string)

  • Render(path string) string

  • 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)

  • SetWeight(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}, a string, weight 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)

  • TotalWeight() int64

  • 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

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.