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.

shoaltoken.gno

6.47 Kb · 257 lines
  1// Package shoaltoken is the Shoal FISH ledger. Init mints a fixed supply and
  2// no later call can mint. Wallet Transfer, Approve, and TransferFrom move the
  3// full amount. BuyTaxBps and SellTaxBps are stored for a later pool realm.
  4// They do not tax a wallet transfer, a cheer, a season reward, or a community claim.
  5//
  6// The symbol is FISH, the same letters as the fish NFT collection. A wallet
  7// that keys only on the symbol can confuse the two. The chain identity is
  8// TokenID: the realm path, the symbol, and the sequence.
  9//
 10// Deployed on onyx-1 at gno.land/r/g10ragdr3cjqkael8rslpw4y6rlelhev4h0fswcx/shoaltoken.
 11// Init is a separate call. It does not register this token.
 12package shoaltoken
 13
 14import (
 15	"chain"
 16	"chain/runtime/unsafe"
 17	"strconv"
 18
 19	"gno.land/p/nt/grc20/v0"
 20	"gno.land/p/nt/ownable/v0"
 21)
 22
 23const (
 24	unit            int64 = 1_000_000
 25	supplyWhole     int64 = 100_000_000
 26	seasonWhole     int64 = 43_000_000
 27	liquidityWhole  int64 = 20_000_000
 28	ecosystemWhole  int64 = 15_000_000
 29	communityWhole  int64 = 12_000_000
 30	teamWhole       int64 = 10_000_000
 31	maxBuyTaxBps    int64 = 500
 32	maxSellTaxBps   int64 = 1000
 33	defaultBuyTax   int64 = 200
 34	defaultSellTax  int64 = 400
 35)
 36
 37var (
 38	token     *grc20.Token
 39	ledger    *grc20.PrivateLedger
 40	admin     *ownable.Ownable
 41	inited    bool
 42	sealed    bool
 43	buyTaxBps  int64 = defaultBuyTax
 44	sellTaxBps int64 = defaultSellTax
 45)
 46
 47// Init mints the fixed supply once and seals the ledger. The five treasuries must be distinct.
 48func Init(cur realm, season, liquidity, ecosystem, community, team address) {
 49	if !cur.IsCurrent() {
 50		panic("shoaltoken: realm")
 51	}
 52	if !cur.Previous().IsUserCall() {
 53		panic("shoaltoken: eoa")
 54	}
 55	takeExact(0)
 56	if inited {
 57		panic("shoaltoken: init")
 58	}
 59	if !distinct(season, liquidity, ecosystem, community, team) {
 60		panic("shoaltoken: addr")
 61	}
 62	tok, led := grc20.NewToken("Shoal FISH", "FISH", 6, 0, cur)
 63	credit(led, season, seasonWhole*unit)
 64	credit(led, liquidity, liquidityWhole*unit)
 65	credit(led, ecosystem, ecosystemWhole*unit)
 66	credit(led, community, communityWhole*unit)
 67	credit(led, team, teamWhole*unit)
 68	if tok.TotalSupply() != supplyWhole*unit {
 69		panic("shoaltoken: supply")
 70	}
 71	token = tok
 72	ledger = led
 73	admin = ownable.NewWithAddress(cur.Previous().Address())
 74	inited = true
 75	sealed = true
 76	chain.Emit(
 77		"Init",
 78		"token", tok.ID(),
 79		"admin", cur.Previous().Address().String(),
 80		"supply", strconv.FormatInt(supplyWhole*unit, 10),
 81	)
 82}
 83
 84// Transfer moves FISH from the caller to to. The amount is not taxed.
 85func Transfer(cur realm, to address, amount int64) {
 86	requireUser(cur)
 87	takeExact(0)
 88	requireReady()
 89	must(ledger.CallerTeller().Transfer(0, cur, to, amount))
 90	chain.Emit("Send", "to", to.String(), "amount", strconv.FormatInt(amount, 10))
 91}
 92
 93// Approve lets spender move up to amount from the caller. This replaces the previous allowance.
 94func Approve(cur realm, spender address, amount int64) {
 95	requireUser(cur)
 96	takeExact(0)
 97	requireReady()
 98	must(ledger.CallerTeller().Approve(0, cur, spender, amount))
 99	chain.Emit("Approve", "spender", spender.String(), "amount", strconv.FormatInt(amount, 10))
100}
101
102// TransferFrom moves amount from owner to to, using the caller's allowance. The amount is not taxed.
103func TransferFrom(cur realm, owner, to address, amount int64) {
104	requireUser(cur)
105	takeExact(0)
106	requireReady()
107	must(ledger.CallerTeller().TransferFrom(0, cur, owner, to, amount))
108	chain.Emit("SendFrom", "owner", owner.String(), "to", to.String(), "amount", strconv.FormatInt(amount, 10))
109}
110
111// SetBuyTaxBps stores the buy tax for a later pool. The cap is 500 bps. This does not tax wallets.
112func SetBuyTaxBps(cur realm, next int64) {
113	caller := requireUser(cur)
114	takeExact(0)
115	requireReady()
116	if !admin.OwnedBy(caller) {
117		panic("shoaltoken: admin")
118	}
119	if next < 0 || next > maxBuyTaxBps {
120		panic("shoaltoken: tax")
121	}
122	buyTaxBps = next
123	chain.Emit("SetBuyTaxBps", "bps", strconv.FormatInt(next, 10))
124}
125
126// SetSellTaxBps stores the sell tax for a later pool. The cap is 1000 bps. This does not tax wallets.
127func SetSellTaxBps(cur realm, next int64) {
128	caller := requireUser(cur)
129	takeExact(0)
130	requireReady()
131	if !admin.OwnedBy(caller) {
132		panic("shoaltoken: admin")
133	}
134	if next < 0 || next > maxSellTaxBps {
135		panic("shoaltoken: tax")
136	}
137	sellTaxBps = next
138	chain.Emit("SetSellTaxBps", "bps", strconv.FormatInt(next, 10))
139}
140
141// TransferAdmin hands the tax hook to next. Balances stay where they are.
142func TransferAdmin(cur realm, next address) {
143	requireUser(cur)
144	takeExact(0)
145	requireReady()
146	if !next.IsValid() {
147		panic("shoaltoken: admin")
148	}
149	if err := admin.TransferOwnership(0, cur, next); err != nil {
150		panic("shoaltoken: admin")
151	}
152	chain.Emit("TransferAdmin", "next", next.String())
153}
154
155func Name() string {
156	requireReady()
157	return token.GetName()
158}
159
160func Symbol() string {
161	requireReady()
162	return token.GetSymbol()
163}
164
165func Decimals() int {
166	requireReady()
167	return token.GetDecimals()
168}
169
170func TotalSupply() int64 {
171	requireReady()
172	return token.TotalSupply()
173}
174
175func BalanceOf(who address) int64 {
176	requireReady()
177	return token.BalanceOf(who)
178}
179
180func Allowance(owner, spender address) int64 {
181	requireReady()
182	return token.Allowance(owner, spender)
183}
184
185func TokenID() string {
186	requireReady()
187	return token.ID()
188}
189
190func BuyTaxBps() int64  { return buyTaxBps }
191func SellTaxBps() int64 { return sellTaxBps }
192func Version() string   { return "shoaltoken-1" }
193func Sealed() bool      { return sealed }
194
195func Admin() address {
196	if admin == nil {
197		return address("")
198	}
199	return admin.Owner()
200}
201
202func Render(path string) string {
203	if path != "" {
204		return "# Shoal FISH\n\nUnknown path.\n"
205	}
206	return "# Shoal FISH\n\nFixed supply. Wallet transfers are not taxed.\n\nThe symbol FISH shares its letters with the fish NFT collection. TokenID is the chain identity.\n"
207}
208
209func credit(led *grc20.PrivateLedger, who address, amount int64) {
210	if err := led.Mint(who, amount); err != nil {
211		panic(err)
212	}
213}
214
215func must(err error) {
216	if err != nil {
217		panic(err)
218	}
219}
220
221func requireReady() {
222	if !inited || !sealed || token == nil || ledger == nil || admin == nil {
223		panic("shoaltoken: init")
224	}
225}
226
227func requireUser(cur realm) address {
228	if !cur.Previous().IsUserCall() {
229		panic("shoaltoken: eoa")
230	}
231	return cur.Previous().Address()
232}
233
234func takeExact(n int64) {
235	sent := unsafe.OriginSend()
236	if n == 0 {
237		if len(sent) != 0 {
238			panic("shoaltoken: payment")
239		}
240		return
241	}
242	panic("shoaltoken: payment")
243}
244
245func distinct(xs ...address) bool {
246	for i, a := range xs {
247		if !a.IsValid() {
248			return false
249		}
250		for j := 0; j < i; j++ {
251			if xs[j] == a {
252				return false
253			}
254		}
255	}
256	return true
257}