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

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}