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.

goldenticket.gno

4.25 Kb · 132 lines
  1// Package goldenticket is a 1/1 GRC721 NFT: one golden ticket, minted to the
  2// publisher when the package is added. It exists to check that a gno.land
  3// collection shows in the Adena wallet before gnofly's planes go to mainnet,
  4// so it is built the way the planes are:
  5//
  6//   - the collection is a gno.land/p/nt/grc721/v0 token made with NewToken,
  7//     whose NewToken and Transfer events Adena finds on its indexer;
  8//   - the read functions take a grc721.TokenID and return (value, error), as
  9//     Adena calls them: OwnerOf("1"), BalanceOf("g1..."), TokenURI("1") as
 10//     the <img src>, TokenMetadata("1") as JSON with camelCase traitType.
 11//
 12// Transfer moves the ticket, so it can reach any Adena account.
 13package goldenticket
 14
 15import (
 16	"errors"
 17	"strings"
 18
 19	"gno.land/p/nt/grc721/v0"
 20)
 21
 22const (
 23	tokenName   = "Golden Ticket"
 24	tokenSymbol = "GOLD"
 25	ticket      = grc721.TokenID("1")
 26
 27	// An https PNG, as the planes' images are: the SuperGnome of gnofly, from
 28	// the Blob store that serves the collection. Adena loads https: and data:
 29	// images only.
 30	image = "https://xjfvwq1pdzcznycc.public.blob.vercel-storage.com/planes/777.png"
 31
 32	errNoTicket = "goldenticket: there is one ticket, id 1"
 33	errNotOwner = "goldenticket: caller does not hold the ticket"
 34	errBadOwner = "goldenticket: invalid recipient address"
 35)
 36
 37var (
 38	// Token is the collection, read-only: Token.OwnerOf("1"), Token.BalanceOf.
 39	Token  *grc721.Token
 40	ledger *grc721.PrivateLedger
 41)
 42
 43// init runs once, when the package is added to the chain. This is the only
 44// moment cur.Previous() is the publisher, who receives the ticket.
 45//
 46// Under `gno test` there is no publisher and the address is empty. The tests
 47// call bootstrap themselves with a real address.
 48func init(cur realm) {
 49	bootstrap(cur.Previous().Address(), cur)
 50}
 51
 52// bootstrap creates the collection and mints the ticket to holder; with no
 53// valid holder, under `gno test`, it mints nothing.
 54func bootstrap(holder address, rlm realm) {
 55	Token, ledger = grc721.NewToken(tokenName, tokenSymbol, 0, rlm)
 56	if !holder.IsValid() {
 57		return
 58	}
 59	if err := ledger.Mint(holder, ticket); err != nil {
 60		panic(err)
 61	}
 62}
 63
 64// Name and Symbol, as GRC721 tokens have them. Adena reads both from the
 65// NewToken event.
 66func Name() string   { return tokenName }
 67func Symbol() string { return tokenSymbol }
 68
 69// OwnerOf returns who holds the ticket, or an error for any other id.
 70func OwnerOf(tid grc721.TokenID) (address, error) {
 71	if tid != ticket {
 72		return "", errors.New(errNoTicket)
 73	}
 74	return Token.OwnerOf(tid)
 75}
 76
 77// BalanceOf returns 1 for the holder of the ticket, 0 for everyone else.
 78func BalanceOf(addr address) (int64, error) {
 79	return Token.BalanceOf(addr)
 80}
 81
 82// TokenURI returns the image of the ticket: what Adena shows.
 83func TokenURI(tid grc721.TokenID) (string, error) {
 84	if _, err := OwnerOf(tid); err != nil {
 85		return "", err
 86	}
 87	return image, nil
 88}
 89
 90// TokenMetadata returns the metadata of the ticket as JSON, with the keys
 91// Adena reads.
 92func TokenMetadata(tid grc721.TokenID) (string, error) {
 93	if _, err := OwnerOf(tid); err != nil {
 94		return "", err
 95	}
 96	return `{"name":"` + tokenName + ` #1","description":"A 1/1 on gno.land, minted to check that collections show in Adena.",` +
 97		`"image":"` + image + `","attributes":[{"traitType":"edition","value":"1 of 1"},{"traitType":"metal","value":"gold"}]}`, nil
 98}
 99
100// Transfer gives the ticket to someone else. Only its holder can, by a
101// direct call (maketx call or Adena), not through another realm.
102func Transfer(cur realm, to address) {
103	caller := cur.Previous()
104	if !caller.IsUserCall() {
105		panic(errNotOwner)
106	}
107	if !to.IsValid() {
108		panic(errBadOwner)
109	}
110	holder, err := Token.OwnerOf(ticket)
111	if err != nil || holder != caller.Address() {
112		panic(errNotOwner)
113	}
114	if err := ledger.TransferFrom(holder, holder, to, ticket); err != nil {
115		panic(err)
116	}
117}
118
119// Render shows the ticket and who holds it.
120func Render(_ string) string {
121	var b strings.Builder
122	b.WriteString("# " + tokenName + "\n\n")
123	b.WriteString("A 1/1 GRC721 on gno.land, minted to check that collections show in Adena.\n\n")
124	b.WriteString("![" + tokenName + "](" + image + ")\n\n")
125	holder, err := Token.OwnerOf(ticket)
126	if err != nil {
127		b.WriteString("Not minted.\n")
128		return b.String()
129	}
130	b.WriteString("Held by `" + holder.String() + "`.\n")
131	return b.String()
132}