1// Package across reads one address across fifteen other realms that are2// already live, and renders everything they will say about it on one page.3//4// It is a composability demo with a working product hiding inside it. The5// realms it reads were written months apart, by nobody coordinating, and none6// of them knows this one exists. Not a line of them changed to make this7// possible: the only requirement any of them had to meet was NOT declaring8// `private = true`, which is what lets another realm import it at all.9//10// # Why this realm holds no state and extracts no library11//12// Every other realm here splits into a pure `p/` and a thin `r/`. This one13// cannot, and that is the point: its entire content is the wiring. Fifteen14// import lines, one call each, and a table. Extract a library and what is left15// on both sides is empty.16//17// # What it measures, besides balances18//19// The signatures below are the honest half of the demo. Eight of these realms20// hold a per-address balance and they agree on nothing:21//22// erc20, vestoken, wrapped BalanceOf(address) uint6423// pairs/aaa, pairs/bbb BalanceOf(address) int6424// grc20faucet BalanceOf(symbol string, owner address) int6425// wikicoin BalanceOf(address) uint26// dice RollsOf(addr string) []int27//28// Three integer widths, two signednesses, one that takes the address as a29// string, one that needs a symbol first. Composition works anyway, because a30// gno import is a type-checked contract with a permanent artifact behind it and31// not a call convention anyone has to agree on in advance. It just costs one32// hand-written adapter per source, which is what the report function is.33//34// # The two sources that could not be read35//36// - r/moul/x/amm/v0 exports SharesOf(keyA, keyB string, owner address), and37// it routes through mustPool, which PANICS when the pool does not exist.38// A getter that panics is not composable: fifteen sources on one page means39// one panicking source takes the whole page down, and a wrapper cannot40// recover, because recover() does not catch a cross-realm abort. So amm41// contributes PoolCount() to the index and nothing to a report.42// - r/moul/demo/vault/v0 exports MyBalance() with no address parameter. It43// answers for whoever is calling, which from here is this realm, so the44// value is structurally unreadable from outside.45//46// Neither is a bug in those realms. Both are what a getter looks like when47// nobody has tried to call it from another realm yet.48package across4950import (51 "strconv"52 "strings"5354 "gno.land/p/moul/kit/ui/v0"5556 "gno.land/r/moul/demo/wikicoin/v0"57 "gno.land/r/moul/reactions/v0"58 "gno.land/r/moul/x/amm/v0"59 "gno.land/r/moul/x/daily/dice/v0"60 "gno.land/r/moul/x/daily/erc20/v0"61 "gno.land/r/moul/x/daily/lottery/v0"62 "gno.land/r/moul/x/daily/ratelimitdemo/v0"63 "gno.land/r/moul/x/daily/staking/v0"64 "gno.land/r/moul/x/daily/streak/v0"65 "gno.land/r/moul/x/daily/vestoken/v0"66 "gno.land/r/moul/x/daily/wrapped/v0"67 "gno.land/r/moul/x/grc20faucet/v0"68 "gno.land/r/moul/x/pairs/aaa/v0"69 "gno.land/r/moul/x/pairs/aaabbb/v0"70 "gno.land/r/moul/x/pairs/bbb/v0"71)7273// reactionPage is the page key handed to r/moul/reactions. Reactions are keyed74// on an arbitrary page string rather than on a realm, so a reader of this75// report is asked about the same page /u/moul shows.76const reactionPage = "gno.land/r/moul/home"7778// Self is this realm's own package path, exported so a caller can build a link79// back without hardcoding the version.80const Self = "gno.land/r/moul/x/across/v0"8182// A fact is one answer from one source realm about one address.83//84// zero is carried separately from value because "0" and "none" are the common85// answer and the table is worth reading only for the rows that are not that.86type fact struct {87 source string // package path, without the gno.land/ prefix88 what string89 value string90 zero bool91}9293// Report collects what every wired source says about who.94//95// Every call in here is a plain exported read on another realm: no cur realm,96// no cross, nothing written. A read with no realm parameter is borrowed rather97// than entered (AGENTS.md), so none of these realms even observes a caller.98func Report(who address) []fact {99 out := []fact{}100101 add := func(source, what string, n int64, unit string) {102 v := strconv.FormatInt(n, 10)103 if unit != "" {104 v += " " + unit105 }106 out = append(out, fact{source: source, what: what, value: v, zero: n == 0})107 }108109 // Balances. Eight sources, five signatures.110 add("r/moul/x/daily/erc20/v0", "balance", int64(erc20.BalanceOf(who)), erc20.Symbol())111 add("r/moul/x/daily/vestoken/v0", "liquid balance", int64(vestoken.BalanceOf(who)), vestoken.Symbol())112 add("r/moul/x/daily/wrapped/v0", "wrapped balance", int64(wrapped.BalanceOf(who)), "WGNOT")113 add("r/moul/demo/wikicoin/v0", "balance", int64(wikicoin.BalanceOf(who)), "WIKI")114 add("r/moul/x/pairs/aaa/v0", "balance", aaa.BalanceOf(who), "AAA")115 add("r/moul/x/pairs/bbb/v0", "balance", bbb.BalanceOf(who), "BBB")116 add("r/moul/x/grc20faucet/v0", "balance", grc20faucet.BalanceOf("RED", who), "RED")117 add("r/moul/x/grc20faucet/v0", "balance", grc20faucet.BalanceOf("BLUE", who), "BLUE")118119 // A grant is three numbers, so it does not fit the add helper.120 total, claimed, claimable := vestoken.GrantOf(who)121 out = append(out, fact{122 source: "r/moul/x/daily/vestoken/v0",123 what: "vesting grant",124 value: strconv.FormatUint(total, 10) + " total, " +125 strconv.FormatUint(claimed, 10) + " claimed, " +126 strconv.FormatUint(claimable, 10) + " claimable",127 zero: total == 0,128 })129130 // Positions.131 add("r/moul/x/pairs/aaabbb/v0", "LP shares", aaabbb.SharesOf(who), "")132 add("r/moul/x/daily/staking/v0", "staked", int64(staking.StakedOf(who)), "")133 add("r/moul/x/daily/staking/v0", "unclaimed rewards", int64(staking.Earned(who)), "")134135 // Activity.136 add("r/moul/x/daily/streak/v0", "current streak", int64(streak.Current(who)), "day(s)")137 add("r/moul/x/daily/streak/v0", "best streak", int64(streak.Best(who)), "day(s)")138 add("r/moul/x/daily/dice/v0", "dice rolled", int64(len(dice.RollsOf(who.String()))), "")139 add("r/moul/x/daily/ratelimitdemo/v0", "rate-limit tokens left", int64(ratelimitdemo.Tokens(who)), "")140141 entered := "no"142 if lottery.HasEntered(who) {143 entered = "yes, round " + strconv.Itoa(lottery.CurrentRound())144 }145 out = append(out, fact{146 source: "r/moul/x/daily/lottery/v0",147 what: "entered the lottery",148 value: entered,149 zero: !lottery.HasEntered(who),150 })151152 // reactions answers with a palette key, already validated at write time, so153 // it never carries caller markdown into this table.154 react := reactions.ReactionOf(reactionPage, who)155 shown := react156 if shown == "" {157 shown = "none"158 }159 out = append(out, fact{160 source: "r/moul/reactions/v0",161 what: "reaction on " + reactionPage,162 value: shown,163 zero: react == "",164 })165166 return out167}168169// sourceRealms is every realm this one imports for its data, in import order.170// It is the single place the count comes from: the index table has one row per171// entry and TestSourcesCoverReport checks that Report names nothing else.172//173// Fifteen entries, which is the number the prose says. TestSourceCount pins it.174var sourceRealms = []string{175 "r/moul/x/daily/erc20/v0",176 "r/moul/x/daily/vestoken/v0",177 "r/moul/x/daily/wrapped/v0",178 "r/moul/demo/wikicoin/v0",179 "r/moul/x/pairs/aaa/v0",180 "r/moul/x/pairs/bbb/v0",181 "r/moul/x/grc20faucet/v0",182 "r/moul/x/pairs/aaabbb/v0",183 "r/moul/x/amm/v0",184 "r/moul/x/daily/staking/v0",185 "r/moul/x/daily/streak/v0",186 "r/moul/x/daily/dice/v0",187 "r/moul/x/daily/lottery/v0",188 "r/moul/x/daily/ratelimitdemo/v0",189 "r/moul/reactions/v0",190}191192// Sources reports how many realms this one reads.193func Sources() int { return len(sourceRealms) }194195func Render(path string) string {196 if path == "" {197 return renderIndex()198 }199 addr := address(path)200 if !addr.IsValid() {201 return "# across\n\n" + ui.Empty(ui.Inline(ui.Short(path))+" is not a valid address.") +202 "\n[back to the index](/r/moul/x/across/v0)\n"203 }204 return renderAddress(addr)205}206207func renderAddress(who address) string {208 var b strings.Builder209 b.WriteString("# across: " + ui.AddrFull(who) + "\n\n")210 b.WriteString("What " + strconv.Itoa(Sources()) + " independently deployed realms say about this address. ")211 b.WriteString("None of them was changed to answer.\n\n")212213 facts := Report(who)214215 live := ui.NewTable("source", "fact", "value")216 quiet := 0217 for _, f := range facts {218 if f.zero {219 quiet++220 continue221 }222 live.Row("[`"+f.source+"`](/"+f.source+")", f.what, f.value)223 }224225 if live.Len() == 0 {226 b.WriteString(ui.Empty("Every one of the " + strconv.Itoa(len(facts)) +227 " reads came back empty. This address has never touched any of them."))228 b.WriteString("\n")229 } else {230 b.WriteString("## Answers\n\n")231 b.WriteString(live.String())232 b.WriteString("\n")233 b.WriteString(strconv.Itoa(quiet) + " of " + strconv.Itoa(len(facts)) +234 " reads came back empty and are hidden.\n\n")235 }236237 b.WriteString("[back to the index](/r/moul/x/across/v0)\n")238 return b.String()239}240241func renderIndex() string {242 var b strings.Builder243 b.WriteString("# across\n\n")244 b.WriteString("One address, read across " + strconv.Itoa(Sources()) + " realms that are already on chain.\n\n")245 b.WriteString("Open `/r/moul/x/across/v0:<address>` to get the report. ")246 b.WriteString("Example: [/r/moul/x/across/v0:" + exampleAddr + "](/r/moul/x/across/v0:" + exampleAddr + ").\n\n")247248 b.WriteString("## Why this realm exists\n\n")249 b.WriteString("A gno realm that does not declare `private = true` can be imported by any ")250 b.WriteString("other realm, forever, at a path that can never be replaced. That makes every ")251 b.WriteString("exported read on it a permanent public API. This realm is what happens when ")252 b.WriteString("somebody actually uses " + strconv.Itoa(Sources()) + " of them at once.\n\n")253 b.WriteString("Nothing here was negotiated. No registry, no interface, no shared base ")254 b.WriteString("package, no version handshake, and no change to any source realm.\n\n")255256 b.WriteString("## The sources, and what each one is showing right now\n\n")257 t := ui.NewTable("realm", "global reading")258 t.Row("[`r/moul/x/daily/erc20/v0`](/r/moul/x/daily/erc20/v0)",259 erc20.Symbol()+" supply "+strconv.FormatUint(erc20.TotalSupply(), 10))260 t.Row("[`r/moul/x/daily/vestoken/v0`](/r/moul/x/daily/vestoken/v0)",261 vestoken.Symbol()+" supply "+strconv.FormatUint(vestoken.TotalSupply(), 10))262 t.Row("[`r/moul/x/daily/wrapped/v0`](/r/moul/x/daily/wrapped/v0)",263 "wrapped supply "+strconv.FormatUint(wrapped.TotalSupply(), 10))264 t.Row("[`r/moul/demo/wikicoin/v0`](/r/moul/demo/wikicoin/v0)", "per-address balances only")265 t.Row("[`r/moul/x/pairs/aaa/v0`](/r/moul/x/pairs/aaa/v0)",266 "AAA supply "+strconv.FormatInt(aaa.TotalSupply(), 10))267 t.Row("[`r/moul/x/pairs/bbb/v0`](/r/moul/x/pairs/bbb/v0)",268 "BBB supply "+strconv.FormatInt(bbb.TotalSupply(), 10))269 t.Row("[`r/moul/x/grc20faucet/v0`](/r/moul/x/grc20faucet/v0)",270 "RED supply "+strconv.FormatInt(grc20faucet.TotalSupply("RED"), 10)+271 ", BLUE supply "+strconv.FormatInt(grc20faucet.TotalSupply("BLUE"), 10))272 t.Row("[`r/moul/x/pairs/aaabbb/v0`](/r/moul/x/pairs/aaabbb/v0)",273 "LP shares "+strconv.FormatInt(aaabbb.TotalShares(), 10))274 t.Row("[`r/moul/x/amm/v0`](/r/moul/x/amm/v0)",275 strconv.Itoa(amm.PoolCount())+" pool(s), index only (see below)")276 t.Row("[`r/moul/x/daily/staking/v0`](/r/moul/x/daily/staking/v0)", "per-address stake and rewards")277 t.Row("[`r/moul/x/daily/streak/v0`](/r/moul/x/daily/streak/v0)",278 "day "+strconv.FormatInt(streak.Day(), 10))279 t.Row("[`r/moul/x/daily/dice/v0`](/r/moul/x/daily/dice/v0)",280 strconv.Itoa(dice.Total())+" roll(s) all time")281 t.Row("[`r/moul/x/daily/lottery/v0`](/r/moul/x/daily/lottery/v0)",282 "round "+strconv.Itoa(lottery.CurrentRound())+", "+283 strconv.Itoa(lottery.NumEntrants())+" entrant(s)")284 t.Row("[`r/moul/x/daily/ratelimitdemo/v0`](/r/moul/x/daily/ratelimitdemo/v0)", "per-address bucket")285 t.Row("[`r/moul/reactions/v0`](/r/moul/reactions/v0)",286 strconv.Itoa(reactions.Pages())+" page(s) reacted to")287 b.WriteString(t.String())288 b.WriteString("\n")289290 b.WriteString("## What it cost\n\n")291 b.WriteString("Eight of these hold a per-address balance, and they share no signature: ")292 b.WriteString("`uint64` three times, `int64` twice, `uint` once, one that wants a symbol ")293 b.WriteString("before the address, one that wants the address as a `string`. Every source ")294 b.WriteString("therefore needs its own adapter line. That is the whole cost, and it is paid ")295 b.WriteString("once by the reader rather than up front by fifteen authors.\n\n")296297 b.WriteString("## The two that could not be read\n\n")298 b.WriteString("- `r/moul/x/amm/v0` routes `SharesOf` through a helper that panics on an ")299 b.WriteString("unknown pool. One panicking source takes down a page built from " +300 strconv.Itoa(Sources()) + ", and ")301 b.WriteString("`recover()` does not catch a cross-realm abort, so only its `PoolCount()` is ")302 b.WriteString("safe to call from here.\n")303 b.WriteString("- `r/moul/demo/vault/v0` exports `MyBalance()` with no address parameter. It ")304 b.WriteString("answers for its caller, which from here is this realm.\n\n")305 b.WriteString("Neither is a bug. Both are what a getter looks like before anyone has tried ")306 b.WriteString("to call it from another realm.\n")307308 return b.String()309}310311// exampleAddr is moul's address, used only to give the index a link that312// resolves to a real report instead of an empty one.313const exampleAddr = "g1manfred47kzduec920z88wfr64ylksmdcedlf5"314Render(path string) string
Report(who string) []gno.land/r/moul/x/across/v0.fact
Sources() int
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
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.