fishnft.gno
4.34 Kb · 165 lines
1// Package fishnft is the six model tokens, one for each sea GLB.
2// It takes no GNOT, draws no FISH, and does not replace shoal/fish.
3package fishnft
4
5import (
6 "chain"
7 "chain/runtime/unsafe"
8 "errors"
9 "strconv"
10 "strings"
11
12 "gno.land/p/nt/grc721/metadata/v0"
13 "gno.land/p/nt/grc721/v0"
14 "gno.land/p/nt/seqid/v0"
15)
16
17type body struct {
18 id int
19 rarity string
20 species string
21 file string
22 owner address
23 uri string
24}
25
26var (
27 token *grc721.Token
28 ledger *grc721.PrivateLedger
29 metaLed *metadata.Ledger
30 tokenSeq seqid.ID
31 bodies []*body
32 minted int
33)
34
35// Init issues token 1 through 6 to the caller. A second call does nothing useful and panics.
36func Init(cur realm) {
37 if !cur.IsCurrent() {
38 panic("fishnft: realm")
39 }
40 if !cur.Previous().IsUserCall() {
41 panic("fishnft: eoa")
42 }
43 if token != nil {
44 panic("fishnft: init")
45 }
46 rejectPay()
47 tok, led := grc721.NewToken("Shoal Models", "MODEL", tokenSeq.Next(), cur)
48 token = tok
49 ledger = led
50 _, metaLed = metadata.NewMetadata(led)
51 bodies = []*body{nil}
52 caller := cur.Previous().Address()
53 issue(caller, "common", "Minnow", "common.glb")
54 issue(caller, "uncommon", "Mackerel", "uncommon.glb")
55 issue(caller, "rare", "Snapper", "rare.glb")
56 issue(caller, "epic", "Angler", "epic.glb")
57 issue(caller, "legendary", "Koi Dragon", "legendary.glb")
58 issue(caller, "mythic", "Abyss Leviathan", "mythic.glb")
59 chain.Emit("Init", "owner", caller.String(), "minted", strconv.Itoa(minted))
60}
61
62// Transfer moves one model token. The caller must own it, and the call must send no coins.
63func Transfer(cur realm, to address, id int) {
64 if !cur.IsCurrent() {
65 panic("fishnft: realm")
66 }
67 if !cur.Previous().IsUserCall() {
68 panic("fishnft: eoa")
69 }
70 if token == nil {
71 panic("fishnft: init")
72 }
73 rejectPay()
74 if !to.IsValid() || to == cur.Previous().Address() {
75 panic("fishnft: addr")
76 }
77 row := must(id)
78 caller := cur.Previous().Address()
79 if row.owner != caller {
80 panic("fishnft: owner")
81 }
82 tid := grc721.TokenID(strconv.Itoa(id))
83 if err := ledger.TransferFrom(caller, caller, to, tid); err != nil {
84 panic("fishnft: transfer")
85 }
86 row.owner = to
87 chain.Emit("Transfer", "id", strconv.Itoa(id), "to", to.String())
88}
89
90func Version() string { return "fishnft-1" }
91func Minted() int { return minted }
92func Name() string { return "Shoal Models" }
93func Symbol() string { return "MODEL" }
94
95func RarityOf(id int) string { return must(id).rarity }
96func SpeciesOf(id int) string { return must(id).species }
97func ModelOf(id int) string { return must(id).file }
98func TokenURI(id int) string { return must(id).uri }
99
100func OwnerOf(tid string) (address, error) {
101 id, err := strconv.Atoi(tid)
102 if err != nil || token == nil || id < 1 || id >= len(bodies) || bodies[id] == nil {
103 return address(""), errors.New("fishnft: unknown")
104 }
105 return bodies[id].owner, nil
106}
107
108func Render(path string) string {
109 if path != "" {
110 return "# Shoal model NFTs\n\nThat path is not a token page.\n"
111 }
112 if token == nil {
113 return "# Shoal model NFTs\n\nNot issued.\n"
114 }
115 var b strings.Builder
116 b.WriteString("# Shoal model NFTs\n\nSix tokens. No GNOT. This is not shoal/fish.\n\n")
117 for _, row := range bodies {
118 if row == nil {
119 continue
120 }
121 b.WriteString("- ")
122 b.WriteString(strconv.Itoa(row.id))
123 b.WriteString(" ")
124 b.WriteString(row.species)
125 b.WriteString(" ")
126 b.WriteString(row.rarity)
127 b.WriteString(" ")
128 b.WriteString(row.file)
129 b.WriteString("\n")
130 }
131 return b.String()
132}
133
134func issue(to address, rarity, species, file string) {
135 minted++
136 id := minted
137 uri := "data:application/json;charset=utf-8," +
138 `{"name":"` + species + `","description":"Shoal model ` + file +
139 `","attributes":[{"trait_type":"Rarity","value":"` + rarity +
140 `"},{"trait_type":"Species","value":"` + species +
141 `"},{"trait_type":"Model","value":"` + file + `"}]}`
142 row := &body{id: id, rarity: rarity, species: species, file: file, owner: to, uri: uri}
143 bodies = append(bodies, row)
144 tid := grc721.TokenID(strconv.Itoa(id))
145 if err := ledger.Mint(to, tid); err != nil {
146 panic("fishnft: mint")
147 }
148 if err := metaLed.SetTokenURI(tid, uri); err != nil {
149 panic("fishnft: uri")
150 }
151 chain.Emit("Mint", "id", strconv.Itoa(id), "model", file)
152}
153
154func must(id int) *body {
155 if token == nil || id < 1 || id >= len(bodies) || bodies[id] == nil {
156 panic("fishnft: unknown")
157 }
158 return bodies[id]
159}
160
161func rejectPay() {
162 if len(unsafe.OriginSend()) != 0 {
163 panic("fishnft: payment")
164 }
165}