gno.land/r/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/nfts/skins⧉ Realm Open in gnoweb ↗
Kind Realm (renderable)
Name skins
Namespace g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd / gnofly / nfts
Exported functions 12
Module gno.land/r/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/nfts/skins
gno 0.9 json.gno gno
⧉
1 package skins 2 3 import ( 4 "errors" 5 "strings" 6 7 "gno.land/p/nt/grc721/v0" 8 BalanceOf (addr string) (int64, interface {Error func() string})
Burn (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id int64)
Buy (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, skinID string) int64
CatalogJSON () string
OwnerOf (tid string) (string, interface {Error func() string})
Render (path string) string
SetSkin (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id string, name string, model string, pilot string, body string, wing string, accent string, price int64, maxSupply int64)
SetSkinListed (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id string, listed bool)
SkinOf (id int64) string
SkinsOf (addr string) string
TokenMetadata (tid string) (string, interface {Error func() string})
TokenURI (tid string) (string, interface {Error func() string})
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
"gno.land/p/onbloc/json/v0"
9 )
10
11 // CatalogJSON returns every design, sorted by id, as a JSON array:
12 //
13 // [ { "id": "baron", "name": "Red Baron", "model": "biplane", "pilot": "none",
14 // "body": "#c1121f", "wing": "#780000", "accent": "#fdf0d5",
15 // "price": 1000000, "listed": true, "minted": 3, "maxSupply": 0 } ]
16 //
17 // The output has no whitespace. maxSupply 0 is unlimited. Classic, the free
18 // look, is not in it: the client draws it without a token.
19 func CatalogJSON() string {
20 items := []*json.Node{}
21 designs.Iterate( "" , "" , func (_ string , v any ) bool {
22 d := v.(*design)
23 b := json.Builder().
24 WriteString( "id" , d.id).
25 WriteString( "name" , d.name).
26 WriteString( "model" , d.model).
27 WriteString( "pilot" , d.pilot).
28 WriteString( "body" , d.body).
29 WriteString( "wing" , d.wing).
30 WriteString( "accent" , d.accent).
31 WriteNumber( "price" , float64 (d.price)).
32 WriteBool( "listed" , d.listed).
33 WriteNumber( "minted" , float64 (d.minted)).
34 WriteNumber( "maxSupply" , float64 (d.maxSupply))
35 items = append(items, b.Node())
36 return false
37 })
38 return encode(json.ArrayNode( "" , items))
39 }
40
41 // SkinsOf returns the designs addr owns a token of, sorted by id, as a JSON
42 // array: ["baron","goldie"]. It is [] for an address without skins.
43 func SkinsOf(addr address) string {
44 ids := []*json.Node{}
45 prefix := addr.String() + "/"
46 if validAddress(addr) {
47 // Keys sort as "<addr>/<design>": the designs of addr are one range,
48 // "/" + 1 is "0".
49 owned.Iterate(prefix, addr.String()+ "0" , func (key string , _ any ) bool {
50 ids = append(ids, json.StringNode( "" , strings.TrimPrefix(key, prefix)))
51 return false
52 })
53 }
54 return encode(json.ArrayNode( "" , ids))
55 }
56
57 // The read functions a wallet calls take a grc721.TokenID and return an
58 // error rather than panic, the way Adena calls them: OwnerOf("1") into two
59 // values, TokenURI("1") as the image it shows, TokenMetadata("1") as JSON
60 // for the traits. The errors are made on the call, not stored (errors.gno).
61
62 // OwnerOf returns who owns skin token tid, or an error when there is none.
63 func OwnerOf(tid grc721.TokenID) (address, error ) {
64 id, err := mintedID(tid)
65 if err != nil {
66 return "" , err
67 }
68 return ownerOf(id), nil
69 }
70
71 // BalanceOf returns how many skin tokens addr owns, one per design at most.
72 func BalanceOf(addr address) ( int64 , error ) {
73 return token.BalanceOf(addr)
74 }
75
76 // TokenMetadata returns the metadata of skin token tid as JSON, the same for
77 // every token of a design, with the keys Adena reads:
78 //
79 // {"name":"Red Baron","description":"A gnofly skin, a biplane. ...",
80 // "image":"https://gnofly.xyz/nft/skins/baron.png",
81 // "attributes":[{"traitType":"model","value":"biplane"}, ...]}
82 func TokenMetadata(tid grc721.TokenID) ( string , error ) {
83 id, err := mintedID(tid)
84 if err != nil {
85 return "" , err
86 }
87 d := getDesign(skinOf(id))
88 if d == nil {
89 return "" , errors.New(errNotMinted)
90 }
91 pilot := ""
92 if d.pilot == "gnome" {
93 pilot = " flown by a gnome"
94 }
95 b := json.Builder().
96 WriteString( "name" , d.name).
97 WriteString( "description" , "A gnofly skin, a " +d.model+pilot+ ". Bound to its buyer: it cannot be transferred." ).
98 WriteString( "image" , uriBase+d.id+ ".png" ).
99 WriteArray( "attributes" , func (ab *json.ArrayBuilder) {
100 for _, t := range [][ 2 ] string {
101 { "model" , d.model}, { "pilot" , d.pilot}, { "body" , d.body}, { "wing" , d.wing},
102 { "accent" , d.accent}, { "soulbound" , "yes" },
103 } {
104 ab.WriteObject( func (o *json.NodeBuilder) {
105 o.WriteString( "traitType" , t[ 0 ]).WriteString( "value" , t[ 1 ])
106 })
107 }
108 })
109 return encode(b.Node()), nil
110 }
111
112 // mintedID reads a token id, "1" and up in decimal, and checks it exists.
113 func mintedID(tid grc721.TokenID) ( int64 , error ) {
114 s := string (tid)
115 n := int64 ( 0 )
116 for i := 0 ; i < len(s); i++ {
117 if s[i] < '0' || s[i] > '9' || i > 15 || (i == 0 && s[i] == '0' ) {
118 return 0 , errors.New(errNotMinted)
119 }
120 n = n* 10 + int64 (s[i]- '0' )
121 }
122 if n == 0 || ownerOf(n) == "" {
123 return 0 , errors.New(errNotMinted)
124 }
125 return n, nil
126 }
127
128 // SkinOf returns the design of skin token id, "" when there is no such token.
129 func SkinOf(id int64 ) string {
130 return skinOf(id)
131 }
132
133 // TokenURI returns the image of skin token tid,
134 // "https://gnofly.xyz/nft/skins/<design>.png": what a wallet shows (Adena
135 // puts it in an <img>). The metadata is TokenMetadata, or
136 // "https://gnofly.xyz/nft/skins/<design>.json" on the website. It is
137 // computed, not stored: a token costs no URI in deposit.
138 func TokenURI(tid grc721.TokenID) ( string , error ) {
139 id, err := mintedID(tid)
140 if err != nil {
141 return "" , err
142 }
143 return uriBase + skinOf(id) + ".png" , nil
144 }
145
146 // encode marshals a document built above; it cannot fail on one.
147 func encode(n *json.Node) string {
148 out, err := json.Marshal(n)
149 if err != nil {
150 panic(err)
151 }
152 return string (out)
153 }
154
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.