views.gno
7.20 Kb · 177 lines
1package sales
2
3import (
4 "strconv"
5
6 "chain"
7 "gno.land/p/samcrew/launchpad/fairmath/v1"
8 "gno.land/p/samcrew/launchpad/meta/v1"
9 "gno.land/r/samcrew/launchpad/config/v1"
10 "gno.land/r/samcrew/launchpad/tokens/v1"
11)
12
13// Read views for launch clients. Every int64, base units and timestamps alike,
14// is a decimal string so JavaScript keeps it exact; booleans stay booleans and
15// counts that fit in an int are numbers. They change no state.
16
17func jsonInt(v int64) string { return meta.JSONString(strconv.FormatInt(v, 10)) }
18
19// fairRecordJSON reports a sale's terms and outcome. Once settled, the primary
20// fee is what was raised minus the creator's share.
21func fairRecordJSON(r FairSaleRecord) string {
22 s := r.Schedule
23 return "{" +
24 `"creator":` + meta.JSONString(r.Creator.String()) +
25 `,"quoteCurrency":` + meta.JSONString(r.QuoteCurrency) +
26 `,"configVersion":` + jsonInt(r.ConfigVersion) +
27 `,"primaryFeeBps":` + jsonInt(r.PrimaryFeeBPS) +
28 `,"allocation":` + jsonInt(r.Allocation) +
29 `,"lotSize":` + jsonInt(r.LotSize) +
30 `,"walletCapLots":` + jsonInt(r.WalletCapLots) +
31 `,"hardCapLots":` + jsonInt(r.Caps.HardLots) +
32 `,"softCapQuote":` + jsonInt(r.Caps.SoftQuote) +
33 `,"start":` + jsonInt(s.Start) +
34 `,"end":` + jsonInt(s.End) +
35 `,"startPrice":` + jsonInt(s.StartPrice) +
36 `,"floorPrice":` + jsonInt(s.FloorPrice) +
37 `,"decrement":` + jsonInt(s.Decrement) +
38 `,"intervalSeconds":` + jsonInt(s.IntervalSeconds) +
39 `,"allowlistRoot":` + meta.JSONString(r.AllowlistRoot) +
40 `,"holdingGates":` + meta.JSONString(r.HoldingGates) +
41 `,"totalLots":` + jsonInt(r.TotalLots) +
42 `,"totalDeposits":` + jsonInt(r.TotalDeposits) +
43 `,"hardClosedAt":` + jsonInt(r.HardClosedAt) +
44 `,"cancelled":` + strconv.FormatBool(r.Cancelled) +
45 `,"settled":` + strconv.FormatBool(r.Settled) +
46 `,"succeeded":` + strconv.FormatBool(r.Succeeded) +
47 `,"closeAt":` + jsonInt(r.CloseAt) +
48 `,"closePrice":` + jsonInt(r.ClosePrice) +
49 `,"soldTokens":` + jsonInt(r.SoldTokens) +
50 `,"grossQuote":` + jsonInt(r.GrossQuote) +
51 `,"primaryFeeQuote":` + jsonInt(r.GrossQuote-r.CreatorQuote) +
52 `,"creatorQuote":` + jsonInt(r.CreatorQuote) +
53 `,"proceedsReleased":` + strconv.FormatBool(r.ProceedsReleased) +
54 `,"refundQuote":` + jsonInt(r.RefundQuote) +
55 `,"unsoldTokens":` + jsonInt(r.UnsoldTokens) +
56 `,"refundPaid":` + jsonInt(r.RefundPaid) +
57 `,"tokensClaimed":` + jsonInt(r.TokensClaimed) + "}"
58}
59
60func airdropRecordJSON(r AirdropRecord) string {
61 return "{" +
62 `"root":` + meta.JSONString(r.Root) +
63 `,"total":` + jsonInt(r.Total) +
64 `,"claimed":` + jsonInt(r.Claimed) + "}"
65}
66
67// TermsJSON is what launching a token in a currency costs under the current
68// config version: the fee a direct creation and a fair-sale creation attach,
69// exactly; the most one fair sale may raise; and the primary fee a new sale
70// pins. A fee of -1 means the currency has no terms. A creation names this
71// version, so if the terms change before it lands it fails with "config
72// changed" and takes nothing. Whether a lane takes new launches now is
73// config's ActionStatusJSON.
74func TermsJSON(currencyKey string) string {
75 if currencyKey == "" || len(currencyKey) > 200 {
76 panic("launchpad/sales: invalid currency")
77 }
78 version := config.GetCurrentVersion()
79 p := config.GetVersion(version)
80 direct, fair, raiseCap := int64(-1), int64(-1), int64(0)
81 for _, c := range p.Currencies {
82 if c.Key == currencyKey {
83 direct, fair, raiseCap = c.DirectCreationFee, c.FairSaleCreationFee, c.FairSaleRaiseCap
84 }
85 }
86 return "{" +
87 `"schema":"launchpad-sales-terms-v1"` +
88 `,"version":` + jsonInt(version) +
89 `,"currency":` + meta.JSONString(currencyKey) +
90 `,"directCreationFee":` + jsonInt(direct) +
91 `,"fairSaleCreationFee":` + jsonInt(fair) +
92 `,"fairSaleRaiseCap":` + jsonInt(raiseCap) +
93 `,"primaryFeeBps":` + jsonInt(p.PrimaryFeeBPS) + "}"
94}
95
96// SolvencyJSON is the realm's books in one currency: the balance it holds and
97// what it owes, in total and by liability. fees is what SweepFees would pay
98// now. Every movement of quote ends with balance >= owed, so a watcher that
99// sees otherwise has found a fault; what the balance holds above owed was
100// sent outside the entry points and is owed to no one. An empty key or one
101// over 200 bytes panics "invalid currency"; a key that is neither ugnot nor a
102// registered GRC20 aborts in the registry ("unknown token").
103func SolvencyJSON(currencyKey string) string {
104 if currencyKey == "" || len(currencyKey) > 200 {
105 panic("launchpad/sales: invalid currency")
106 }
107 return "{" +
108 `"schema":"launchpad-sales-solvency-v1"` +
109 `,"currency":` + meta.JSONString(currencyKey) +
110 `,"balance":` + jsonInt(quoteBalance(chain.PackageAddress(salesPath), currencyKey)) +
111 `,"owed":` + jsonInt(owedIn(currencyKey)) +
112 `,"fees":` + jsonInt(feeLiability[currencyKey]) +
113 `,"escrow":` + jsonInt(fairEscrowLiability[currencyKey]) +
114 `,"refunds":` + jsonInt(fairRefundLiability[currencyKey]) +
115 `,"proceeds":` + jsonInt(fairProceedsLiability[currencyKey]) + "}"
116}
117
118// LaunchJSON is a token's launch state: the ledger's record of the token, what
119// sales still owes in it, how many vesting records it has, and its fair sale
120// and airdrop campaign, each null when the token has none.
121func LaunchJSON(id string) string {
122 token := tokens.TokenJSON(id)
123 fair := "null"
124 if r, ok := fairSales.Get(id).(FairSaleRecord); ok {
125 fair = fairRecordJSON(r)
126 }
127 airdrop := "null"
128 if r, ok := airdrops.Get(id).(AirdropRecord); ok {
129 airdrop = airdropRecordJSON(r)
130 }
131 return "{" +
132 `"schema":"launchpad-sales-v1"` +
133 `,"token":` + token +
134 `,"tokenLiability":` + jsonInt(tokenOwed(id)) +
135 `,"vestingCount":` + strconv.Itoa(vestingCount(id)) +
136 `,"fairSale":` + fair +
137 `,"airdrop":` + airdrop + "}"
138}
139
140// FairBuyerJSON is one buyer's record in a sale and, once the sale is settled
141// and until the buyer is paid, what ClaimFair would send them. Before
142// settlement both amounts are 0 and settled is false: settle first.
143func FairBuyerJSON(id string, buyer address) string {
144 r := GetFairSale(id)
145 b := GetFairBuyer(id, buyer)
146 claimTokens, claimRefund := int64(0), int64(0)
147 if r.Settled && b.Lots > 0 && !b.Claimed {
148 c := fairmath.Claim(r.Succeeded, b.Lots, b.Deposit, r.LotSize, r.ClosePrice)
149 claimTokens, claimRefund = c.Tokens, c.RefundQuote
150 }
151 return "{" +
152 `"buyer":` + meta.JSONString(buyer.String()) +
153 `,"lots":` + jsonInt(b.Lots) +
154 `,"deposit":` + jsonInt(b.Deposit) +
155 `,"claimableTokens":` + jsonInt(claimTokens) +
156 `,"claimableRefund":` + jsonInt(claimRefund) +
157 `,"settled":` + strconv.FormatBool(r.Settled) +
158 `,"claimed":` + strconv.FormatBool(b.Claimed) + "}"
159}
160
161// VestingJSON is one vesting record: its schedule, who holds the claim right
162// and who has been proposed to take it.
163func VestingJSON(id string, index int) string {
164 r := GetVesting(id, index)
165 return "{" +
166 `"index":` + strconv.Itoa(index) +
167 `,"beneficiary":` + meta.JSONString(r.Beneficiary.String()) +
168 `,"pendingBeneficiary":` + meta.JSONString(r.PendingBeneficiary.String()) +
169 `,"total":` + jsonInt(r.Total) +
170 `,"claimed":` + jsonInt(r.Claimed) +
171 `,"start":` + jsonInt(r.Start) +
172 `,"cliff":` + jsonInt(r.Cliff) +
173 `,"duration":` + jsonInt(r.Duration) +
174 `,"revocable":` + strconv.FormatBool(r.Revocable) +
175 `,"revoked":` + strconv.FormatBool(r.Revoked) +
176 `,"revokedVested":` + jsonInt(r.RevokedVested) + "}"
177}