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/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/fishlive

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
fishlive
Namespace
g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt
Files
2 (gnomod.toml)
Exported functions
34
Module
gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/fishlive
gno
0.9

Files (2)

  • gnomod.tomltoml
  • fishlive.gnogno
fishlive.gnogno
1// Package fishlive is the person-versus-person sea.2// CommitMint pays 150 GNOT and stores only the caller and the commit height.3// RevealMint, once the chain is at least two blocks later, rolls rarity and trait4// from sha256 of the caller, the commit height, that fixed height plus two, and the id.5// Waiting longer does not change the roll. The player does not pass trait, size, or rarity.6// Swim stays off chain. Register locks at least 10 GNOT. Leave returns it after 5 seconds.7// Settle pays one covered stake. There is no machine fish and no FISH draw.8// A seated wallet can settle any other seated fish. The realm does not replay the bite.9// The five older commons stay on shoal/fish. This package does not move them.10// fishlive-1.11package fishlive1213import (14	"chain"15	"chain/banker"16	"chain/runtime"17	"chain/runtime/unsafe"18	"crypto/sha256"19	"strconv"20	"time"21)2223const (24	denom          = "ugnot"25	mintFee  int64 = 150_000_00026	stakeMin int64 = 10_000_00027	quietMs  int64 = 5_00028	capFish        = 12829)3031var (32	hpBase   = [6]int{100, 122, 148, 178, 214, 256}33	biteBase = [6]int{24, 30, 37, 45, 54, 66}34	spdBase  = [6]int{108, 104, 100, 96, 92, 88}35	species  = [6]string{"Bubble", "Tideblade", "Abysswing", "Embermaw", "Aurelion", "Astravex"}36	rarities = [6]string{"common", "uncommon", "rare", "epic", "legendary", "mythic"}37	traits   = [3]string{"swift", "fang", "hide"}38)3940type fish struct {41	owner    address42	name     string43	rarity   string44	trait    string45	bite     int46	hpMax    int47	hp       int48	speed    int49	pool     int6450	bouts    int51	commitH  int6452	touched  int6453	inSea    bool54	revealed bool55	mustHeal bool56}5758var (59	inited bool60	self   address61	lpSink address62	team   address63	burn   address64	minted int65	// A fixed [128]fish of this struct is refused by the Onyx approver.66	// Index 0 stays empty so id and index stay equal.67	roster []fish68)6970// Init records three GNOT sinks. They must be distinct. Call once, from an EOA, with no coins.71// Mint is 60/20/10 and the last tenth joins team. Kill cheer also joins team.72func Init(cur realm, lp, crew, ash address) {73	if !cur.Previous().IsUserCall() {74		panic("fishlive: eoa")75	}76	takeExact(0)77	if inited || !lp.IsValid() || !crew.IsValid() || !ash.IsValid() || lp == crew || lp == ash || crew == ash {78		panic("fishlive: init")79	}80	inited = true81	self = cur.Address()82	lpSink = lp83	team = crew84	burn = ash85	chain.Emit("Init", "self", self.String())86}8788// CommitMint sells one unrevealed fish for 150 GNOT. The fee leaves this realm in the same call.89func CommitMint(cur realm) int {90	caller := gate(cur, mintFee)91	if minted >= capFish || pending(caller) != 0 {92		panic("fishlive: mint")93	}94	height := runtime.ChainHeight()95	if height < 1 {96		panic("fishlive: mint")97	}98	id := grow()99	roster[id].owner = caller100	roster[id].commitH = height101	payMint(mintFee, cur)102	chain.Emit("Commit", "id", strconv.Itoa(id), "owner", caller.String())103	return id104}105106// RevealMint assigns rarity, trait, and the species name. Entropy is the commit height plus two.107func RevealMint(cur realm, id int) {108	caller := gate(cur, 0)109	row := mustFish(id)110	entropyH := row.commitH + 2111	if row.owner != caller || row.revealed || runtime.ChainHeight() < entropyH {112		panic("fishlive: reveal")113	}114	rarity, trait, bite, hp, speed := Roll(caller, row.commitH, entropyH, id)115	row.rarity = rarity116	row.trait = trait117	row.bite = bite118	row.hpMax = hp119	row.hp = hp120	row.speed = speed121	row.name = pickName(tierOf(rarity))122	row.revealed = true123	chain.Emit("Reveal", "id", strconv.Itoa(id), "rarity", rarity, "trait", trait, "name", row.name)124}125126// Register seats one revealed fish. The attached coins are the stake.127func Register(cur realm, id int, stake int64) {128	caller := gate(cur, stake)129	row := mustFish(id)130	if stake < stakeMin || row.owner != caller || !row.revealed || row.inSea || row.mustHeal || seated(caller) {131		panic("fishlive: register")132	}133	row.pool = stake134	row.inSea = true135	row.touched = time.Now().UnixMilli()136	chain.Emit("Register", "id", strconv.Itoa(id), "stake", strconv.FormatInt(stake, 10))137}138139// Leave returns the seated stake once 5 seconds have passed since Register.140func Leave(cur realm, id int) {141	caller := gate(cur, 0)142	row := mustFish(id)143	if row.owner != caller || !row.inSea || time.Now().UnixMilli() < row.touched+quietMs {144		panic("fishlive: leave")145	}146	stake := row.pool147	row.pool = 0148	row.inSea = false149	send(caller, stake, cur)150	chain.Emit("Leave", "id", strconv.Itoa(id), "stake", strconv.FormatInt(stake, 10))151}152153// Settle pays the smaller stake to the killer's books. The NFT stays. HP falls to 30% and must heal.154// The caller has to own the killer. This realm does not check who bit whom.155func Settle(cur realm, id int, target int) {156	caller := gate(cur, 0)157	if id == target {158		panic("fishlive: settle")159	}160	atk := mustFish(id)161	vic := mustFish(target)162	if atk.owner != caller || !atk.inSea || !vic.inSea || atk.owner == vic.owner || atk.pool < stakeMin || vic.pool < stakeMin {163		panic("fishlive: settle")164	}165	covered := atk.pool166	if vic.pool < covered {167		covered = vic.pool168	}169	excess := vic.pool - covered170	owner := vic.owner171	vic.pool = 0172	vic.inSea = false173	vic.mustHeal = true174	vic.hp = vic.hpMax * 30 / 100175	if vic.hp < 1 {176		vic.hp = 1177	}178	atk.bouts++179	vic.bouts++180	killer, lp, crew, buyback := splitKill(covered)181	send(atk.owner, killer, cur)182	send(lpSink, lp, cur)183	send(team, crew, cur)184	send(burn, buyback, cur)185	send(owner, excess, cur)186	chain.Emit("Kill", "id", strconv.Itoa(id), "target", strconv.Itoa(target), "covered", strconv.FormatInt(covered, 10))187}188189// Heal clears mustHeal outside the sea so Register can run again.190func Heal(cur realm, id int) {191	caller := gate(cur, 0)192	row := mustFish(id)193	if row.owner != caller || row.inSea || !row.mustHeal || !row.revealed {194		panic("fishlive: heal")195	}196	row.mustHeal = false197	row.hp = row.hpMax198	chain.Emit("Heal", "id", strconv.Itoa(id))199}200201// Roll is the reveal hash. entropyH must be commitH+2. Trait adds 12%, half up.202func Roll(caller address, commitH int64, entropyH int64, id int) (rarity string, trait string, bite int, hp int, speed int) {203	if id < 1 || commitH < 1 || entropyH != commitH+2 {204		panic("fishlive: roll")205	}206	raw := caller.String() + "|" + strconv.FormatInt(commitH, 10) + "|" + strconv.FormatInt(entropyH, 10) + "|" + strconv.Itoa(id)207	sum := sha256.Sum256([]byte(raw))208	n := (int(sum[0])<<8 | int(sum[1])) % 10000209	tier := 0210	if n == 0 {211		tier = 5212	} else if n < 4 {213		tier = 4214	} else if n < 14 {215		tier = 3216	} else if n < 54 {217		tier = 2218	} else if n < 254 {219		tier = 1220	}221	kind := int(sum[2]) % 3222	bite = biteBase[tier]223	hp = hpBase[tier]224	speed = spdBase[tier]225	if kind == 0 {226		speed = (speed*112 + 50) / 100227	} else if kind == 1 {228		bite = (bite*112 + 50) / 100229	} else {230		hp = (hp*112 + 50) / 100231	}232	return rarities[tier], traits[kind], bite, hp, speed233}234235func Realm() address   { return self }236func Version() string  { return "fishlive-1" }237func Name() string     { return "Shoal Live" }238func Symbol() string   { return "LIVE" }239func MintPrice() int64 { return mintFee }240func StakeMin() int64  { return stakeMin }241func Minted() int      { return minted }242func Height() int64    { return runtime.ChainHeight() }243244func OwnerOf(id string) string {245	return mustFish(atoi(id)).owner.String()246}247248func TokenURI(id string) string {249	row := mustFish(atoi(id))250	if !row.revealed {251		panic("fishlive: uri")252	}253	return `{"name":"` + row.name + `","image":""}`254}255256func TokensOf(owner string) string {257	who := address(owner)258	if !who.IsValid() {259		return ""260	}261	out := ""262	for i := 1; i <= minted; i++ {263		if roster[i].owner != who {264			continue265		}266		if out != "" {267			out += ","268		}269		out += strconv.Itoa(i)270	}271	return out272}273274func TraitOf(id int) string   { return mustFish(id).trait }275func RarityOf(id int) string  { return mustFish(id).rarity }276func SizeOf(id int) string {277	mustFish(id)278	return "medium"279}280func BoutsOf(id int) int      { return mustFish(id).bouts }281func InMatch(id int) bool     { return mustFish(id).inSea }282func PoolOf(id int) int64     { return mustFish(id).pool }283func Revealed(id int) bool    { return mustFish(id).revealed }284func MustHeal(id int) bool    { return mustFish(id).mustHeal }285func CommitOf(id int) int64   { return mustFish(id).commitH }286func HpOf(id int) int         { return mustFish(id).hp }287func BiteOf(id int) int       { return mustFish(id).bite }288func SpeedOf(id int) int      { return mustFish(id).speed }289func NameOf(id int) string    { return mustFish(id).name }290291func Pending(owner string) int {292	who := address(owner)293	if !who.IsValid() {294		return 0295	}296	return pending(who)297}298299func Render(path string) string {300	if path != "" {301		return "# Shoal Live\n\nUnknown path.\n"302	}303	return "# Shoal Live\n\nPerson versus person. CommitMint, RevealMint, Register, Leave, Heal, and Settle. No machine fish. The older shoal/fish commons stay on that package.\n"304}305306func gate(cur realm, pay int64) address {307	if !cur.Previous().IsUserCall() {308		panic("fishlive: eoa")309	}310	if !inited || self == address("") {311		panic("fishlive: init")312	}313	takeExact(pay)314	return cur.Previous().Address()315}316317func payMint(fee int64, rlm realm) {318	lp := fee * 6000 / 10000319	crew := fee * 2000 / 10000320	ash := fee * 1000 / 10000321	send(lpSink, lp, rlm)322	send(team, crew+fee-lp-crew-ash, rlm)323	send(burn, ash, rlm)324}325326func splitKill(covered int64) (killer int64, lp int64, crew int64, buyback int64) {327	killer = covered * 80 / 100328	lp = covered * 10 / 100329	teamPart := covered * 3 / 100330	buyback = covered * 3 / 100331	cheer := covered * 4 / 100332	buyback += covered - killer - lp - teamPart - buyback - cheer333	crew = teamPart + cheer334	return335}336337func send(to address, amt int64, rlm realm) {338	if !rlm.IsCurrent() {339		panic("fishlive: realm")340	}341	if amt == 0 {342		return343	}344	bk := banker.NewBanker(banker.BankerTypeRealmSend, rlm)345	bk.SendCoins(rlm.Address(), to, chain.Coins{{Denom: denom, Amount: amt}})346}347348func grow() int {349	if len(roster) == 0 {350		roster = append(roster, fish{})351	}352	if minted >= capFish || len(roster) != minted+1 {353		panic("fishlive: mint")354	}355	roster = append(roster, fish{})356	minted++357	return minted358}359360func mustFish(id int) *fish {361	if !inited || id < 1 || id > minted || id >= len(roster) {362		panic("fishlive: fish")363	}364	return &roster[id]365}366367func pending(who address) int {368	for i := 1; i <= minted; i++ {369		if roster[i].owner == who && !roster[i].revealed {370			return i371		}372	}373	return 0374}375376func seated(who address) bool {377	for i := 1; i <= minted; i++ {378		if roster[i].owner == who && roster[i].inSea {379			return true380		}381	}382	return false383}384385func pickName(tier int) string {386	base := species[tier]387	if !nameTaken(base) {388		return base389	}390	for n := 2; n <= capFish; n++ {391		candidate := base + strconv.Itoa(n)392		if !nameTaken(candidate) {393			return candidate394		}395	}396	panic("fishlive: name")397}398399func nameTaken(name string) bool {400	for i := 1; i <= minted; i++ {401		if roster[i].revealed && roster[i].name == name {402			return true403		}404	}405	return false406}407408func tierOf(rarity string) int {409	for i := 0; i < len(rarities); i++ {410		if rarities[i] == rarity {411			return i412		}413	}414	panic("fishlive: roll")415}416417func takeExact(n int64) {418	sent := unsafe.OriginSend()419	if n == 0 {420		if len(sent) != 0 {421			panic("fishlive: payment")422		}423		return424	}425	if len(sent) != 1 || sent[0].Denom != denom || sent[0].Amount != n {426		panic("fishlive: payment")427	}428}429430func atoi(s string) int {431	n, err := strconv.Atoi(s)432	if err != nil {433		panic("fishlive: fish")434	}435	return n436}437

Functions

  • BiteOf(id int) int

  • BoutsOf(id int) int

  • CommitMint(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}) int

  • CommitOf(id int) int64

  • Heal(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 int)

  • Height() int64

  • HpOf(id int) int

  • Init(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}, lp string, crew string, ash string)

  • InMatch(id int) bool

  • Leave(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 int)

  • Minted() int

  • MintPrice() int64

  • MustHeal(id int) bool

  • Name() string

  • NameOf(id int) string

  • OwnerOf(id string) string

  • Pending(owner string) int

  • PoolOf(id int) int64

  • RarityOf(id int) string

  • Realm() string

  • Register(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 int, stake int64)

  • Render(path string) string

  • Revealed(id int) bool

  • RevealMint(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 int)

  • Roll(caller string, commitH int64, entropyH int64, id int) (rarity string, trait string, bite int, hp int, speed int)

  • Settle(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 int, target int)

  • SizeOf(id int) string

  • SpeedOf(id int) int

  • StakeMin() int64

  • Symbol() string

  • TokensOf(owner string) string

  • TokenURI(id string) string

  • TraitOf(id int) string

  • Version() 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.