Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

buy.gno

3.19 Kb · 119 lines
  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}