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.

planes.gno

7.77 Kb · 237 lines
  1package planes
  2
  3import (
  4	"chain"
  5	"strconv"
  6	"strings"
  7
  8	"gno.land/p/nt/bptree/v0"
  9	"gno.land/p/nt/grc721/royalty/v0"
 10	"gno.land/p/nt/grc721/v0"
 11)
 12
 13// The read side of the collection, exported for other realms and for
 14// vm/qeval, as GRC721 tokens are on gno.land. Neither type has a mutator: a
 15// Token hands out tellers that act as the calling realm and go through the
 16// ledger's approval checks, Royalty only reads. There is no metadata
 17// extension: the traits are in the table, read with TokenMetadata(tid) and
 18// PlaneJSON(id), and the token URI, the image, is computed by TokenURI(tid).
 19//
 20//	planes.TokenMetadata("42")
 21//	planes.Token.RealmTeller(0, cur).TransferFrom(0, cur, from, to, "42")
 22var (
 23	Token     *grc721.Token    // name, symbol, owners, balances, approvals
 24	Royalties *royalty.Royalty // EIP-2981 royalty
 25)
 26
 27// The rest of the state. Everything is unexported and nothing below is ever
 28// returned by reference: the ledgers and the trees are /p/ types with mutator
 29// methods, so handing one out would be handing out write access to the
 30// collection.
 31var (
 32	ledger        *grc721.PrivateLedger // the collection, write side: mint, burn
 33	teller        grc721.Teller         // acts as the caller of this realm: transfers and approvals
 34	royaltyLedger *royalty.Ledger       // royalty, write side
 35
 36	mintCount int64  // planes minted so far, burned ones included; the ids left are in pool (draw.gno)
 37	price     int64  // ugnot, what a mint costs
 38	mintOpen  bool   // whether Mint is accepting calls
 39	mintCap   int64  // how many planes one wallet may mint, over its lifetime
 40	imageBase string // "https://gnofly.xyz/nft/collections/planes/", the image of plane 42 is at <base>042.png
 41
 42	mints      bptree.BPTree // minter address -> int64, planes minted so far, for the cap
 43	tierMinted [4]int64      // planes minted so far per tier, in the order of tiers
 44	recent     [5]int64      // the last ids minted, newest first, 0 for none, for Render
 45
 46	webPath string // "/r/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/nfts/planes", used to build links in Render
 47)
 48
 49// The collection, and the settings at deploy.
 50const (
 51	supply      = int64(777)
 52	tokenName   = "gnofly planes"
 53	tokenSymbol = "PLANE"
 54	payDenom    = "ugnot" // the only coin planes are sold for
 55
 56	defaultPrice     = int64(77000000) // 77 GNOT
 57	minPrice         = int64(1000000)
 58	maxPrice         = int64(10000000000)
 59	defaultMintCap   = int64(7)
 60	defaultImageBase = "https://gnofly.xyz/nft/collections/planes/"
 61
 62	defaultRoyaltyBps = int64(500)  // 5% of a resale, to the treasury
 63	maxRoyaltyBps     = int64(1000) // the most SetRoyaltyBps accepts
 64
 65	// Models are what the game client renders, tiers are what the table
 66	// says. Both are validated against these lists.
 67	models = "prop,biplane,stunt,jet,seaplane,twin,delta,glider,supergnome"
 68	pilots = "none,gnome"
 69	tiers  = "common,rare,epic,legendary"
 70)
 71
 72// tierSupply is how many planes of each tier the table holds, in the order
 73// of tiers. The tests check it against the table.
 74var tierSupply = [4]int64{500, 200, 70, 7}
 75
 76// tierIndex returns the position of tier in tiers, or -1.
 77func tierIndex(tier string) int {
 78	for i, t := range strings.Split(tiers, ",") {
 79		if t == tier {
 80			return i
 81		}
 82	}
 83	return -1
 84}
 85
 86// init runs once, when the package is added to the chain. The realm has
 87// nothing to remember about its deployer: it has no role of its own.
 88func init(cur realm) {
 89	bootstrap(0, cur)
 90}
 91
 92// bootstrap (re)creates the initial state: the token, its extensions and the
 93// settings. The tests call it to start from a clean slate. rlm is the live
 94// realm of the caller: grc721 records its package path as the home of the
 95// Token.
 96func bootstrap(_ int, rlm realm) {
 97	// A table of the wrong shape is refused before anything is written, so
 98	// that a bad hand edit fails at publish rather than at the first mint.
 99	if first, last := rowID(0), rowID(len(table)-1); first != 1 || last != supply {
100		panic(errBadTable)
101	}
102
103	Token, ledger = grc721.NewToken(tokenName, tokenSymbol, 0, rlm)
104	// Minted from the ledger: the frame-relative teller is confined to this
105	// realm, which is where it is used.
106	teller = ledger.CallerTeller()
107	Royalties, royaltyLedger = royalty.NewRoyalty(ledger, maxRoyaltyBps)
108
109	mintCount = 0
110	resetPool(rlm.PkgPath())
111	recent = [5]int64{}
112	price = defaultPrice
113	mintOpen = false
114	mintCap = defaultMintCap
115	imageBase = defaultImageBase
116	mints = bptree.BPTree{}
117	tierMinted = [4]int64{}
118	webPath = webPathOf(rlm.PkgPath())
119
120	// Under `gno test` the game realm has no treasury yet (see roles.gno):
121	// the tests set one and call bootstrap again.
122	if roles.Treasury().IsValid() {
123		setRoyalty(defaultRoyaltyBps)
124	}
125}
126
127// setRoyalty points the royalty of every plane at the treasury of the game
128// realm, as it is now, at bps basis points.
129func setRoyalty(bps int64) {
130	if err := royaltyLedger.SetDefaultRoyalty(roles.Treasury(), bps); err != nil {
131		panic(err)
132	}
133}
134
135// SetMintOpen opens or closes the mint. Admin only. The mint starts closed.
136func SetMintOpen(cur realm, open bool) {
137	assertAdmin(0, cur)
138	mintOpen = open
139	chain.Emit("MintOpen", "open", boolString(open))
140}
141
142// SetPrice sets what a mint costs, from 1 to 10000 GNOT. Admin only. It
143// applies to the next mint.
144func SetPrice(cur realm, ugnot int64) {
145	assertAdmin(0, cur)
146	if ugnot < minPrice || ugnot > maxPrice {
147		panic(errInvalidPrice)
148	}
149	price = ugnot
150	chain.Emit("PriceSet", "price", itoa(ugnot))
151}
152
153// SetMintCap sets how many planes one wallet may mint over its lifetime, from
154// 1 to 777. Admin only. A wallet already over a lowered cap keeps its planes
155// and mints no more.
156func SetMintCap(cur realm, n int64) {
157	assertAdmin(0, cur)
158	if n < 1 || n > supply {
159		panic(errInvalidCap)
160	}
161	mintCap = n
162	chain.Emit("MintCapSet", "cap", itoa(n))
163}
164
165// SetImageBase sets where the images and the metadata of the planes are
166// served from: the image of plane 42 is <base>042.png and its token URI is
167// <base>42.json. Admin only.
168//
169// It applies at once to every plane: the URLs are computed on read, nothing
170// is stored per token.
171func SetImageBase(cur realm, base string) {
172	assertAdmin(0, cur)
173	if !validBase(base) {
174		panic(errInvalidBase)
175	}
176	imageBase = base
177	chain.Emit("ImageBaseSet", "base", base)
178}
179
180// SetRoyaltyBps sets the royalty of a resale, from 0 to 1000 basis points,
181// and points it at the treasury of the game realm as it is now. Admin only.
182//
183// The royalty is advisory, as EIP-2981 is: a marketplace reads RoyaltyInfo
184// and pays it, the realm cannot enforce it on a transfer.
185func SetRoyaltyBps(cur realm, bps int64) {
186	assertAdmin(0, cur)
187	if bps < 0 || bps > maxRoyaltyBps {
188		panic(errInvalidRoyalty)
189	}
190	setRoyalty(bps)
191}
192
193// Minted returns how many planes were minted so far, burned ones included.
194// Planes are drawn at random, so it says nothing of which ids. The
195// settings (price, open, cap, image base, royalty) are read with
196// CollectionJSON: every exported function costs storage at deploy, measured
197// at about 650 bytes each, so there is no getter per setting.
198func Minted() int64 {
199	return mintCount
200}
201
202// webPathOf turns "gno.land/r/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/nfts/planes" into "/r/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/nfts/planes".
203func webPathOf(pkgPath string) string {
204	for i := 0; i < len(pkgPath); i++ {
205		if pkgPath[i] == '/' {
206			return pkgPath[i:]
207		}
208	}
209	return ""
210}
211
212// imageURL returns where the image of plane id is: <image base><id>.png,
213// the id on three digits, "042.png", as the image files are named. The token
214// URI keeps the plain id, "42.json".
215func imageURL(id int64) string {
216	return imageBase + pad3(id) + ".png"
217}
218
219// pad3 writes an id of 1 to 777 on three digits: 7 -> "007", 42 -> "042".
220func pad3(id int64) string {
221	s := itoa(id)
222	for len(s) < 3 {
223		s = "0" + s
224	}
225	return s
226}
227
228func itoa(n int64) string {
229	return strconv.FormatInt(n, 10)
230}
231
232func boolString(b bool) string {
233	if b {
234		return "true"
235	}
236	return "false"
237}