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.

mint.gno

4.80 Kb · 156 lines
  1package bubble
  2
  3import (
  4	"strconv"
  5	"strings"
  6	"time"
  7
  8	"chain"
  9)
 10
 11// The supply, in three pools that add up to the maximum and never mix.
 12const (
 13	MaxSupply    = 100_000_000 * one
 14	premine      = 10_000_000 * one // Jae's, at init
 15	ReferralPool = 40_000_000 * one // the minter's, for referrals, under the day cap
 16	Reserve      = 50_000_000 * one // the holders', by govern:reserve only
 17
 18	MaxBatch = 100          // entries in one MintMany
 19	MaxEntry = 50_000 * one // per address per batch: room for a big inviter's day, never a whole day's cap
 20)
 21
 22// premineTo is the address the premine goes to (Jae, jarvis room 99/102/105, 2026-09-25).
 23const premineTo = address("g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6")
 24
 25var (
 26	premined       int64 // what init minted (the premine)
 27	referralMinted int64 // out of ReferralPool
 28	reserveMinted  int64 // out of Reserve
 29	mintedDay      int64 // minted by the minter on dayKey's day
 30	dayKey         int64 // unix day of mintedDay
 31	lastBatch      string
 32	batches        int64
 33)
 34
 35func Premined() int64       { return premined }
 36func ReferralMinted() int64 { return referralMinted }
 37func ReserveMinted() int64  { return reserveMinted }
 38func Batches() int64        { return batches }
 39func LastBatch() string     { return lastBatch }
 40
 41// MintedToday is what the minter has minted on the current UTC day, and the day's cap.
 42func MintedToday() (minted, ceiling int64) {
 43	if dayOf() != dayKey {
 44		return 0, dayCap
 45	}
 46	return mintedDay, dayCap
 47}
 48
 49func dayOf() int64 { return time.Now().Unix() / 86400 }
 50
 51func mustBeMinter(cur realm) {
 52	who := caller(cur)
 53	if minter == "" {
 54		panic("bubble: nobody may mint; the holders can name a minter")
 55	}
 56	if who != minter && (admin == "" || who != admin) {
 57		panic("bubble: only the minter")
 58	}
 59	if paused {
 60		panic("bubble: minting is paused")
 61	}
 62}
 63
 64// referralMint is one credit out of the referral pool, under every wall. amount is base units.
 65func referralMint(to address, amount int64) {
 66	if !to.IsValid() {
 67		panic("bubble: not an address: " + to.String())
 68	}
 69	if amount <= 0 || amount > MaxEntry {
 70		panic("bubble: an entry is between 1 and " + whole(MaxEntry) + " BUBBLE")
 71	}
 72	if referralMinted+amount > ReferralPool {
 73		panic("bubble: the referral pool is spent; what is left is the holders' reserve")
 74	}
 75	if d := dayOf(); d != dayKey {
 76		dayKey, mintedDay = d, 0
 77	}
 78	if mintedDay+amount > dayCap {
 79		panic("bubble: over today's cap of " + whole(dayCap) + " BUBBLE")
 80	}
 81	mintedDay += amount
 82	referralMinted += amount
 83	ledger.Mint(to, amount)
 84}
 85
 86// Mint credits one address. The minter's hand tool; the box mints through MintMany.
 87func Mint(cur realm, to address, amount int64) {
 88	mustBeMinter(cur)
 89	referralMint(to, amount)
 90	chain.Emit("Credit", "to", to.String(), "amount", strconv.FormatInt(amount, 10))
 91}
 92
 93// MintMany credits a batch: "g1…:amount,g1…:amount" (base units), at most MaxBatch entries, no address twice, under
 94// the pool and the day cap. All or nothing — a bad entry panics, which aborts the whole transaction, and names the
 95// entry ("entry 3 g1…: over today's cap") so the box can drop it and send the rest. batchID is eight ASCII digits
 96// that must exceed the last one landed, so a batch the box re-sends after a lost receipt cannot land twice.
 97func MintMany(cur realm, batchID string, list string) {
 98	mustBeMinter(cur)
 99	if len(batchID) != 8 {
100		panic("bubble: a batch id is eight digits")
101	}
102	for i := 0; i < len(batchID); i++ {
103		if batchID[i] < '0' || batchID[i] > '9' {
104			panic("bubble: a batch id is eight digits")
105		}
106	}
107	if batchID <= lastBatch {
108		panic("bubble: batch " + batchID + " is not after " + lastBatch)
109	}
110	parts := strings.Split(list, ",")
111	if list == "" || len(parts) > MaxBatch {
112		panic("bubble: a batch is 1 to " + strconv.Itoa(MaxBatch) + " entries")
113	}
114	seen := map[string]bool{}
115	total := int64(0)
116	for i, p := range parts {
117		c := strings.Index(p, ":")
118		if c <= 0 {
119			panic("bubble: entry " + strconv.Itoa(i+1) + ": expected g1…:amount")
120		}
121		to, raw := p[:c], p[c+1:]
122		n, err := strconv.ParseInt(raw, 10, 64)
123		if err != nil {
124			panic("bubble: entry " + strconv.Itoa(i+1) + " " + to + ": bad amount")
125		}
126		if seen[to] {
127			panic("bubble: entry " + strconv.Itoa(i+1) + " " + to + ": twice in one batch")
128		}
129		seen[to] = true
130		func() {
131			defer func() {
132				if r := recover(); r != nil {
133					panic("bubble: entry " + strconv.Itoa(i+1) + " " + to + ": " + strings.TrimPrefix(toString(r), "bubble: "))
134				}
135			}()
136			referralMint(address(to), n)
137		}()
138		total += n
139	}
140	lastBatch = batchID
141	batches++
142	chain.Emit("Batch", "id", batchID, "entries", strconv.Itoa(len(parts)), "total", strconv.FormatInt(total, 10))
143}
144
145func toString(r any) string {
146	switch e := r.(type) {
147	case error:
148		return e.Error()
149	case string:
150		return e
151	}
152	return "failed"
153}
154
155// whole renders base units as whole BUBBLE for a message.
156func whole(n int64) string { return strconv.FormatInt(n/one, 10) }