Realm
Gno.land Mainnet
gno.land/r/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/nfts/skins
Overview
Network
gnoland-1
Realm Path
gno.land/r/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/nfts/skins
Exported Functions
12
State Entries
39
Source Files
10
Total Package Entries
79
Exported Functions
12 exported functions
State
39 state entries
Source Code
FILES
buy.gno
go
1package skins
2
3import (
4 "chain"
5 "chain/banker"
6
7 // Only for OriginSend, in Buy. Caller identity always comes from
8 // cur.Previous(), never from this package.
9 "chain/runtime/unsafe"
10
11 "gno.land/p/nt/grc721/v0"
12)
13
14// Buy buys one token of a skin design for the caller and returns its id. The
15// exact price must be attached to the transaction (-send "<price>ugnot") and
16// is forwarded to the treasury of the game realm before the call returns.
17//
18// An address owns at most one token of a design: a skin cannot be given
19// away, so a second copy would be of no use. It must be called directly by a
20// user (maketx call): a call relayed by another realm or by a maketx run
21// script is refused.
22func Buy(cur realm, skinID string) int64 {
23 buyer := callerOf(0, cur)
24
25 // Paid-call guard: two checks that are only correct together.
26 //
27 // (a) The caller is a plain user account. Only then is the -send
28 // envelope guaranteed to have landed at this realm's address.
29 if !cur.Previous().IsUserCall() {
30 panic(errNotUserCall)
31 }
32 d := getDesign(skinID)
33 if d == nil {
34 panic(errUnknownSkin)
35 }
36 if !d.listed {
37 panic(errSkinUnlisted)
38 }
39 if d.maxSupply > 0 && d.minted >= d.maxSupply {
40 panic(errSoldOut)
41 }
42 key := ownedKey(buyer, skinID)
43 if owned.Has(key) {
44 panic(errAlreadyOwned)
45 }
46
47 // (b) The envelope is exactly the price: one coin, the right denom, the
48 // right amount.
49 sent := unsafe.OriginSend()
50 if len(sent) != 1 || sent[0].Denom != payDenom || sent[0].Amount != d.price {
51 panic("gnofly: wrong payment, send exactly " + itoa(d.price) + payDenom)
52 }
53 treasury := roles.Treasury()
54 payment := chain.NewCoins(chain.NewCoin(payDenom, d.price))
55 banker.NewBanker(banker.BankerTypeOriginSend, cur).SendCoins(cur.Address(), treasury, payment)
56
57 id := nextID
58 nextID++
59 tid := grc721.TokenID(itoa(id))
60 if err := ledger.Mint(buyer, tid); err != nil {
61 panic(err)
62 }
63 tokenSkin.Set(string(tid), skinID)
64 owned.Set(key, id)
65 d.minted++
66
67 chain.Emit("SkinBought",
68 "id", itoa(id),
69 "skin", skinID,
70 "buyer", buyer.String(),
71 "price", itoa(d.price),
72 "treasury", treasury.String(),
73 )
74 return id
75}
76
77// Burn destroys a skin token. Only its owner can. The storage deposit paid at
78// purchase comes back; the price does not. The owner can buy the design
79// again, while it is listed.
80func Burn(cur realm, id int64) {
81 caller := callerOf(0, cur)
82 skinID := skinOf(id)
83 if skinID == "" {
84 panic(errNotMinted)
85 }
86 if ownerOf(id) != caller {
87 panic(errNotSkinOwner)
88 }
89 tid := grc721.TokenID(itoa(id))
90 if err := ledger.Burn(tid); err != nil {
91 panic(err)
92 }
93 tokenSkin.Remove(string(tid))
94 owned.Remove(ownedKey(caller, skinID))
95 chain.Emit("SkinBurned", "id", itoa(id), "skin", skinID, "owner", caller.String())
96}
97
98// ownedKey is the key of owned: the address, then the design.
99func ownedKey(addr address, skinID string) string {
100 return addr.String() + "/" + skinID
101}
102
103// skinOf returns the design of token id, "" when there is no such token.
104func skinOf(id int64) string {
105 v := tokenSkin.Get(itoa(id))
106 if v == nil {
107 return ""
108 }
109 return v.(string)
110}
111
112// ownerOf returns who owns token id, "" for none.
113func ownerOf(id int64) address {
114 owner, err := token.OwnerOf(grc721.TokenID(itoa(id)))
115 if err != nil {
116 return ""
117 }
118 return owner
119}
120Raw Package Data
Raw JSON data