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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

coins.gno

5.94 Kb · 205 lines
  1// GnoMoji — Unified Coin Realm
  2// gno.land/r/g138gdmwj6heduu3st796lw6qqdwylgaxf7ty5gz/coins
  3//
  4// Single realm managing all GnoMoji GRC20-style token balances.
  5// Only the authorized minter address can call Mint or Burn.
  6// Players can call Transfer to move coins to other addresses.
  7//
  8// Balance storage: AVL tree with key "coinId:addr" → amount string.
  9// Total supply: AVL tree with key "coinId" → total minted string.
 10
 11package coins
 12
 13import (
 14	"strconv"
 15	"strings"
 16
 17	"gno.land/p/nt/avl/v0"
 18	"gno.land/p/nt/ufmt/v0"
 19)
 20
 21var (
 22	minterAddr address  // set to minter4 realm address post-deploy via SetMinter
 23	adminAddr  address  // set at init to deployer
 24	balances   avl.Tree // key: "coinId:addr" → amount string
 25	supplies   avl.Tree // key: "coinId" → total supply string
 26)
 27
 28func init(cur realm) {
 29	adminAddr = cur.Previous().Address()
 30}
 31
 32// SetMinter sets the authorized minter address.
 33// Called once after the minter realm is deployed, by the admin.
 34func SetMinter(cur realm, newMinter string) {
 35	if cur.Previous().Address() != adminAddr {
 36		panic("coins: only admin can set minter")
 37	}
 38	minterAddr = address(newMinter)
 39}
 40
 41func isMinter(cur realm) bool {
 42	return cur.Previous().Address() == minterAddr
 43}
 44
 45func isAdmin(cur realm) bool {
 46	return cur.Previous().Address() == adminAddr
 47}
 48
 49// ── Core operations ──────────────────────────────────────────────────────────
 50
 51// Mint creates new tokens of coinId and credits them to toAddr.
 52// Only the authorized minter or admin can call this.
 53func Mint(cur realm, coinId string, toAddr string, amountStr string) {
 54	if !isMinter(cur) && !isAdmin(cur) {
 55		panic("coins: unauthorized — only minter or admin can mint")
 56	}
 57	if coinId == "" || toAddr == "" {
 58		panic("coins: coinId and toAddr required")
 59	}
 60	if !strings.HasPrefix(toAddr, "g1") {
 61		panic("coins: invalid Gno.land address")
 62	}
 63	amount, err := strconv.ParseUint(amountStr, 10, 64)
 64	if err != nil || amount == 0 {
 65		panic("coins: invalid amount")
 66	}
 67
 68	// Credit balance
 69	balKey := coinId + ":" + toAddr
 70	existing := uint64(0)
 71	if balances.Has(balKey) {
 72		existing, _ = strconv.ParseUint(balances.Get(balKey).(string), 10, 64)
 73	}
 74	balances.Set(balKey, strconv.FormatUint(existing+amount, 10))
 75
 76	// Update total supply
 77	supExisting := uint64(0)
 78	if supplies.Has(coinId) {
 79		supExisting, _ = strconv.ParseUint(supplies.Get(coinId).(string), 10, 64)
 80	}
 81	supplies.Set(coinId, strconv.FormatUint(supExisting+amount, 10))
 82}
 83
 84// Burn destroys tokens of coinId from fromAddr.
 85// Only the minter or admin can burn.
 86func Burn(cur realm, coinId string, fromAddr string, amountStr string) {
 87	if !isMinter(cur) && !isAdmin(cur) {
 88		panic("coins: unauthorized — only minter or admin can burn")
 89	}
 90	amount, err := strconv.ParseUint(amountStr, 10, 64)
 91	if err != nil || amount == 0 {
 92		panic("coins: invalid amount")
 93	}
 94
 95	balKey := coinId + ":" + fromAddr
 96	current := uint64(0)
 97	if balances.Has(balKey) {
 98		current, _ = strconv.ParseUint(balances.Get(balKey).(string), 10, 64)
 99	}
100	if current < amount {
101		panic("coins: insufficient balance to burn")
102	}
103	balances.Set(balKey, strconv.FormatUint(current-amount, 10))
104
105	supCurrent := uint64(0)
106	if supplies.Has(coinId) {
107		supCurrent, _ = strconv.ParseUint(supplies.Get(coinId).(string), 10, 64)
108	}
109	if supCurrent >= amount {
110		supplies.Set(coinId, strconv.FormatUint(supCurrent-amount, 10))
111	}
112}
113
114// Transfer moves tokens from the caller to toAddr.
115// Any holder can call this.
116func Transfer(cur realm, coinId string, toAddr string, amountStr string) {
117	caller := cur.Previous().Address().String()
118	if !strings.HasPrefix(toAddr, "g1") {
119		panic("coins: invalid destination address")
120	}
121	if caller == toAddr {
122		panic("coins: cannot transfer to self")
123	}
124	amount, err := strconv.ParseUint(amountStr, 10, 64)
125	if err != nil || amount == 0 {
126		panic("coins: invalid amount")
127	}
128
129	fromKey := coinId + ":" + caller
130	current := uint64(0)
131	if balances.Has(fromKey) {
132		current, _ = strconv.ParseUint(balances.Get(fromKey).(string), 10, 64)
133	}
134	if current < amount {
135		panic("coins: insufficient balance")
136	}
137
138	balances.Set(fromKey, strconv.FormatUint(current-amount, 10))
139
140	toKey := coinId + ":" + toAddr
141	toExisting := uint64(0)
142	if balances.Has(toKey) {
143		toExisting, _ = strconv.ParseUint(balances.Get(toKey).(string), 10, 64)
144	}
145	balances.Set(toKey, strconv.FormatUint(toExisting+amount, 10))
146}
147
148// ── Queries ──────────────────────────────────────────────────────────────────
149
150// BalanceOf returns the token balance of addr for coinId.
151func BalanceOf(coinId string, addr string) string {
152	key := coinId + ":" + addr
153	if !balances.Has(key) {
154		return "0"
155	}
156	return balances.Get(key).(string)
157}
158
159// BalancesOf returns all coin balances for a given address as a JSON object.
160func BalancesOf(addr string) string {
161	var parts []string
162	balances.Iterate(
163		"",
164		"\xFF",
165		func(key string, val interface{}) bool {
166			// key format: "coinId:addr"
167			idx := strings.LastIndex(key, ":")
168			if idx < 0 {
169				return false
170			}
171			balAddr := key[idx+1:]
172			if balAddr != addr {
173				return false
174			}
175			coinId := key[:idx]
176			amt := val.(string)
177			if amt != "0" {
178				parts = append(parts, ufmt.Sprintf(`"%s":"%s"`, coinId, amt))
179			}
180			return false
181		},
182	)
183	return "{" + strings.Join(parts, ",") + "}"
184}
185
186// TotalSupply returns the total minted supply for coinId.
187func TotalSupply(coinId string) string {
188	if !supplies.Has(coinId) {
189		return "0"
190	}
191	return supplies.Get(coinId).(string)
192}
193
194// MinterAddr returns the current authorized minter address.
195func MinterAddr() string {
196	return minterAddr.String()
197}
198
199func Render(_ string) string {
200	return ufmt.Sprintf(
201		"# GnoMoji Coins\n\nAdmin: `%s`\nMinter: `%s`\n\nUnified multi-token realm for all GnoMoji coins.\nUse `BalanceOf(coinId, addr)` or `BalancesOf(addr)` to query balances.\n",
202		adminAddr.String(),
203		minterAddr.String(),
204	)
205}