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.

subtoken.gno

5.01 Kb · 185 lines
  1// Package subtoken is spikes (b) and (c) of tasarim-v6 §5.5:
  2//
  3//	(b) a grc721 token created with a sub-realm identity cur.Sub("c/1"):
  4//	    does NewToken accept it, what is Token.ID(), and can the host spend
  5//	    coins that land on a sub-address ("roy/1") with a banker bound to
  6//	    cur.Sub("roy/1")?
  7//	(c) a grc721 Extension that vetoes every transfer and burn until a
  8//	    reveal flag flips, while mints keep working.
  9//
 10// One token, one collection, no owner: anyone may call every function.
 11// Never part of the product.
 12package subtoken
 13
 14import (
 15	"chain"
 16	"chain/banker"
 17	"strconv"
 18
 19	"gno.land/p/nt/grc721/royalty/v0"
 20	"gno.land/p/nt/grc721/v0"
 21)
 22
 23const (
 24	tokenSub   = "c/1"
 25	royaltySub = "roy/1"
 26	royaltyBps = 500
 27)
 28
 29// lockExt is declared in this realm, so its hooks run with this realm's
 30// authority (borrow rule #1) even when another realm moves the token
 31// through a RealmTeller.
 32type lockExt struct {
 33	revealed bool
 34	moves    int
 35}
 36
 37func (e *lockExt) ExtensionKind() string { return "spike-lock" }
 38
 39func (e *lockExt) OnMint(to address, tid grc721.TokenID) {}
 40
 41func (e *lockExt) OnTransfer(from, to address, tid grc721.TokenID) {
 42	if !e.revealed {
 43		panic("spike-lock: transfers are locked until reveal")
 44	}
 45	e.moves++
 46}
 47
 48func (e *lockExt) OnBurn(tid grc721.TokenID) {
 49	if !e.revealed {
 50		panic("spike-lock: burns are locked until reveal")
 51	}
 52}
 53
 54var (
 55	tok     *grc721.Token
 56	ledger  *grc721.PrivateLedger
 57	roy     *royalty.Royalty
 58	lock    = &lockExt{}
 59	nextTID int64 = 1
 60)
 61
 62// Create builds the token once. The rlm handed to NewToken is a sub-realm
 63// token, so the hypothesis is Token.ID() == "<this pkgpath>#c/1.SPK.<seq>".
 64func Create(cur realm) string {
 65	if tok != nil {
 66		panic("already created")
 67	}
 68	t, led := grc721.NewToken("Spike", "SPK", 1, cur.Sub(tokenSub))
 69	led.RegisterExtension(lock)
 70	r, royLed := royalty.NewRoyalty(led, 1000)
 71	if err := royLed.SetDefaultRoyalty(chain.DerivePkgSubAddr(cur.PkgPath(), royaltySub), royaltyBps); err != nil {
 72		panic(err)
 73	}
 74	tok, ledger, roy = t, led, r
 75	return tok.ID()
 76}
 77
 78// Mint mints the next token to the caller; allowed while locked.
 79func Mint(cur realm) string {
 80	mustCreated()
 81	tid := strconv.FormatInt(nextTID, 10)
 82	nextTID++
 83	if err := ledger.Mint(cur.Previous().Address(), grc721.TokenID(tid)); err != nil {
 84		panic(err)
 85	}
 86	return tid
 87}
 88
 89func Approve(cur realm, tid string, to address) {
 90	mustCreated()
 91	if err := ledger.Approve(cur.Previous().Address(), to, grc721.TokenID(tid)); err != nil {
 92		panic(err)
 93	}
 94}
 95
 96func Transfer(cur realm, tid string, to address) {
 97	mustCreated()
 98	caller := cur.Previous().Address()
 99	if err := ledger.TransferFrom(caller, caller, to, grc721.TokenID(tid)); err != nil {
100		panic(err)
101	}
102}
103
104func Burn(cur realm, tid string) {
105	mustCreated()
106	owner, err := tok.OwnerOf(grc721.TokenID(tid))
107	if err != nil {
108		panic(err)
109	}
110	if owner != cur.Previous().Address() {
111		panic("only the owner can burn")
112	}
113	if err := ledger.Burn(grc721.TokenID(tid)); err != nil {
114		panic(err)
115	}
116}
117
118// Reveal lifts the lock forever.
119func Reveal(cur realm) {
120	lock.revealed = true
121}
122
123// Sweep spends the whole roy/1 sub-treasury to `to` with a banker bound to
124// the sub-realm identity (interrealm v2 §5.5 example).
125func Sweep(cur realm, to address) int64 {
126	sub := cur.Sub(royaltySub)
127	bal := banker.NewReadonlyBanker().GetCoin(sub.Address(), "ugnot")
128	if bal > 0 {
129		banker.NewBanker(banker.BankerTypeRealmSend, sub).
130			SendCoins(sub.Address(), to, chain.NewCoins(chain.NewCoin("ugnot", bal)))
131	}
132	return bal
133}
134
135// --- reads -----------------------------------------------------------------
136
137func Token() *grc721.Token { return tok }
138
139func TokenID() string {
140	mustCreated()
141	return tok.ID()
142}
143
144func OwnerOf(tid string) (address, error) {
145	mustCreated()
146	return tok.OwnerOf(grc721.TokenID(tid))
147}
148
149func Moves() int { return lock.moves }
150
151func IsRevealed() bool { return lock.revealed }
152
153// RoyaltyAddress is computed without cur, the way an off-chain tool or
154// another realm would; it must equal the receiver stored in the royalty.
155func RoyaltyAddress() address {
156	return chain.DerivePkgSubAddr("gno.land/r/g19dzk54hhsdc3gllpksuj6gu4a3e6fl8cr6rn4w/minhwa/spike/subtoken", royaltySub)
157}
158
159func RoyaltyInfo(tid string, price int64) (address, int64, error) {
160	mustCreated()
161	return roy.RoyaltyInfo(grc721.TokenID(tid), price)
162}
163
164func RoyaltyBalance() int64 {
165	return banker.NewReadonlyBanker().GetCoin(RoyaltyAddress(), "ugnot")
166}
167
168// Render also answers spike §5.5-6: does gnoweb display a data: SVG image?
169func Render(path string) string {
170	out := "# spike/subtoken\n\n"
171	if tok == nil {
172		return out + "not created yet\n"
173	}
174	out += "- token id: `" + tok.ID() + "`\n"
175	out += "- revealed: " + strconv.FormatBool(lock.revealed) + ", moves: " + strconv.Itoa(lock.moves) + "\n"
176	out += "- royalty address: `" + RoyaltyAddress().String() + "`\n\n"
177	out += "![data-uri test](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiBmaWxsPSIjMGI3Ii8+PC9zdmc+)\n"
178	return out
179}
180
181func mustCreated() {
182	if tok == nil {
183		panic("call Create first")
184	}
185}