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}