Search Apps Documentation Source Content File Folder Download Copy Actions Download

grc20factory.gno

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