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.

fishtoken.gno

8.57 Kb · 339 lines
  1// Package fishtoken is the unreleased GnoFish FISH ledger.
  2// Init mints a fixed supply once. No later call can mint.
  3// While transfers are closed, a normal wallet cannot send FISH, so it cannot
  4// fund a pool or sell through a market. The deploy wallet opens transfers.
  5// The five treasury wallets, the deploy wallet, and a reward realm the deploy
  6// wallet has granted may still send. This realm has no pool and no trade.
  7// Public-sale FISH is minted to the deploy wallet. fishtoken-1.
  8package fishtoken
  9
 10import (
 11	"chain"
 12	"chain/runtime/unsafe"
 13	"strconv"
 14
 15	"gno.land/p/nt/grc20/v0"
 16	"gno.land/p/nt/seqid/v0"
 17)
 18
 19const (
 20	unit            int64 = 1_000_000
 21	supplyWhole     int64 = 100_000_000
 22	seasonWhole     int64 = 43_000_000
 23	liquidityWhole  int64 = 20_000_000
 24	ecosystemWhole  int64 = 15_000_000
 25	communityWhole  int64 = 12_000_000
 26	teamWhole       int64 = 5_000_000
 27	saleWhole       int64 = 5_000_000
 28)
 29
 30var (
 31	seasonAddr    = address("g1m6z3l9q577dltj73ym8fu0068peupqg468r5xe")
 32	liquidityAddr = address("g1lepzz7lehwfnwng0ddxaglz2kzmsf6pyy5f6tw")
 33	ecosystemAddr = address("g1p4cylgemajmw93e9a0nr0fmph60gee2yhkn2kz")
 34	communityAddr = address("g1dpklfykp2mrhcwccek54scv43kamtxlszya2z8")
 35	teamAddr      = address("g15ejerz2n0xn5x94zezhquvwvp98m3jgkcwdrya")
 36)
 37
 38var (
 39	token     *grc20.Token
 40	ledger    *grc20.PrivateLedger
 41	seq       seqid.ID
 42	inited    bool
 43	open      bool
 44	admin     address
 45	mover     map[address]bool
 46	payer     map[address]bool
 47)
 48
 49// Init mints the fixed supply once. The caller is the deploy wallet and receives the public-sale FISH. No coins.
 50func Init(cur realm) {
 51	caller := requireUser(cur)
 52	takeExact(0)
 53	if inited {
 54		panic("fishtoken: init")
 55	}
 56	if !distinct(caller, seasonAddr, liquidityAddr, ecosystemAddr, communityAddr, teamAddr) {
 57		panic("fishtoken: addr")
 58	}
 59	tok, led := grc20.NewToken("FISH", "FISH", 6, seq.Next(), cur)
 60	credit(led, seasonAddr, seasonWhole*unit)
 61	credit(led, liquidityAddr, liquidityWhole*unit)
 62	credit(led, ecosystemAddr, ecosystemWhole*unit)
 63	credit(led, communityAddr, communityWhole*unit)
 64	credit(led, teamAddr, teamWhole*unit)
 65	credit(led, caller, saleWhole*unit)
 66	if tok.TotalSupply() != supplyWhole*unit {
 67		panic("fishtoken: supply")
 68	}
 69	token = tok
 70	ledger = led
 71	admin = caller
 72	open = false
 73	mover = map[address]bool{
 74		caller:        true,
 75		seasonAddr:    true,
 76		liquidityAddr: true,
 77		ecosystemAddr: true,
 78		communityAddr: true,
 79		teamAddr:      true,
 80	}
 81	payer = map[address]bool{}
 82	inited = true
 83	chain.Emit("Init", "admin", caller.String(), "supply", strconv.FormatInt(supplyWhole*unit, 10))
 84}
 85
 86// SetTransfers opens or closes ordinary wallet sends. The deploy wallet only. No coins.
 87func SetTransfers(cur realm, next bool) {
 88	caller := requireUser(cur)
 89	takeExact(0)
 90	requireReady()
 91	requireAdmin(caller)
 92	open = next
 93	chain.Emit("SetTransfers", "open", strconv.FormatBool(next))
 94}
 95
 96// GrantMover lets that wallet send FISH while transfers are closed. The deploy wallet only. No coins.
 97func GrantMover(cur realm, who address) {
 98	caller := requireUser(cur)
 99	takeExact(0)
100	requireReady()
101	requireAdmin(caller)
102	if !who.IsValid() {
103		panic("fishtoken: addr")
104	}
105	mover[who] = true
106	chain.Emit("GrantMover", "who", who.String())
107}
108
109// RevokeMover removes that wallet from the closed-transfer list. The deploy wallet only. No coins.
110func RevokeMover(cur realm, who address) {
111	caller := requireUser(cur)
112	takeExact(0)
113	requireReady()
114	requireAdmin(caller)
115	if !who.IsValid() {
116		panic("fishtoken: addr")
117	}
118	delete(mover, who)
119	chain.Emit("RevokeMover", "who", who.String())
120}
121
122// GrantDistributor lets that realm pay FISH to users while transfers are closed. The deploy wallet only. No coins.
123func GrantDistributor(cur realm, who address) {
124	caller := requireUser(cur)
125	takeExact(0)
126	requireReady()
127	requireAdmin(caller)
128	if !who.IsValid() {
129		panic("fishtoken: addr")
130	}
131	payer[who] = true
132	chain.Emit("GrantDistributor", "who", who.String())
133}
134
135// RevokeDistributor removes that reward realm. The deploy wallet only. No coins.
136func RevokeDistributor(cur realm, who address) {
137	caller := requireUser(cur)
138	takeExact(0)
139	requireReady()
140	requireAdmin(caller)
141	if !who.IsValid() {
142		panic("fishtoken: addr")
143	}
144	delete(payer, who)
145	chain.Emit("RevokeDistributor", "who", who.String())
146}
147
148// TransferAdmin hands the switch to next. Balances stay where they are. No coins.
149func TransferAdmin(cur realm, next address) {
150	caller := requireUser(cur)
151	takeExact(0)
152	requireReady()
153	requireAdmin(caller)
154	if !next.IsValid() {
155		panic("fishtoken: admin")
156	}
157	admin = next
158	mover[next] = true
159	chain.Emit("TransferAdmin", "next", next.String())
160}
161
162// Transfer sends FISH from the caller to to.
163// A closed ledger rejects an ordinary wallet. Listed wallets and granted reward realms may still send.
164func Transfer(cur realm, to address, amount int64) {
165	caller := cur.Previous().Address()
166	if cur.Previous().IsUserCall() {
167		takeExact(0)
168	}
169	requireReady()
170	requireAmount(amount, to)
171	if !mayMove(cur, caller, caller) {
172		panic("fishtoken: closed")
173	}
174	must(ledger.CallerTeller().Transfer(0, cur, to, amount))
175	chain.Emit("Send", "to", to.String(), "amount", strconv.FormatInt(amount, 10))
176}
177
178// Approve sets the spender allowance. It does not move FISH and does not open trading. No coins from an EOA.
179func Approve(cur realm, spender address, amount int64) {
180	if cur.Previous().IsUserCall() {
181		takeExact(0)
182	} else if !payer[cur.Previous().Address()] {
183		panic("fishtoken: closed")
184	}
185	requireReady()
186	if amount < 0 || !spender.IsValid() {
187		panic("fishtoken: amount")
188	}
189	must(ledger.CallerTeller().Approve(0, cur, spender, amount))
190	chain.Emit("Approve", "spender", spender.String(), "amount", strconv.FormatInt(amount, 10))
191}
192
193// TransferFrom sends FISH using the caller's allowance.
194// While closed, only a granted reward realm may pull, and only from its own balance or from a listed wallet.
195func TransferFrom(cur realm, owner, to address, amount int64) {
196	caller := cur.Previous().Address()
197	if cur.Previous().IsUserCall() {
198		takeExact(0)
199	}
200	requireReady()
201	requireAmount(amount, to)
202	if !mayMove(cur, caller, owner) {
203		panic("fishtoken: closed")
204	}
205	must(ledger.CallerTeller().TransferFrom(0, cur, owner, to, amount))
206	chain.Emit("SendFrom", "owner", owner.String(), "to", to.String(), "amount", strconv.FormatInt(amount, 10))
207}
208
209func TransfersOpen() bool { return open }
210func IsMover(who address) bool {
211	return mover[who]
212}
213func IsDistributor(who address) bool { return payer[who] }
214func Version() string                { return "fishtoken-1" }
215func Admin() address                 { return admin }
216
217func Name() string {
218	requireReady()
219	return token.GetName()
220}
221
222func Symbol() string {
223	requireReady()
224	return token.GetSymbol()
225}
226
227func Decimals() int {
228	requireReady()
229	return token.GetDecimals()
230}
231
232func TotalSupply() int64 {
233	requireReady()
234	return token.TotalSupply()
235}
236
237func BalanceOf(who address) int64 {
238	requireReady()
239	return token.BalanceOf(who)
240}
241
242func Allowance(owner, spender address) int64 {
243	requireReady()
244	return token.Allowance(owner, spender)
245}
246
247func TokenID() string {
248	requireReady()
249	return token.ID()
250}
251
252func Render(path string) string {
253	if path != "" {
254		return "# FISH\n\nUnknown path.\n"
255	}
256	if !inited {
257		return "# FISH\n\nNot opened.\n"
258	}
259	state := "closed"
260	if open {
261		state = "open"
262	}
263	return "# FISH\n\nFixed supply. Transfers are " + state + ". A closed ledger does not fund a pool and does not trade. Listed wallets and granted reward realms can still send. Public-sale FISH was minted to the deploy wallet.\n"
264}
265
266func mayMove(cur realm, caller, from address) bool {
267	if open {
268		return true
269	}
270	if cur.Previous().IsUserCall() {
271		return from == caller && mover[caller]
272	}
273	if !payer[caller] {
274		return false
275	}
276	return from == caller || mover[from]
277}
278
279func requireAdmin(caller address) {
280	if caller != admin {
281		panic("fishtoken: admin")
282	}
283}
284
285func requireAmount(amount int64, to address) {
286	if amount <= 0 || !to.IsValid() {
287		panic("fishtoken: amount")
288	}
289}
290
291func credit(led *grc20.PrivateLedger, who address, amount int64) {
292	if err := led.Mint(who, amount); err != nil {
293		panic(err)
294	}
295}
296
297func must(err error) {
298	if err != nil {
299		panic(err)
300	}
301}
302
303func requireReady() {
304	if !inited || token == nil || ledger == nil {
305		panic("fishtoken: init")
306	}
307}
308
309func requireUser(cur realm) address {
310	if !cur.Previous().IsUserCall() {
311		panic("fishtoken: eoa")
312	}
313	return cur.Previous().Address()
314}
315
316func takeExact(n int64) {
317	sent := unsafe.OriginSend()
318	if n == 0 {
319		if len(sent) != 0 {
320			panic("fishtoken: payment")
321		}
322		return
323	}
324	panic("fishtoken: payment")
325}
326
327func distinct(xs ...address) bool {
328	for i, a := range xs {
329		if !a.IsValid() {
330			return false
331		}
332		for j := 0; j < i; j++ {
333			if xs[j] == a {
334				return false
335			}
336		}
337	}
338	return true
339}