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

payment.gno

3.34 Kb · 99 lines
 1package market
 2
 3import (
 4	"chain"
 5	"chain/banker"
 6	"strconv"
 7
 8	"gno.land/p/samcrew/launchpad/currency/v1"
 9	sm "gno.land/p/samcrew/launchpad/safemath/v1"
10	"gno.land/r/nt/grc20reg/v0"
11	"gno.land/r/samcrew/launchpad/config/v1"
12	"gno.land/r/samcrew/launchpad/nft/v1"
13)
14
15// escrow is, per currency, the total of open offers: everything this realm
16// owes. Currencies are few (config allows sixteen), so a map is enough.
17var escrow = map[string]int64{}
18
19// terms is what this realm needs from config.
20type terms struct {
21	treasury address
22	feeBPS   int64 // protocol share of each sale price
23}
24
25// policy returns the current terms after checking that the market lane is
26// open for the currency. It is a variable only so that unit tests can stand in
27// for the config realm, whose admin cannot be set from another package's tests.
28var policy = func(currencyKey string) terms {
29	config.AssertLaneOpen(config.LaneNFTMarket, currencyKey)
30	p := config.GetVersion(config.GetCurrentVersion())
31	return terms{treasury: p.Treasury, feeBPS: p.NFTSecondaryFeeBPS}
32}
33
34func balance(cur realm, key string) int64 {
35	if key == config.NativeCurrency {
36		return banker.NewReadonlyBanker().GetCoin(cur.Address(), key)
37	}
38	return grc20reg.MustGet(key).BalanceOf(cur.Address())
39}
40
41// surplus is what the realm holds beyond its open offers. Every entry point
42// must leave it exactly as it found it.
43func surplus(cur realm, key string) int64 { return sm.Sub(balance(cur, key), escrow[key]) }
44
45// take collects exactly amount from the direct caller, attached for native
46// coins and pulled for a token, and returns the surplus before the payment.
47func take(cur realm, key string, amount int64) int64 {
48	if key == config.NativeCurrency {
49		currency.CollectNative(0, cur, amount)
50		return sm.Sub(surplus(cur, key), amount)
51	}
52	before := surplus(cur, key)
53	currency.CollectToken(0, cur, grc20reg.MustGet(key), amount)
54	return before
55}
56
57// untouched is take for an entry point that receives nothing.
58func untouched(cur realm, key string) int64 {
59	currency.AssertNoCoins(0, cur)
60	return surplus(cur, key)
61}
62
63func pay(cur realm, key string, to address, amount int64) {
64	if amount == 0 {
65		return
66	}
67	if key == config.NativeCurrency {
68		currency.PayNative(0, cur, to, amount)
69	} else {
70		currency.PayToken(0, cur, grc20reg.MustGet(key), to, amount)
71	}
72}
73
74// settle splits a sale price: the protocol fee to the treasury, each royalty
75// to its receiver, the rest, rounding included, to the seller.
76func settle(cur realm, key, collection string, price, feeBPS int64, seller, treasury address) {
77	fee := sm.BPS(price, feeBPS)
78	rest := sm.Sub(price, fee)
79	pay(cur, key, treasury, fee)
80	var royalties int64
81	for _, r := range nft.RoyaltyQuote(collection, price) {
82		pay(cur, key, r.Account, r.Amount)
83		rest = sm.Sub(rest, r.Amount)
84		royalties += r.Amount
85	}
86	pay(cur, key, seller, rest)
87	chain.Emit("Settled", "collection", collection, "currency", key,
88		"price", strconv.FormatInt(price, 10), "seller", strconv.FormatInt(rest, 10),
89		"fee", strconv.FormatInt(fee, 10), "royalties", strconv.FormatInt(royalties, 10))
90}
91
92// settled aborts the transaction unless the realm holds exactly its open
93// offers plus the surplus it had before the call: nothing taken is left
94// behind, and nothing owed has been spent.
95func settled(cur realm, key string, before int64) {
96	if surplus(cur, key) != before {
97		panic("launchpad/market: payment not fully settled")
98	}
99}