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/moul/x/daily/streaks/v0

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v0
Namespace
moul / x / daily / streaks
Files
3 (README)(gnomod.toml)
Exported functions
4
Module
gno.land/r/moul/x/daily/streaks/v0
gno
0.9

Files (3)

  • README.mdmarkdown
  • gnomod.tomltoml
  • streaks.gnogno
streaks.gnogno
1// Package streaks is an on-chain habit-streak tracker. Call CheckIn() again2// within graceBlocks of your last check-in and the streak keeps growing; let3// the gap run past that and it resets to zero on your next check-in. Gno has4// no wall clock, so "again soon enough" is measured in blocks rather than5// calendar days — graceBlocks is the tunable stand-in for "before tomorrow".6package streaks78import (9	"sort"10	"strconv"1112	"chain/runtime"1314	"gno.land/p/nt/avl/v0"15)1617// graceBlocks is the max block gap between two check-ins that still counts18// as consecutive. A gap larger than this lapses the streak.19const graceBlocks int64 = 1002021// record is the persisted streak state for one address.22type record struct {23	owner      address24	current    int6425	longest    int6426	total      int6427	lastHeight int6428}2930var streaks avl.Tree // owner address string -> *record3132func get(owner address) (*record, bool) {33	v, ok := streaks.Get(owner.String()).(*record)34	return v, ok35}3637// project reports the record's current streak as of height without38// mutating it: once the gap since lastHeight exceeds graceBlocks the streak39// reads as lapsed, even though the stored value only resets on the next40// actual check-in.41func project(r *record, height int64) (current int64, alive bool) {42	if height-r.lastHeight > graceBlocks {43		return 0, false44	}45	return r.current, true46}4748// checkin is the non-crossing core of CheckIn, kept separate so unit tests49// can drive it directly by address and height.50func checkin(owner address, height int64) string {51	r, ok := get(owner)52	if !ok {53		r = &record{owner: owner}54		streaks.Set(owner.String(), r)55	} else {56		if height <= r.lastHeight {57			panic("already checked in at this block height")58		}59		if height-r.lastHeight > graceBlocks {60			r.current = 061		}62	}63	r.current++64	if r.current > r.longest {65		r.longest = r.current66	}67	r.lastHeight = height68	r.total++69	return "checked in — current streak: " + strconv.FormatInt(r.current, 10) + " 🔥"70}7172// CheckIn records a check-in for the caller at the current block height.73func CheckIn(cur realm) string {74	if !cur.IsCurrent() {75		panic("spoofed realm")76	}77	return checkin(cur.Previous().Address(), runtime.ChainHeight())78}7980// CurrentStreak returns addr's live current streak, 0 if it has lapsed or81// addr has never checked in.82func CurrentStreak(addr string) int64 {83	r, ok := get(address(addr))84	if !ok {85		return 086	}87	current, _ := project(r, runtime.ChainHeight())88	return current89}9091// LongestStreak returns addr's best streak ever, 0 if it has never checked in.92func LongestStreak(addr string) int64 {93	r, ok := get(address(addr))94	if !ok {95		return 096	}97	return r.longest98}99100// byRank orders the leaderboard by longest streak, then current streak, then101// owner address — fully deterministic regardless of avl iteration order.102type byRank []*record103104func (b byRank) Len() int      { return len(b) }105func (b byRank) Swap(i, j int) { b[i], b[j] = b[j], b[i] }106func (b byRank) Less(i, j int) bool {107	if b[i].longest != b[j].longest {108		return b[i].longest > b[j].longest109	}110	if b[i].current != b[j].current {111		return b[i].current > b[j].current112	}113	return b[i].owner.String() < b[j].owner.String()114}115116func shortAddr(a address) string {117	s := a.String()118	if len(s) > 12 {119		return s[:8] + "…" + s[len(s)-4:]120	}121	return s122}123124func renderDetail(r *record, height int64) string {125	current, alive := project(r, height)126	status := "🔥 active"127	if !alive {128		status = "💤 lapsed"129	}130	out := "## `" + shortAddr(r.owner) + "`\n\n"131	out += "- Status: " + status + "\n"132	out += "- Current streak: " + strconv.FormatInt(current, 10) + "\n"133	out += "- Longest streak: " + strconv.FormatInt(r.longest, 10) + "\n"134	out += "- Total check-ins: " + strconv.FormatInt(r.total, 10) + "\n"135	out += "- Last check-in at block " + strconv.FormatInt(r.lastHeight, 10) + "\n"136	return out137}138139// Render shows either the full leaderboard (path == "") or a single140// address's detail card when path is a bech32 address.141func Render(path string) string {142	height := runtime.ChainHeight()143144	out := "# 🔥 Streaks\n\n"145	out += "An on-chain habit-streak tracker. Call `CheckIn()` again within " +146		strconv.FormatInt(graceBlocks, 10) + " blocks of your last check-in to keep it alive; " +147		"wait longer than that and it resets to zero on your next check-in.\n\n"148149	if path != "" {150		r, ok := get(address(path))151		if !ok {152			return out + "_No check-ins yet for `" + path + "`._\n"153		}154		return out + renderDetail(r, height)155	}156157	var rows []*record158	streaks.Iterate("", "", func(_ string, v any) bool {159		rows = append(rows, v.(*record))160		return false161	})162	if len(rows) == 0 {163		out += "_Nobody has checked in yet. Be the first!_\n"164		return out165	}166	sort.Stable(byRank(rows))167168	out += "| Rank | Address | Status | Current | Longest | Total |\n"169	out += "| ---: | :--- | :--- | ---: | ---: | ---: |\n"170	for i, r := range rows {171		current, alive := project(r, height)172		status := "🔥"173		if !alive {174			status = "💤"175		}176		out += "| " + strconv.Itoa(i+1) + " | `" + shortAddr(r.owner) + "` | " + status +177			" | " + strconv.FormatInt(current, 10) + " | " + strconv.FormatInt(r.longest, 10) +178			" | " + strconv.FormatInt(r.total, 10) + " |\n"179	}180	out += "\n_View a single address at `?<address>`._\n"181	return out182}183

Functions

  • CheckIn(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}) string

  • CurrentStreak(addr string) int64

  • LongestStreak(addr string) int64

  • Render(path string) 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.