Kind Realm (renderable)
Name gnft
Namespace gnoswap
Exported functions 17
Module gno.land/r/gnoswap/gnft
gno 0.9 svg_generator.gno gno
⧉
1 package gnft 2 3 import ( 4 b64 "encoding/base64" 5 "errors" 6 "math/rand" 7 "strconv" 8 Approve (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}, approved string, tid string) interface {Error func() string}
BalanceOf (owner 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}, tid string)
Exists (tid string) bool
GetApproved (tid string) (string, interface {Error func() string})
IsApprovedForAll (owner string, operator string) bool
Mint (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}, to string, tid string) string
MustOwnerOf (tid string) string
Name () string
OwnerOf (tid string) (string, interface {Error func() string})
Render (path string) string
SetApprovalForAll (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}, operator string, approved bool) interface {Error func() string}
SetTokenURI (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}, tid string, tURI string) (bool, interface {Error func() string})
Symbol () string
TokenURI (tid string) (string, interface {Error func() string})
TotalSupply () int64
TransferFrom (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}, from string, to string, tid string) interface {Error func() string}
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
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.
"strings"
9
10 "gno.land/p/nt/grc721/v0"
11 ufmt "gno.land/p/nt/ufmt/v0"
12 )
13
14 // MUST BE IMMUTABLE, DO NOT MODIFY.
15 // SVG template structure:
16 // The template is split at variable insertion points for efficient string concatenation.
17 // Full template with placeholders (for reference):
18 //
19 // <svg width="135" height="135" viewBox="0 0 135 135" fill="none" xmlns="...">
20 // <g clip-path="url(#clip0_7698_56846)">
21 // <circle cx="67.5" cy="67.5" r="67.5" fill="url(#paint0_linear_7698_56846)"/>
22 // ... (path elements) ...
23 // </g>
24 // <defs>
25 // <linearGradient id="paint0_linear_7698_56846"
26 // x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" <-- variables
27 // gradientUnits="userSpaceOnUse">
28 // <stop stop-color="{color1}"/> <-- variable
29 // <stop offset="1" stop-color="{color2}"/> <-- variable
30 // </linearGradient>
31 // ...
32 // </defs>
33 // </svg>
34
35 // svgTemplate holds pre-split template parts for efficient concatenation.
36 // Usage: svgTemplate[0] + clipID + svgTemplate[1] + paintID + ... + color2 + svgTemplate[10]
37 var svgTemplate = [ 11 ] string {
38 // [0] SVG header and body (before clip-path id)
39 `<svg width="135" height="135" viewBox="0 0 135 135" fill="none" xmlns="http://www.w3.org/2000/svg">
40 <g clip-path="url(#`,
41 // [1] between clip-path id and fill id
42 `)">
43 <circle cx= "67.5" cy= "67.5" r= "67.5" fill="url(#`,
44 // [2] between fill id and gradient id
45 `)"/>
46 <path d= "M51.2905 42.9449L66.4895 33L97 52.8061L81.8241 62.7425L51.2905 42.9449Z" fill= "white" />
47 <path d= "M51.6055 67.5059L66.8044 57.561L97 77.0657L82.1046 87.1793L51.6055 67.5059Z" fill= "white" fill-opacity= "0.4" />
48 <path d= "M36.0464 81.7559L51.2905 71.811L81.7336 91.6547L66.4895 101.508L36.0464 81.7559Z" fill= "white" fill-opacity= "0.6" />
49 <path d= "M36.001 52.8055L51.2884 42.9177L51.2884 71.8145L36.001 81.779L36.001 52.8055Z" fill= "white" />
50 <path d= "M82.1051 87.1797L97.0016 77.0662L97.0016 81.7029L81.7896 91.629L82.1051 87.1797Z" fill= "white" fill-opacity= "0.5" />
51 </g>
52 <defs>
53 <linearGradient id="`,
54 // [3] between gradient id and x1
55 `" x1="` ,
56 // [4] between x1 and y1
57 `" y1="` ,
58 // [5] between y1 and x2
59 `" x2="` ,
60 // [6] between x2 and y2
61 `" y2="` ,
62 // [7] between y2 and color1
63 `" gradientUnits="userSpaceOnUse">
64 <stop stop-color="`,
65 // [8] between color1 and color2
66 `"/>
67 <stop offset= "1" stop-color="`,
68 // [9] between color2 and clipPath id
69 `"/>
70 </linearGradient>
71 <clipPath id="`,
72 // [10] SVG footer (after clipPath id)
73 `">
74 <rect width= "135" height= "135" fill= "white" />
75 </clipPath>
76 </defs>
77 </svg>
78 `,
79 }
80
81 // charset contains valid hex digits for color generation.
82 const charset = "0123456789ABCDEF"
83
84 // Parameter range constants for gradient coordinates.
85 const (
86 x1Min = 7
87 x1Max = 13
88 y1Min = 7
89 y1Max = 13
90 x2Min = 121
91 x2Max = 126
92 y2Min = 121
93 y2Max = 126
94
95 x1Range = x1Max - x1Min + 1
96 y1Range = y1Max - y1Min + 1
97 x2Range = x2Max - x2Min + 1
98 y2Range = y2Max - y2Min + 1
99 )
100
101 // genImageParamsString generates random gradient parameters and returns them as a compact string.
102 // Format: "x1,y1,x2,y2,color1,color2" (e.g., "10,12,125,123,#AABBCC,#DDEEFF")
103 func genImageParamsString(r *rand.Rand) string {
104 x1 := x1Min + r.Uint64N(x1Range)
105 y1 := y1Min + r.Uint64N(y1Range)
106 x2 := x2Min + r.Uint64N(x2Range)
107 y2 := y2Min + r.Uint64N(y2Range)
108
109 var buf1 [ 7 ] byte
110 var buf2 [ 7 ] byte
111 buf1[ 0 ] = '#'
112 buf2[ 0 ] = '#'
113 for i := 1 ; i < 7 ; i++ {
114 buf1[i] = charset[r.IntN( 16 )]
115 buf2[i] = charset[r.IntN( 16 )]
116 }
117 color1 := string (buf1[:])
118 color2 := string (buf2[:])
119
120 return strconv.Itoa( int (x1)) + "," + strconv.Itoa( int (y1)) + "," +
121 strconv.Itoa( int (x2)) + "," + strconv.Itoa( int (y2)) + "," +
122 color1 + "," + color2
123 }
124
125 // ImageParams holds parsed and validated image parameters.
126 type ImageParams struct {
127 x1, y1, x2, y2 int
128 color1, color2 string
129 }
130
131 // validateCoordinate checks if a coordinate value is within valid range.
132 // Returns error with details about which coordinate failed if out of range.
133 func validateCoordinate(value int , min int , max int , name string ) error {
134 if value < min || value > max {
135 details := ufmt.Sprintf( "%s=%d (expected range: [%d, %d])" , name, value, min, max)
136 return makeErrorWithDetails(errInvalidTokenParamsRange, details)
137 }
138 return nil
139 }
140
141 // parseImageParams parses and validates parameters in one step.
142 // Returns parsed ImageParams pointer or error.
143 // Returns nil on error to avoid allocating zero-value struct.
144 // Expected format: "x1,y1,x2,y2,color1,color2"
145 func parseImageParams(s string ) (*ImageParams, error ) {
146 parts := strings.Split(s, "," )
147 if len(parts) != 6 {
148 return nil , errors.New(errInvalidTokenParams)
149 }
150
151 x1, err := strconv.Atoi(parts[ 0 ])
152 if err != nil {
153 return nil , errors.New(errInvalidTokenParams)
154 }
155
156 y1, err := strconv.Atoi(parts[ 1 ])
157 if err != nil {
158 return nil , errors.New(errInvalidTokenParams)
159 }
160
161 x2, err := strconv.Atoi(parts[ 2 ])
162 if err != nil {
163 return nil , errors.New(errInvalidTokenParams)
164 }
165
166 y2, err := strconv.Atoi(parts[ 3 ])
167 if err != nil {
168 return nil , errors.New(errInvalidTokenParams)
169 }
170
171 // Validate coordinate ranges with detailed error messages
172 if err := validateCoordinate(x1, x1Min, x1Max, "x1" ); err != nil {
173 return nil , err
174 }
175 if err := validateCoordinate(y1, y1Min, y1Max, "y1" ); err != nil {
176 return nil , err
177 }
178 if err := validateCoordinate(x2, x2Min, x2Max, "x2" ); err != nil {
179 return nil , err
180 }
181 if err := validateCoordinate(y2, y2Min, y2Max, "y2" ); err != nil {
182 return nil , err
183 }
184
185 color1 := parts[ 4 ]
186 color2 := parts[ 5 ]
187
188 // Validate color format (#XXXXXX)
189 if !isValidHexColor(color1) || !isValidHexColor(color2) {
190 return nil , errors.New(errInvalidColorFormat)
191 }
192
193 return &ImageParams{
194 x1: x1,
195 y1: y1,
196 x2: x2,
197 y2: y2,
198 color1: color1,
199 color2: color2,
200 }, nil
201 }
202
203 // generateImageURI converts parsed image parameters to a base64-encoded SVG image URI.
204 func (params ImageParams) generateImageURI(tid grc721.TokenID) string {
205 svg := params.generateSVG(tid)
206 sEnc := b64.StdEncoding.EncodeToString([] byte (svg))
207
208 return "data:image/svg+xml;base64," + sEnc
209 }
210
211 // generateSVG generates SVG image from parsed and validated parameters.
212 func (params ImageParams) generateSVG(tid grc721.TokenID) string {
213 suffix := svgIDSuffix(tid)
214 paintID := "paint0_linear_" + suffix
215 clipID := "clip0_" + suffix
216 return svgTemplate[ 0 ] + clipID + svgTemplate[ 1 ] + paintID + svgTemplate[ 2 ] + paintID +
217 svgTemplate[ 3 ] + strconv.Itoa(params.x1) + svgTemplate[ 4 ] + strconv.Itoa(params.y1) +
218 svgTemplate[ 5 ] + strconv.Itoa(params.x2) + svgTemplate[ 6 ] + strconv.Itoa(params.y2) +
219 svgTemplate[ 7 ] + params.color1 + svgTemplate[ 8 ] + params.color2 + svgTemplate[ 9 ] +
220 clipID + svgTemplate[ 10 ]
221 }
222
223 func svgIDSuffix(tid grc721.TokenID) string {
224 return "gnft_" + string (tid)
225 }
226
227 // isValidHexColor checks if a string is a valid hex color in #XXXXXX format.
228 func isValidHexColor(color string ) bool {
229 if len(color) != 7 || color[ 0 ] != '#' {
230 return false
231 }
232
233 for i := 1 ; i < 7 ; i++ {
234 c := color[i]
235
236 isHex := (c >= '0' && c <= '9' ) || (c >= 'A' && c <= 'F' ) || (c >= 'a' && c <= 'f' )
237 if !isHex {
238 return false
239 }
240 }
241
242 return true
243 }
244