← Back to Realms

Realm

gno.land/r/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/adtreasury

Overview

Realm Path
gno.land/r/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/adtreasury
Exported Functions
11
State Entries
9
Source Files
2
Total Package Entries
26

Exported Functions

11 exported functions

State

9 state entries

Source Code

FILES
adtreasury.gno
go
1// Package adtreasury is the ad station's own treasury (jae, his terminal, 2026-10-14: "it goes to a treasury
2// specifically for the ad station with its own controls/governance" → "just me for now but make it upgradeable").
3//
4// It holds GNOT: what the ad station's weekly auctions earn (r/…/adstation sends each settled week's winning bids
5// here), and whatever anyone sends to its address. One controller spends it, each spend on the record with its memo.
6// The controller is Jae's cold key at first; the role moves by a two-step hand-off (SetController names, AcceptController
7// takes), and the one named may be a realm — a council or a vote later calls AcceptController and Spend through a
8// crossing call, so the governance can be upgraded without moving the money.
9//
10// Immutable, not private (gnomod.toml).
11package adtreasury
12
13import (
14	"chain"
15	"chain/banker"
16	"strconv"
17	"strings"
18	"time"
19)
20
21const (
22	pkgPath = "gno.land/r/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/adtreasury" // must match gnomod.toml
23	GNOT    = "ugnot"
24	maxMemo = 120
25	listMax = 50 // spends the page lists
26)
27
28// Payment is one spend out of the treasury, on the record for good.
29type Payment struct {
30	ID     int64
31	To     address
32	Amount int64 // ugnot
33	Memo   string
34	By     address // the controller that sent it
35	At     int64   // unix seconds
36}
37
38var (
39	controller     = address("g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6") // Jae's cold key (the realm ownership rule)
40	nextController address
41	spends         []Payment
42	spent          int64
43)
44
45func init(cur realm) {
46	if cur.Address() != chain.PackageAddress(pkgPath) {
47		panic("adtreasury: deployed at a path other than pkgPath")
48	}
49}
50
51// ---------------------------------------------------------------- reading --
52
53// Address is where the treasury receives: send GNOT here.
54func Address() address { return chain.PackageAddress(pkgPath) }
55
56// Balance is the GNOT the treasury holds, in ugnot.
57func Balance() int64 {
58	return banker.NewReadonlyBanker().GetCoin(Address(), GNOT)
59}
60
61func Controller() address     { return controller }
62func NextController() address { return nextController }
63func Spent() int64            { return spent }
64func Count() int              { return len(spends) }
65
66// Get is a copy of spend id (1-based).
67func Get(id int64) Payment {
68	if id < 1 || id > int64(len(spends)) {
69		panic("adtreasury: no such spend")
70	}
71	return spends[id-1]
72}
73
74// ---------------------------------------------------------------- writing --
75
76func caller(cur realm) address {
77	if !cur.IsCurrent() {
78		panic("adtreasury: spoofed realm")
79	}
80	return cur.Previous().Address()
81}
82
83func mustControl(cur realm) address {
84	who := caller(cur)
85	if who != controller {
86		panic("adtreasury: only the controller")
87	}
88	return who
89}
90
91// Spend pays ugnot to an address, by the controller, with a memo kept on the record (plain text, at most 120
92// characters). Returns the spend's id.
93func Spend(cur realm, to address, ugnot int64, memo string) int64 {
94	by := mustControl(cur)
95	if !to.IsValid() {
96		panic("adtreasury: not an address")
97	}
98	if to == Address() {
99		panic("adtreasury: the treasury cannot pay itself")
100	}
101	if ugnot <= 0 {
102		panic("adtreasury: the amount must be positive")
103	}
104	if ugnot > Balance() {
105		panic("adtreasury: the treasury holds only " + strconv.FormatInt(Balance(), 10) + " ugnot")
106	}
107	plain(memo)
108	banker.NewBanker(banker.BankerTypeRealmSend, cur).SendCoins(Address(), to, chain.Coins{{GNOT, ugnot}})
109	spent += ugnot
110	id := int64(len(spends)) + 1
111	spends = append(spends, Payment{ID: id, To: to, Amount: ugnot, Memo: memo, By: by, At: time.Now().Unix()})
112	chain.Emit("Spent", "id", strconv.FormatInt(id, 10), "to", to.String(), "ugnot", strconv.FormatInt(ugnot, 10), "by", by.String())
113	return id
114}
115
116// SetController names the next controller; nothing changes until they AcceptController. "" withdraws the offer.
117func SetController(cur realm, to address) {
118	mustControl(cur)
119	if to != "" && !to.IsValid() {
120		panic("adtreasury: not an address")
121	}
122	nextController = to
123	chain.Emit("ControllerNamed", "next", to.String())
124}
125
126// AcceptController takes the controller role, by the one named (a user or a realm).
127func AcceptController(cur realm) {
128	who := caller(cur)
129	if nextController == "" || who != nextController {
130		panic("adtreasury: not the one named")
131	}
132	prev := controller
133	controller, nextController = who, ""
134	chain.Emit("ControllerChanged", "from", prev.String(), "to", who.String())
135}
136
137// plain refuses text the page would render as markup.
138func plain(s string) {
139	if len(s) > maxMemo {
140		panic("adtreasury: the memo is at most " + strconv.Itoa(maxMemo) + " characters")
141	}
142	for i := 0; i < len(s); i++ {
143		if s[i] < 0x20 || s[i] == 0x7f || strings.IndexByte("[]()<>`*_#|\\!~", s[i]) >= 0 {
144			panic("adtreasury: the memo is plain text without markup")
145		}
146	}
147}
148
149// ---------------------------------------------------------------- pages --
150
151// Render: "" the treasury and its last spends; "json" its figures.
152func Render(path string) string {
153	switch path {
154	case "":
155		return home()
156	case "json":
157		return `{"address":"` + Address().String() + `","balance":` + strconv.FormatInt(Balance(), 10) + `,"spent":` + strconv.FormatInt(spent, 10) +
158			`,"controller":"` + controller.String() + `","next":"` + nextController.String() + `","spends":` + strconv.Itoa(len(spends)) + `}`
159	}
160	return "404"
161}
162
163func home() string {
164	var b strings.Builder
165	b.WriteString("# The ad station's treasury\n\n")
166	b.WriteString("What gno.world's ad station earns: each week's winning bids, from the station's auction. One controller spends it, every spend on the record below.\n\n")
167	b.WriteString("- holds: " + gnot(Balance()) + "\n")
168	b.WriteString("- spent so far: " + gnot(spent) + "\n")
169	b.WriteString("- controller: " + controller.String() + "\n")
170	if nextController != "" {
171		b.WriteString("- named to take over: " + nextController.String() + "\n")
172	}
173	b.WriteString("- address: " + Address().String() + "\n\n")
174	if len(spends) == 0 {
175		b.WriteString("_Nothing spent yet._\n")
176		return b.String()
177	}
178	b.WriteString("## Spends\n\n")
179	for i, n := len(spends)-1, 0; i >= 0 && n < listMax; i, n = i-1, n+1 {
180		s := spends[i]
181		b.WriteString("- #" + strconv.FormatInt(s.ID, 10) + " " + time.Unix(s.At, 0).UTC().Format("2006-01-02") + " · " + gnot(s.Amount) + " to " + s.To.String())
182		if s.Memo != "" {
183			b.WriteString(" · " + s.Memo)
184		}
185		b.WriteString("\n")
186	}
187	return b.String()
188}
189
190// gnot formats ugnot as GNOT, the trailing zeros trimmed.
191func gnot(ugnot int64) string {
192	whole, frac := ugnot/1_000_000, ugnot%1_000_000
193	if frac == 0 {
194		return strconv.FormatInt(whole, 10) + " GNOT"
195	}
196	f := strconv.FormatInt(1_000_000+frac, 10)[1:]
197	return strconv.FormatInt(whole, 10) + "." + strings.TrimRight(f, "0") + " GNOT"
198}
199

Raw Package Data

Raw JSON data