1// Package faucet is a toy points faucet realm for the gno.land sapphire testnet.2//3// Any caller may Claim() 100 points, but only once per cooldown window of 1004// blocks. Cooldown is enforced deterministically via runtime.ChainHeight() — no5// real clocks, no RNG. Per-caller balance and last-claim height live in an6// avl.Tree so Render iterates in deterministic (address-sorted) order.7package faucet89import (10 "chain"11 "chain/runtime"12 "chain/runtime/unsafe"13 "strconv"14 "strings"1516 "gno.land/p/nt/avl/v0"17)1819const (20 // ClaimAmount is granted on every successful claim.21 ClaimAmount = 10022 // CooldownBlocks is the minimum number of blocks between two claims by the23 // same caller.24 CooldownBlocks = 10025)2627// claimer holds the per-address state.28type claimer struct {29 Balance int64 // total points accumulated30 LastClaim int64 // chain height of the last successful claim31 Claims int64 // number of successful claims by this address32}3334var (35 // claimers maps address string -> *claimer.36 claimers = avl.NewTree()37 // totalDistributed is the running sum of all points ever granted.38 totalDistributed int6439 // totalClaims is the total number of successful claims across all callers.40 totalClaims int6441)4243// Claim grants ClaimAmount points to the caller, subject to the per-caller44// cooldown. It aborts (cross-realm) if the caller must still wait.45func Claim(cur realm) {46 caller := unsafe.PreviousRealm().Address()47 key := caller.String()48 height := runtime.ChainHeight()4950 var c *claimer51 if v := claimers.Get(key); v != nil {52 c = v.(*claimer)53 wait := c.LastClaim + CooldownBlocks - height54 if wait > 0 {55 panic("cooldown active: " + strconv.FormatInt(wait, 10) +56 " block(s) remaining before you can claim again")57 }58 } else {59 c = &claimer{}60 }6162 c.Balance += ClaimAmount63 c.LastClaim = height64 c.Claims++65 claimers.Set(key, c)6667 totalDistributed += ClaimAmount68 totalClaims++6970 chain.Emit(71 "Claim",72 "caller", key,73 "amount", strconv.FormatInt(ClaimAmount, 10),74 "balance", strconv.FormatInt(c.Balance, 10),75 "height", strconv.FormatInt(height, 10),76 )77}7879// BalanceOf returns the current point balance of addr (0 if never claimed).80func BalanceOf(addr string) int64 {81 if v := claimers.Get(addr); v != nil {82 return v.(*claimer).Balance83 }84 return 085}8687// Render produces a Markdown dashboard of faucet activity.88func Render(path string) string {89 var b strings.Builder9091 b.WriteString("# Token Faucet\n\n")92 b.WriteString("A toy points faucet. Call `Claim` to receive **")93 b.WriteString(strconv.FormatInt(ClaimAmount, 10))94 b.WriteString(" points** — but only once every **")95 b.WriteString(strconv.FormatInt(CooldownBlocks, 10))96 b.WriteString(" blocks** per address.\n\n")9798 b.WriteString("## Stats\n\n")99 b.WriteString("- Total distributed: **")100 b.WriteString(strconv.FormatInt(totalDistributed, 10))101 b.WriteString("** points\n")102 b.WriteString("- Total claims: **")103 b.WriteString(strconv.FormatInt(totalClaims, 10))104 b.WriteString("**\n")105 b.WriteString("- Unique claimers: **")106 b.WriteString(strconv.Itoa(claimers.Size()))107 b.WriteString("**\n")108 b.WriteString("- Current block height: **")109 b.WriteString(strconv.FormatInt(runtime.ChainHeight(), 10))110 b.WriteString("**\n\n")111112 b.WriteString("## Claimers\n\n")113 if claimers.Size() == 0 {114 b.WriteString("_No one has claimed yet. Be the first!_\n")115 return b.String()116 }117118 b.WriteString("| Address | Balance | Claims | Last claim (height) |\n")119 b.WriteString("| --- | ---: | ---: | ---: |\n")120 claimers.Iterate("", "", func(key string, value interface{}) bool {121 c := value.(*claimer)122 b.WriteString("| `")123 b.WriteString(key)124 b.WriteString("` | ")125 b.WriteString(strconv.FormatInt(c.Balance, 10))126 b.WriteString(" | ")127 b.WriteString(strconv.FormatInt(c.Claims, 10))128 b.WriteString(" | ")129 b.WriteString(strconv.FormatInt(c.LastClaim, 10))130 b.WriteString(" |\n")131 return false132 })133134 return b.String()135}136BalanceOf(addr 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})
Render(path string) string
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.