← Back to Realms

Realm

gno.land/r/samcrew/launchpad/sales/v1

Overview

Realm Path
gno.land/r/samcrew/launchpad/sales/v1
Exported Functions
33
State Entries
24
Source Files
8
Total Package Entries
92

Exported Functions

33 exported functions

State

24 state entries

Source Code

FILES
airdrop.gno
go
1package sales
2
3import (
4	"strconv"
5	"strings"
6
7	"chain"
8	"gno.land/p/samcrew/launchpad/currency/v1"
9	"gno.land/p/samcrew/launchpad/merkle/v1"
10	"gno.land/p/samcrew/launchpad/meta/v1"
11	sm "gno.land/p/samcrew/launchpad/safemath/v1"
12	"gno.land/r/samcrew/launchpad/config/v1"
13	"gno.land/r/samcrew/launchpad/tokens/v1"
14)
15
16const maxAirdropProofBytes = merkle.MaxDepth*64 + merkle.MaxDepth - 1
17
18// AirdropRecord keeps the whole funded allocation encumbered until recipients
19// claim. Claims never expire, and nothing sweeps or returns an unclaimed
20// remainder.
21type AirdropRecord struct {
22	Root    string
23	Total   int64
24	Claimed int64
25}
26
27func requireAirdropRoot(root string) {
28	h, err := merkle.ParseHash(root)
29	if err != nil || merkle.FormatHash(h) != root || h == (merkle.Hash{}) {
30		panic("launchpad/sales: invalid airdrop root")
31	}
32}
33
34// CreateAirdrop funds one immutable campaign for a token from its creator's
35// allowance. Its Merkle leaves are sha256(0x00 ||
36// "launchpad-airdrop-v1|<token-id>|<index>|<beneficiary>|<amount>").
37// The token ID in every leaf prevents a proof from authorizing a claim on a
38// different launch, even if two creators publish the same tree shape.
39func CreateAirdrop(cur realm, id, root string, total int64) {
40	creator := directCaller(cur)
41	currency.AssertNoCoins(0, cur)
42	info := tokens.GetInfo(id)
43	if info.Creator != creator || total <= 0 || airdropOf(id).Total != 0 {
44		panic("launchpad/sales: creator airdrop only")
45	}
46	requireAirdropRoot(root)
47	config.AssertLaneOpen(config.LaneAirdrop, info.CurrencyKey)
48	tokens.TransferFrom(cross(cur), id, creator, chain.PackageAddress(salesPath), total)
49	airdrops.Set(id, AirdropRecord{Root: root, Total: total})
50	setTokenOwed(id, sm.Add(tokenOwed(id), total))
51	if tokens.BalanceOf(id, chain.PackageAddress(salesPath)) < tokenOwed(id) {
52		panic("launchpad/sales: token custody shortfall")
53	}
54	chain.Emit("LaunchpadAirdropCreated", "id", id, "creator", creator.String(),
55		"root", root, "total", strconv.FormatInt(total, 10))
56}
57
58func GetAirdrop(id string) AirdropRecord {
59	r := airdropOf(id)
60	if r.Total == 0 {
61		panic("launchpad/sales: airdrop not found")
62	}
63	return r
64}
65
66func AirdropClaimed(id string, index int) bool {
67	if index < 0 {
68		panic("launchpad/sales: invalid airdrop index")
69	}
70	return airdropClaims.Has(indexKey(id, index))
71}
72
73// ClaimAirdrop remains available while the airdrop lane is paused. Anyone can
74// submit a proof; the destination is always the leaf's beneficiary. A leaf
75// whose beneficiary is not written as the chain writes a caller's address can
76// never be claimed: the token ledger would credit a balance no caller can
77// reach.
78func ClaimAirdrop(cur realm, id string, index int, beneficiary address, amount int64, proof string) {
79	if !cur.IsCurrent() {
80		panic("launchpad/sales: stale realm context")
81	}
82	currency.AssertNoCoins(0, cur)
83	if index < 0 || amount <= 0 || len(proof) > maxAirdropProofBytes ||
84		!meta.ValidAddress(beneficiary) ||
85		beneficiary == chain.PackageAddress(salesPath) {
86		panic("launchpad/sales: invalid airdrop claim")
87	}
88	r := GetAirdrop(id)
89	key := indexKey(id, index)
90	if airdropClaims.Has(key) {
91		panic("launchpad/sales: airdrop already claimed")
92	}
93	h, _ := merkle.ParseHash(r.Root)
94	p, err := merkle.ParseProof(proof)
95	if err != nil {
96		panic("launchpad/sales: invalid airdrop proof")
97	}
98	if proof != "" {
99		parts := strings.Split(proof, ",")
100		for i, sibling := range p {
101			if merkle.FormatHash(sibling) != parts[i] {
102				panic("launchpad/sales: invalid airdrop proof")
103			}
104		}
105	}
106	leaf := merkle.Leaf([]byte("launchpad-airdrop-v1|" + id + "|" +
107		strconv.Itoa(index) + "|" + beneficiary.String() + "|" +
108		strconv.FormatInt(amount, 10)))
109	if !merkle.Verify(h, leaf, p) || sm.Add(r.Claimed, amount) > r.Total {
110		panic("launchpad/sales: airdrop proof or allocation denied")
111	}
112	r.Claimed = sm.Add(r.Claimed, amount)
113	airdropClaims.Set(key, true)
114	airdrops.Set(id, r)
115	setTokenOwed(id, sm.Sub(tokenOwed(id), amount))
116	tokens.Transfer(cross(cur), id, beneficiary, amount)
117	if tokens.BalanceOf(id, chain.PackageAddress(salesPath)) < tokenOwed(id) {
118		panic("launchpad/sales: token custody shortfall")
119	}
120	chain.Emit("LaunchpadAirdropClaimed", "id", id,
121		"index", strconv.Itoa(index), "beneficiary", beneficiary.String(),
122		"amount", strconv.FormatInt(amount, 10))
123}
124

Raw Package Data

Raw JSON data