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

grc20factory.gno

5.71 Kb · 224 lines
  1package grc20factory
  2
  3import (
  4	"gno.land/p/moul/md/v0"
  5	p "gno.land/p/nt/avl/pager/v0"
  6	"gno.land/p/nt/avl/rotree/v0"
  7	"gno.land/p/nt/avl/v0"
  8	"gno.land/p/nt/grc20/v0"
  9	"gno.land/p/nt/mux/v0"
 10	"gno.land/p/nt/ownable/v0"
 11	"gno.land/p/nt/seqid/v0"
 12	"gno.land/p/nt/ufmt/v0"
 13	"gno.land/r/nt/grc20reg/v0"
 14)
 15
 16var (
 17	instances   avl.Tree // symbol -> *instance
 18	nextTokenID seqid.ID
 19	pager       = p.NewPager(rotree.Wrap(&instances, nil), 20, false)
 20)
 21
 22type instance struct {
 23	token  *grc20.Token
 24	ledger *grc20.PrivateLedger
 25	admin  *ownable.Ownable
 26	faucet int64 // per-request amount. disabled if 0.
 27}
 28
 29func New(cur realm, name, symbol string, decimals int, initialMint, faucet int64) {
 30	caller := cur.Previous().Address()
 31	NewWithAdmin(cur, name, symbol, decimals, initialMint, faucet, caller)
 32}
 33
 34func NewWithAdmin(cur realm, name, symbol string, decimals int, initialMint, faucet int64, admin address) {
 35	exists := instances.Has(symbol)
 36	if exists {
 37		panic("token already exists")
 38	}
 39
 40	token, ledger := grc20.NewToken(name, symbol, decimals, nextTokenID.Next(), cur)
 41	if initialMint > 0 {
 42		ledger.Mint(admin, initialMint)
 43	}
 44
 45	inst := instance{
 46		token:  token,
 47		ledger: ledger,
 48		admin:  ownable.NewWithAddress(admin),
 49		faucet: faucet,
 50	}
 51	instances.Set(symbol, &inst)
 52
 53	grc20reg.Register(cross(cur), token, symbol)
 54}
 55
 56func Bank(symbol string) *grc20.Token {
 57	inst := mustGetInstance(symbol)
 58	return inst.token
 59}
 60
 61func TotalSupply(symbol string) int64 {
 62	inst := mustGetInstance(symbol)
 63	return inst.token.ReadonlyTeller().TotalSupply()
 64}
 65
 66func HasAddr(symbol string, owner address) bool {
 67	inst := mustGetInstance(symbol)
 68	return inst.token.HasAddr(owner)
 69}
 70
 71func BalanceOf(symbol string, owner address) int64 {
 72	inst := mustGetInstance(symbol)
 73	return inst.token.ReadonlyTeller().BalanceOf(owner)
 74}
 75
 76func Allowance(symbol string, owner, spender address) int64 {
 77	inst := mustGetInstance(symbol)
 78	return inst.token.ReadonlyTeller().Allowance(owner, spender)
 79}
 80
 81func Transfer(cur realm, symbol string, to address, amount int64) {
 82	inst := mustGetInstance(symbol)
 83	caller := cur.Previous().Address()
 84	teller := inst.ledger.ImpersonateTeller(caller)
 85	checkErr(teller.Transfer(0, cur, to, amount))
 86}
 87
 88func Approve(cur realm, symbol string, spender address, amount int64) {
 89	inst := mustGetInstance(symbol)
 90	caller := cur.Previous().Address()
 91	teller := inst.ledger.ImpersonateTeller(caller)
 92	checkErr(teller.Approve(0, cur, spender, amount))
 93}
 94
 95func TransferFrom(cur realm, symbol string, from, to address, amount int64) {
 96	inst := mustGetInstance(symbol)
 97	caller := cur.Previous().Address()
 98	teller := inst.ledger.ImpersonateTeller(caller)
 99	checkErr(teller.TransferFrom(0, cur, from, to, amount))
100}
101
102// faucet.
103func Faucet(cur realm, symbol string) {
104	inst := mustGetInstance(symbol)
105	if inst.faucet == 0 {
106		panic("faucet disabled for this token")
107	}
108	// FIXME: add limits?
109	// FIXME: add payment in gnot?
110	caller := cur.Previous().Address()
111	checkErr(inst.ledger.Mint(caller, inst.faucet))
112}
113
114func Mint(cur realm, symbol string, to address, amount int64) {
115	inst := mustGetInstance(symbol)
116	inst.admin.AssertOwnedBy(cur.Previous().Address())
117	checkErr(inst.ledger.Mint(to, amount))
118}
119
120func Burn(cur realm, symbol string, from address, amount int64) {
121	inst := mustGetInstance(symbol)
122	inst.admin.AssertOwnedBy(cur.Previous().Address())
123	checkErr(inst.ledger.Burn(from, amount))
124}
125
126// instance admin functionality
127func DropInstanceOwnership(cur realm, symbol string) {
128	inst := mustGetInstance(symbol)
129	checkErr(inst.admin.DropOwnership(0, cur))
130}
131
132func TransferInstanceOwnership(cur realm, symbol string, newOwner address) {
133	inst := mustGetInstance(symbol)
134	checkErr(inst.admin.TransferOwnership(0, cur, newOwner))
135}
136
137func ListTokens(pageNumber, pageSize int) []*grc20.Token {
138	page := pager.GetPageWithSize(pageNumber, pageSize)
139
140	tokens := make([]*grc20.Token, len(page.Items))
141	for i := range page.Items {
142		tokens[i] = page.Items[i].Value.(*instance).token
143	}
144
145	return tokens
146}
147
148func Render(path string) string {
149	router := mux.NewRouter()
150	router.HandleFunc("", renderHome)
151	router.HandleFunc("{symbol}", renderToken)
152	router.HandleFunc("{symbol}/balance/{address}", renderBalance)
153	return router.Render(path)
154}
155
156func renderHome(res *mux.ResponseWriter, req *mux.Request) {
157	out := md.H1(ufmt.Sprintf("GRC20 Tokens (%d)", instances.Size()))
158
159	// Get the current page of tokens based on the request path.
160	page := pager.MustGetPageByPath(req.RawPath)
161
162	// Render the list of tokens.
163	for _, item := range page.Items {
164		token := item.Value.(*instance).token
165		out += md.BulletItem(
166			md.Link(
167				ufmt.Sprintf("%s ($%s)", token.GetName(), token.GetSymbol()),
168				ufmt.Sprintf("/r/demo/grc20factory:%s", token.GetSymbol()),
169			),
170		)
171	}
172	out += "\n"
173
174	// Add the page picker.
175	out += md.Paragraph(page.Picker(req.Path))
176
177	res.Write(out)
178}
179
180func renderToken(res *mux.ResponseWriter, req *mux.Request) {
181	// Get the token symbol from the request.
182	symbol := req.GetVar("symbol")
183	inst := mustGetInstance(symbol)
184
185	// Render the token details.
186	out := inst.token.RenderHome()
187	out += md.BulletItem(
188		ufmt.Sprintf("%s: %s", md.Bold("Admin"), inst.admin.Owner()),
189	)
190
191	res.Write(out)
192}
193
194func renderBalance(res *mux.ResponseWriter, req *mux.Request) {
195	var (
196		symbol = req.GetVar("symbol")
197		addr   = req.GetVar("address")
198	)
199
200	// Get the balance of the specified address for the token.
201	inst := mustGetInstance(symbol)
202	balance := inst.token.BalanceOf(address(addr))
203
204	// Render the balance information.
205	out := md.Paragraph(
206		ufmt.Sprintf("%s balance: %d", md.Bold(addr), balance),
207	)
208
209	res.Write(out)
210}
211
212func mustGetInstance(symbol string) *instance {
213	t := instances.Get(symbol)
214	if t == nil {
215		panic("token instance does not exist")
216	}
217	return t.(*instance)
218}
219
220func checkErr(err error) {
221	if err != nil {
222		panic(err.Error())
223	}
224}