Kind Realm (renderable)
Name craft
Namespace akkadia / beta
Exported functions 14
Module gno.land/r/akkadia/beta/craft
gno 0.9 craft.gno gno
⧉
1 package craft 2 3 import ( 4 "chain" 5 "chain/banker" 6 "chain/runtime/unsafe" 7 "strconv" 8 Craft (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}, recipe string) uint64
DeleteRecipe (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}, recipe string)
DeleteRecipes (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}, recipesCSV string)
GetCraftFee () int64
GetListLimit () int
GetRecipe (recipe string) uint32
GetRecipeCount () int
HasRecipe (recipe string) bool
ListRecipes (page int, count int) []map[string]string
Render (iURL string) string
SetCraftFee (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}, fee int64)
SetListLimit (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}, limit int)
SetRecipe (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}, recipe string, itemID uint32)
SetRecipes (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}, recipesCSV string, itemIDsCSV 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.
9 "gno.land/p/akkadia/beta/accesscontrol"
10 "gno.land/p/nt/bptree/v0"
11 "gno.land/r/akkadia/beta/admin"
12 "gno.land/r/akkadia/beta/equipment"
13 "gno.land/r/akkadia/beta/item"
14 )
15
16 const (
17 SetRecipeEvent = "SetRecipe"
18 DeleteRecipeEvent = "DeleteRecipe"
19 DeleteRecipesEvent = "DeleteRecipes"
20 SetListLimitEvent = "SetListLimit"
21 SetCraftFeeEvent = "SetCraftFee"
22 CraftEvent = "Craft"
23 )
24
25 var (
26 recipes = bptree.NewBPTree32()
27 listLimit = defaultRecipeListLimit
28 craftFee = defaultCraftFee
29 )
30
31 func init() { resetCraftState() }
32
33 func resetCraftState() {
34 recipes = bptree.NewBPTree32()
35 listLimit = defaultRecipeListLimit
36 craftFee = defaultCraftFee
37 }
38
39 // SetRecipe stores the item definition ID produced by a canonical recipe.
40 func SetRecipe(cur realm, recipe string , itemID uint32 ) {
41 accesscontrol.AssertIsAdmin( 0 , cur, admin.IsAdmin)
42 assertCraftNoCoins()
43 canonicalRecipe, _ := mustParseRecipe(recipe)
44 setRecipe(canonicalRecipe, itemID)
45 }
46
47 // SetRecipes stores up to 100 canonical recipe and item ID pairs in one transaction.
48 func SetRecipes(cur realm, recipesCSV, itemIDsCSV string ) {
49 accesscontrol.AssertIsAdmin( 0 , cur, admin.IsAdmin)
50 assertCraftNoCoins()
51 canonicalRecipes, itemIDs := mustParseRecipeCSV(recipesCSV, itemIDsCSV)
52 for i, recipe := range canonicalRecipes {
53 setRecipe(recipe, itemIDs[i])
54 }
55 }
56
57 func setRecipe(canonicalRecipe string , itemID uint32 ) {
58 recipes.Set(canonicalRecipe, itemID)
59 chain.Emit(
60 SetRecipeEvent,
61 "recipe" , canonicalRecipe,
62 "itemId" , strconv.FormatUint( uint64 (itemID), 10 ),
63 )
64 }
65
66 func DeleteRecipe(cur realm, recipe string ) {
67 accesscontrol.AssertIsAdmin( 0 , cur, admin.IsAdmin)
68 assertCraftNoCoins()
69 canonicalRecipe := mustCanonicalRecipe(recipe)
70 deleteRecipe(canonicalRecipe)
71 chain.Emit(DeleteRecipeEvent, "recipe" , canonicalRecipe)
72 }
73
74 func DeleteRecipes(cur realm, recipesCSV string ) {
75 accesscontrol.AssertIsAdmin( 0 , cur, admin.IsAdmin)
76 assertCraftNoCoins()
77 canonicalRecipes := mustParseRecipeKeyCSV(recipesCSV)
78 for _, recipe := range canonicalRecipes {
79 if !recipes.Has(recipe) {
80 panic( "recipe not found: " + recipe)
81 }
82 }
83 for _, recipe := range canonicalRecipes {
84 deleteRecipe(recipe)
85 }
86 chain.Emit(DeleteRecipesEvent, "count" , strconv.Itoa(len(canonicalRecipes)))
87 }
88
89 func deleteRecipe(canonicalRecipe string ) {
90 if _, removed := recipes.Remove(canonicalRecipe); !removed {
91 panic( "recipe not found: " + canonicalRecipe)
92 }
93 }
94
95 func HasRecipe(recipe string ) bool {
96 canonical, _, errMsg := parseRecipe(recipe)
97 if errMsg != "" {
98 return false
99 }
100 return recipes.Has(canonical)
101 }
102
103 func GetRecipe(recipe string ) uint32 {
104 return mustGetRecipeItemID(mustCanonicalRecipe(recipe))
105 }
106
107 func GetRecipeCount() int {
108 return recipes.Size()
109 }
110
111 func ListRecipes(page, count int ) [] map [ string ] string {
112 assertRecipeListPageCount(page, count)
113 result := [] map [ string ] string {}
114 recipes.IterateByOffset((page- 1 )*count, count, func (recipe string , value any ) bool {
115 result = append(result, map [ string ] string {
116 "recipe" : recipe,
117 "itemId" : strconv.FormatUint( uint64 (value.( uint32 )), 10 ),
118 })
119 return false
120 })
121 return result
122 }
123
124 func SetListLimit(cur realm, limit int ) {
125 accesscontrol.AssertIsAdmin( 0 , cur, admin.IsAdmin)
126 assertCraftNoCoins()
127 if limit < 1 {
128 panic( "limit must be at least 1" )
129 }
130
131 oldLimit := listLimit
132 listLimit = limit
133 chain.Emit(
134 SetListLimitEvent,
135 "oldLimit" , strconv.Itoa(oldLimit),
136 "newLimit" , strconv.Itoa(limit),
137 )
138 }
139
140 func GetListLimit() int {
141 return listLimit
142 }
143
144 func SetCraftFee(cur realm, fee int64 ) {
145 accesscontrol.AssertIsAdmin( 0 , cur, admin.IsAdmin)
146 assertCraftNoCoins()
147 if fee < 0 {
148 panic( "fee must be non-negative" )
149 }
150
151 oldFee := craftFee
152 craftFee = fee
153 chain.Emit(
154 SetCraftFeeEvent,
155 "oldFee" , strconv.FormatInt(oldFee, 10 ),
156 "newFee" , strconv.FormatInt(fee, 10 ),
157 )
158 }
159
160 func GetCraftFee() int64 {
161 return craftFee
162 }
163
164 // Craft burns a canonical recipe and mints the next GRC721 equipment token.
165 func Craft(cur realm, recipe string ) uint64 {
166 caller := accesscontrol.MustGetUserCaller( 0 , cur)
167 canonicalRecipe, ingredients := mustParseRecipe(recipe)
168 itemID := mustGetRecipeItemID(canonicalRecipe)
169 assertExactUgnotPayment(craftFee)
170
171 for _, ingredient := range ingredients {
172 item.BurnFromRealm(cross(cur), caller, ingredient.TokenID, ingredient.Amount)
173 }
174 tokenID := equipment.MintFromRealm(cross(cur), caller, itemID, "" , "" )
175 if craftFee > 0 {
176 bnk := banker.NewBanker(banker.BankerTypeRealmSend, cur)
177 bnk.SendCoins(cur.Address(), admin.GetFeeCollector(), chain.Coins{{ "ugnot" , craftFee}})
178 }
179
180 chain.Emit(
181 CraftEvent,
182 "owner" , caller.String(),
183 "recipe" , canonicalRecipe,
184 "equipmentTokenId" , strconv.FormatUint(tokenID, 10 ),
185 "itemId" , strconv.FormatUint( uint64 (itemID), 10 ),
186 "fee" , strconv.FormatInt(craftFee, 10 ),
187 )
188 return tokenID
189 }
190
191 func mustCanonicalRecipe(recipe string ) string {
192 canonical, _ := mustParseRecipe(recipe)
193 return canonical
194 }
195
196 func mustGetRecipeItemID(recipe string ) uint32 {
197 value := recipes.Get(recipe)
198 if value == nil {
199 panic( "recipe not found: " + recipe)
200 }
201 return value.( uint32 )
202 }
203
204 func assertCraftNoCoins() {
205 if len(unsafe.OriginSend()) != 0 {
206 panic( "attached coins not allowed" )
207 }
208 }
209
210 func assertExactUgnotPayment(expected int64 ) {
211 coins := unsafe.OriginSend()
212 if expected == 0 {
213 if len(coins) != 0 {
214 panic( "exact payment required: expected no coins, got " + coins.String())
215 }
216 return
217 }
218 if len(coins) != 1 || coins[ 0 ].Denom != "ugnot" || coins[ 0 ].Amount != expected {
219 panic( "exact payment required: expected " + strconv.FormatInt(expected, 10 ) + "ugnot, got " + coins.String())
220 }
221 }
222