Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

faucet.gno

3.84 Kb · 135 lines
  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 100
  4// blocks. Cooldown is enforced deterministically via runtime.ChainHeight() — no
  5// real clocks, no RNG. Per-caller balance and last-claim height live in an
  6// avl.Tree so Render iterates in deterministic (address-sorted) order.
  7package faucet
  8
  9import (
 10	"chain"
 11	"chain/runtime"
 12	"chain/runtime/unsafe"
 13	"strconv"
 14	"strings"
 15
 16	"gno.land/p/nt/avl/v0"
 17)
 18
 19const (
 20	// ClaimAmount is granted on every successful claim.
 21	ClaimAmount = 100
 22	// CooldownBlocks is the minimum number of blocks between two claims by the
 23	// same caller.
 24	CooldownBlocks = 100
 25)
 26
 27// claimer holds the per-address state.
 28type claimer struct {
 29	Balance   int64 // total points accumulated
 30	LastClaim int64 // chain height of the last successful claim
 31	Claims    int64 // number of successful claims by this address
 32}
 33
 34var (
 35	// claimers maps address string -> *claimer.
 36	claimers = avl.NewTree()
 37	// totalDistributed is the running sum of all points ever granted.
 38	totalDistributed int64
 39	// totalClaims is the total number of successful claims across all callers.
 40	totalClaims int64
 41)
 42
 43// Claim grants ClaimAmount points to the caller, subject to the per-caller
 44// 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()
 49
 50	var c *claimer
 51	if v := claimers.Get(key); v != nil {
 52		c = v.(*claimer)
 53		wait := c.LastClaim + CooldownBlocks - height
 54		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	}
 61
 62	c.Balance += ClaimAmount
 63	c.LastClaim = height
 64	c.Claims++
 65	claimers.Set(key, c)
 66
 67	totalDistributed += ClaimAmount
 68	totalClaims++
 69
 70	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}
 78
 79// 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).Balance
 83	}
 84	return 0
 85}
 86
 87// Render produces a Markdown dashboard of faucet activity.
 88func Render(path string) string {
 89	var b strings.Builder
 90
 91	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")
 97
 98	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")
111
112	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	}
117
118	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 false
132	})
133
134	return b.String()
135}