PathrockNetwork Gno Explorer
HomeBlocksTransactionsRealmsPackagesValidatorsAnalytics

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/eggling/v0

Realm
Open in gnoweb ↗

Overview

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

Files (3)

  • README.mdmarkdown
  • gnomod.tomltoml
  • eggling.gnogno
eggling.gnogno
1// Package eggling is an on-chain egg incubator and creature collector.2// Plant an egg, let it incubate for a minimum number of blocks, then Hatch3// it: the longer you waited (and a little luck, seeded from the egg's own4// history) decides the rarity of the creature you get. Train hatched5// creatures to level them up. Unlike a tamagotchi there's nothing to feed6// or lose to neglect — eggling is about patience and the luck of the7// hatch, not survival.8package eggling910import (11	"crypto/sha256"12	"encoding/hex"13	"sort"14	"strconv"15	"strings"1617	"chain/runtime"1819	"gno.land/p/nt/avl/v0"20)2122// minIncubation is the minimum number of blocks an egg must sit before it23// can hatch.24const minIncubation int64 = 202526// waitCap is the incubation-time bonus cap (in blocks) used by rarityTier,27// so waiting forever doesn't guarantee a legendary — it just improves odds.28const waitCap int64 = 2002930var speciesNames = [...]string{31	"Slime", "Sprout", "Ember", "Pebble", "Breeze", "Glimmer", "Shade", "Coral",32}3334var rarityNames = [...]string{"Common", "Uncommon", "Rare", "Legendary"}35var rarityEmoji = [...]string{"⚪", "🟢", "🔵", "🟡"}3637// egg is the persisted record for one planted egg, hatched or not.38type egg struct {39	ID         string40	Owner      address41	PlantedAt  int6442	Hatched    bool43	Species    string44	RarityTier int45	Name       string46	XP         int47	Level      int48}4950var (51	eggs         avl.Tree // id string -> *egg52	nextID       int53	totalPlanted int54	totalHatched int55	rarityCounts [4]int56)5758func get(id string) (*egg, bool) {59	v := eggs.Get(id)60	if v == nil {61		return nil, false62	}63	return v.(*egg), true64}6566// hashSeed hashes the given parts (joined with ":") to a deterministic hex67// digest. Used to derive an egg's species and rarity roll from facts68// already fixed at plant time, so nobody — not even the owner — can game69// the outcome after the fact.70func hashSeed(parts ...string) string {71	sum := sha256.Sum256([]byte(strings.Join(parts, ":")))72	return hex.EncodeToString(sum[:])73}7475// seedBytes pulls the first two bytes out of a hex digest for use as two76// independent 0-255 rolls (species pick, rarity roll).77func seedBytes(seedHex string) (byte, byte) {78	raw, err := hex.DecodeString(seedHex)79	if err != nil || len(raw) < 2 {80		return 0, 081	}82	return raw[0], raw[1]83}8485// rarityTier turns a 0-255 roll plus the blocks waited into a tier index86// 0..3 (common..legendary). Waiting longer raises the effective score, so87// patience improves your odds, but a bad roll can still land common even88// after a long wait, and a lucky roll can hatch rare almost immediately.89func rarityTier(roll uint8, waitBlocks int64) int {90	bonus := waitBlocks91	if bonus > waitCap {92		bonus = waitCap93	}94	score := int(roll) + int(bonus)/295	switch {96	case score >= 300:97		return 398	case score >= 220:99		return 2100	case score >= 140:101		return 1102	default:103		return 0104	}105}106107// plant is the non-crossing core of PlantEgg.108func plant(owner address, height int64) *egg {109	nextID++110	id := strconv.Itoa(nextID)111	e := &egg{ID: id, Owner: owner, PlantedAt: height}112	eggs.Set(id, e)113	totalPlanted++114	return e115}116117// PlantEgg starts incubating a new egg for the caller. Returns its ID.118func PlantEgg(cur realm) string {119	if !cur.IsCurrent() {120		panic("spoofed realm")121	}122	e := plant(cur.Previous().Address(), runtime.ChainHeight())123	return "🥚 planted egg #" + e.ID + " — incubate at least " +124		strconv.Itoa(int(minIncubation)) + " blocks, then Hatch(\"" + e.ID + "\")"125}126127// hatch is the non-crossing core of Hatch.128func hatch(e *egg, height int64) string {129	if e.Hatched {130		panic("egg #" + e.ID + " already hatched into a " + e.Species)131	}132	wait := height - e.PlantedAt133	if wait < minIncubation {134		panic("egg #" + e.ID + " needs " + strconv.Itoa(int(minIncubation-wait)) + " more blocks to incubate")135	}136137	seed := hashSeed(e.ID, e.Owner.String(), strconv.FormatInt(e.PlantedAt, 10))138	rarityRoll, speciesRoll := seedBytes(seed)139	tier := rarityTier(rarityRoll, wait)140	species := speciesNames[int(speciesRoll)%len(speciesNames)]141142	e.Hatched = true143	e.Species = species144	e.RarityTier = tier145	e.Name = species146	e.Level = 1147	rarityCounts[tier]++148	totalHatched++149150	return rarityEmoji[tier] + " egg #" + e.ID + " hatched into a " + rarityNames[tier] + " " + species + "!"151}152153// Hatch hatches an incubated egg the caller owns.154func Hatch(cur realm, id string) string {155	if !cur.IsCurrent() {156		panic("spoofed realm")157	}158	e, ok := get(id)159	if !ok {160		panic("no such egg #" + id)161	}162	if e.Owner != cur.Previous().Address() {163		panic("only the owner can hatch egg #" + id)164	}165	return hatch(e, runtime.ChainHeight())166}167168// train is the non-crossing core of Train.169func train(e *egg) string {170	if !e.Hatched {171		panic("egg #" + e.ID + " hasn't hatched yet")172	}173	e.XP += 10174	newLevel := e.XP/50 + 1175	leveled := newLevel > e.Level176	e.Level = newLevel177178	msg := e.Name + " gained 10 XP (" + strconv.Itoa(e.XP) + " total)"179	if leveled {180		msg += " and reached level " + strconv.Itoa(e.Level) + "!"181	}182	return msg183}184185// Train gives a hatched creature the caller owns some experience, leveling186// it up every 50 XP.187func Train(cur realm, id string) string {188	if !cur.IsCurrent() {189		panic("spoofed realm")190	}191	e, ok := get(id)192	if !ok {193		panic("no such egg #" + id)194	}195	if e.Owner != cur.Previous().Address() {196		panic("only the owner can train egg #" + id)197	}198	return train(e)199}200201// rename is the non-crossing core of Rename.202func rename(e *egg, name string) string {203	if !e.Hatched {204		panic("egg #" + e.ID + " hasn't hatched yet — nothing to name")205	}206	name = strings.TrimSpace(name)207	if name == "" {208		panic("name cannot be empty")209	}210	old := e.Name211	e.Name = name212	return old + " renamed to " + name213}214215// Rename gives a hatched creature the caller owns a custom name.216func Rename(cur realm, id string, name string) string {217	if !cur.IsCurrent() {218		panic("spoofed realm")219	}220	e, ok := get(id)221	if !ok {222		panic("no such egg #" + id)223	}224	if e.Owner != cur.Previous().Address() {225		panic("only the owner can rename egg #" + id)226	}227	return rename(e, name)228}229230// escapeInline neutralizes markdown-active characters in untrusted text231// before it's embedded inline in Render output.232func escapeInline(s string) string {233	r := strings.NewReplacer(234		"\\", "\\\\",235		"`", "\\`",236		"*", "\\*",237		"_", "\\_",238		"[", "\\[",239		"]", "\\]",240		"|", "\\|",241	)242	return r.Replace(s)243}244245func shortAddr(a address) string {246	s := a.String()247	if len(s) > 12 {248		return s[:8] + "…" + s[len(s)-4:]249	}250	return s251}252253// byIDNumeric orders eggs by their numeric ID, ascending.254type byIDNumeric []*egg255256func (r byIDNumeric) Len() int      { return len(r) }257func (r byIDNumeric) Swap(i, j int) { r[i], r[j] = r[j], r[i] }258func (r byIDNumeric) Less(i, j int) bool {259	a, _ := strconv.Atoi(r[i].ID)260	b, _ := strconv.Atoi(r[j].ID)261	return a < b262}263264func allEggs() []*egg {265	var rows []*egg266	eggs.Iterate("", "", func(_ string, v any) bool {267		rows = append(rows, v.(*egg))268		return false269	})270	sort.Stable(byIDNumeric(rows))271	return rows272}273274func renderHome() string {275	var b strings.Builder276	b.WriteString("# 🥚 Eggling\n\n")277	b.WriteString("An on-chain egg incubator. `PlantEgg()` to start one, wait at least " +278		strconv.Itoa(int(minIncubation)) + " blocks, then `Hatch(id)` — the longer you " +279		"waited (plus a little sealed-in luck) decides the rarity of the creature you get. " +280		"`Train(id)` levels a hatched creature up; `Rename(id, name)` gives it a name.\n\n")281282	b.WriteString("- Eggs planted: " + strconv.Itoa(totalPlanted) + "\n")283	b.WriteString("- Creatures hatched: " + strconv.Itoa(totalHatched) + "\n")284	if totalHatched > 0 {285		for tier := 3; tier >= 0; tier-- {286			if rarityCounts[tier] > 0 {287				b.WriteString("  - " + rarityEmoji[tier] + " " + rarityNames[tier] + ": " +288					strconv.Itoa(rarityCounts[tier]) + "\n")289			}290		}291	}292293	rows := allEggs()294	b.WriteString("\n## Eggs\n\n")295	if len(rows) == 0 {296		b.WriteString("_None planted yet. Be the first!_\n")297		return b.String()298	}299300	b.WriteString("| ID | Owner | Status | Level |\n")301	b.WriteString("| :--- | :--- | :--- | ---: |\n")302	for _, e := range rows {303		status := "🥚 incubating"304		level := "-"305		if e.Hatched {306			status = rarityEmoji[e.RarityTier] + " " + escapeInline(e.Name) + " (" + rarityNames[e.RarityTier] + " " + e.Species + ")"307			level = strconv.Itoa(e.Level)308		}309		b.WriteString("| #" + e.ID + " | `" + shortAddr(e.Owner) + "` | " + status + " | " + level + " |\n")310	}311	b.WriteString("\n_View a single egg at this realm's path plus its ID (e.g. `.../eggling:3`), " +312		"or one owner's eggs plus their address._\n")313	return b.String()314}315316func renderEgg(id string, height int64) string {317	e, ok := get(id)318	if !ok {319		return "# Egg #" + escapeInline(id) + "\n\nNo such egg.\n"320	}321322	var b strings.Builder323	if !e.Hatched {324		wait := height - e.PlantedAt325		b.WriteString("# 🥚 Egg #" + e.ID + "\n\n")326		b.WriteString("- Owner: `" + e.Owner.String() + "`\n")327		b.WriteString("- Planted at block: " + strconv.FormatInt(e.PlantedAt, 10) + "\n")328		if wait < minIncubation {329			b.WriteString("- Ready to hatch in: " + strconv.FormatInt(minIncubation-wait, 10) + " more blocks\n")330		} else {331			b.WriteString("- Ready to hatch now — call `Hatch(\"" + e.ID + "\")`\n")332		}333		return b.String()334	}335336	b.WriteString("# " + rarityEmoji[e.RarityTier] + " " + e.Name + "\n\n")337	b.WriteString("- Owner: `" + e.Owner.String() + "`\n")338	b.WriteString("- Species: " + e.Species + "\n")339	b.WriteString("- Rarity: " + rarityNames[e.RarityTier] + "\n")340	b.WriteString("- Level: " + strconv.Itoa(e.Level) + " (" + strconv.Itoa(e.XP) + " XP)\n")341	return b.String()342}343344func renderOwner(rawAddr string) string {345	owner := address(strings.TrimSpace(rawAddr))346	safe := escapeInline(owner.String())347348	var b strings.Builder349	b.WriteString("# Eggs owned by " + safe + "\n\n")350351	found := false352	for _, e := range allEggs() {353		if e.Owner != owner {354			continue355		}356		found = true357		if e.Hatched {358			b.WriteString("- #" + e.ID + ": " + rarityEmoji[e.RarityTier] + " " + escapeInline(e.Name) +359				" — " + rarityNames[e.RarityTier] + " " + e.Species + ", level " + strconv.Itoa(e.Level) + "\n")360		} else {361			b.WriteString("- #" + e.ID + ": 🥚 incubating\n")362		}363	}364	if !found {365		b.WriteString("_No eggs found for this address._\n")366	}367	return b.String()368}369370// Render shows the full egg list at "", a single egg's detail when path is371// a numeric ID, or one owner's eggs when path is a bech32 address.372func Render(path string) string {373	path = strings.TrimPrefix(strings.TrimSpace(path), "/")374	if path == "" {375		return renderHome()376	}377	if _, err := strconv.Atoi(path); err == nil {378		return renderEgg(path, runtime.ChainHeight())379	}380	return renderOwner(path)381}382

Functions

  • Hatch(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}, id string) string

  • PlantEgg(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

  • Rename(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}, id string, name string) string

  • Render(path string) string

  • Train(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}, id 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.