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

4.43 Kb · 127 lines
  1package nsskin
  2
  3import (
  4	"chain"
  5	"strings"
  6	"chain/banker"
  7	"chain/runtime/unsafe"
  8
  9	"gno.land/p/nt/ufmt/v0"
 10	"gno.land/r/nym-mnsrehearsal001/nsdata/v2"
 11)
 12
 13const ugnot = "ugnot"
 14
 15/*
 16BUYING ONE, AND WEARING IT, IN A SINGLE TRANSACTION.
 17
 18Splitting these would mean a buyer signs twice and can stop between —
 19leaving a skin they paid for sitting unworn in a wallet, which is the
 20state nobody wants and the one a two-step flow produces most often. So
 21the name is an argument: pass it and the skin is minted and attached
 22together; pass "" and it is minted loose, which is what somebody buying
 23ahead of registering a name needs.
 24
 25NOTHING IS EVER HELD. Payment, treasury and refund all settle inside the
 26buyer's own transaction, exactly as nslogic does it — and for the same
 27reason, which is worth restating because it is a legal position rather
 28than a style: a realm you can have a BALANCE in is a different kind of
 29thing to operate, subject to different rules, run by people with
 30licences. Every ugnot that arrives leaves again in the same call.
 31
 32THE 70/30 SPLIT IS NOT HERE, and that is deliberate rather than
 33forgotten. Every skin in this collection is ours, so the artist and the
 34treasury are the same address and a split would move money from a
 35pocket to itself while costing gas to do it. The artist is recorded on
 36every definition so the day it stops being us, this is where the split
 37goes — see SKINS.md.
 38*/
 39func Buy(cur realm, id, label, domain string) string {
 40	if !cur.Previous().IsUserCall() {
 41		panic("nsskin: only direct wallet calls may attach payment")
 42	}
 43	buyer := cur.Previous().Address()
 44	// Same normalization as Mint, for the same reason: the token id is
 45	// built from this string, and an un-lowercased one mints a token
 46	// that can never render. See the note in write.gno.
 47	id = strings.ToLower(strings.TrimSpace(id))
 48	s := mustSkin(id)
 49	if s.withdrawn {
 50		panic("nsskin: " + id + " has been withdrawn and is not for sale")
 51	}
 52	// Refuse the sale rather than the attach. Minting a locked skin to
 53	// somebody who wanted it for a name it will never go on is a refund
 54	// request wearing the clothes of a successful purchase.
 55	if label != "" && s.lock != "" && s.lock != domain {
 56		panic(ufmt.Sprintf("nsskin: %s only goes on *%s names, not *%s", id, s.lock, domain))
 57	}
 58
 59	sent := unsafe.OriginSend()
 60	for _, c := range sent {
 61		if c.Denom != ugnot {
 62			panic("nsskin: only ugnot is accepted, got: " + c.Denom)
 63		}
 64	}
 65	// PER DESIGN, not one figure for the collection. A third of the
 66	// definitions are free by the rule in price.gno, and an admin may
 67	// have priced any of them by hand — see PriceOf, which is the only
 68	// place that decides.
 69	due := PriceOf(id)
 70	paid := sent.AmountOf(ugnot)
 71	if paid < due {
 72		panic(ufmt.Sprintf("nsskin: insufficient payment: need %d ugnot, got %d", due, paid))
 73	}
 74
 75	// A FREE SKIN TAKES NO PAYMENT AND MOVES NO COINS, and the refund
 76	// below is what makes that safe: somebody whose page quoted the old
 77	// price, or who simply attached coins to a free mint, gets every
 78	// ugnot back in the same transaction rather than donating it.
 79	b := banker.NewBanker(banker.BankerTypeRealmSend, cur)
 80	me := cur.Address()
 81	if due > 0 {
 82		b.SendCoins(me, nsdata.GetTreasury(), chain.Coins{chain.NewCoin(ugnot, due)})
 83	}
 84	if excess := paid - due; excess > 0 {
 85		b.SendCoins(me, buyer, chain.Coins{chain.NewCoin(ugnot, excess)})
 86	}
 87
 88	s.minted++
 89	tid := ufmt.Sprintf("%s#%d", id, s.minted)
 90	if err := ledger.Mint(buyer, grcID(tid)); err != nil {
 91		panic("nsskin: mint failed: " + err.Error())
 92	}
 93	tokenSkin.Set(tid, id)
 94
 95	if label != "" {
 96		wear(cur, tid, label, domain, buyer)
 97	}
 98	return tid
 99}
100
101// SetPrice sets the BASE price, in ugnot — what a design costs when the
102// rule has not made it free and no admin has priced it by hand. ugnot
103// because that is what the chain moves: what it is worth in anything
104// else is a question for the page that quotes it, and pinning a dollar
105// figure into a contract that cannot see an exchange rate would be a lie
106// with a number in it.
107func SetPrice(cur realm, p int64) {
108	assertIsAdmin(cur)
109	if p < 0 {
110		panic("nsskin: a negative price is not a discount")
111	}
112	price = p
113}
114
115func GetPrice() int64 { return price }
116
117// Free calls must not be able to strand coins here. This realm has no
118// withdrawal path, so anything attached to one would sit in its address
119// permanently.
120func assertNoSend(cur realm) {
121	if !cur.Previous().IsUserCall() {
122		return
123	}
124	if len(unsafe.OriginSend()) > 0 {
125		panic("nsskin: this call takes no payment")
126	}
127}