tokenfactory.gno
4.59 Kb · 280 lines
1package tokenfactory
2
3import (
4 "chain"
5 "chain/banker"
6 "chain/runtime/unsafe"
7
8 "gno.land/p/nt/avl/v0"
9 "gno.land/p/nt/grc20/v0"
10 "gno.land/p/nt/ownable/v0"
11 "gno.land/p/nt/seqid/v0"
12 "gno.land/r/nt/grc20reg/v0"
13)
14
15const creationFee = int64(40000000) // 40 GNOT
16
17var (
18 instances avl.Tree
19 nextTokenID seqid.ID
20
21 feeRecipient = address(
22 "g17mgsf396a9ysyj4z22ft0zc0xv8ka6dns4fx7f",
23 )
24)
25
26type instance struct {
27 token *grc20.Token
28 ledger *grc20.PrivateLedger
29 admin *ownable.Ownable
30}
31
32// CreationFee returns the token creation fee in ugnot.
33func CreationFee() int64 {
34 return creationFee
35}
36
37// FeeRecipient returns the address receiving creation fees.
38func FeeRecipient() address {
39 return feeRecipient
40}
41
42// CreateToken creates a new GRC20 token.
43//
44// initialSupply is expressed in the token's smallest unit.
45//
46// Example:
47// decimals = 6
48// initialSupply = 1000000000000
49// => 1,000,000 tokens.
50//
51// A direct user call must attach exactly 40 GNOT.
52func CreateToken(
53 cur realm,
54 name string,
55 symbol string,
56 decimals int,
57 initialSupply int64,
58) string {
59 // Creation must come directly from a user wallet.
60 if !cur.Previous().IsUserCall() {
61 panic("token creation must be called directly by a user")
62 }
63
64 // Exactly 40 GNOT must be attached.
65 if unsafe.OriginSend().AmountOf("ugnot") != creationFee {
66 panic("invalid token creation fee")
67 }
68
69 if name == "" {
70 panic("token name cannot be empty")
71 }
72
73 if symbol == "" {
74 panic("token symbol cannot be empty")
75 }
76
77 if decimals < 0 {
78 panic("decimals cannot be negative")
79 }
80
81 if initialSupply < 0 {
82 panic("initial supply cannot be negative")
83 }
84
85 if instances.Has(symbol) {
86 panic("token symbol already exists")
87 }
88
89 creator := cur.Previous().Address()
90
91 token, ledger := grc20.NewToken(
92 name,
93 symbol,
94 decimals,
95 nextTokenID.Next(),
96 cur,
97 )
98
99 // Mint the user-selected initial supply directly
100 // to the wallet that created the token.
101 if initialSupply > 0 {
102 checkErr(
103 ledger.Mint(
104 creator,
105 initialSupply,
106 ),
107 )
108 }
109
110 inst := instance{
111 token: token,
112 ledger: ledger,
113 admin: ownable.NewWithAddress(creator),
114 }
115
116 instances.Set(
117 symbol,
118 &inst,
119 )
120
121 // Register the token in the canonical GRC20 registry.
122 grc20reg.Register(
123 cross(cur),
124 token,
125 symbol,
126 )
127
128 // Transfer the 40 GNOT creation fee
129 // from the Token Factory realm directly
130 // to the GNO Explorer fee recipient.
131 banker_ := banker.NewBanker(
132 banker.BankerTypeRealmSend,
133 cur,
134 )
135
136 feeCoins := chain.NewCoins(
137 chain.NewCoin(
138 "ugnot",
139 creationFee,
140 ),
141 )
142
143 banker_.SendCoins(
144 cur.Address(),
145 feeRecipient,
146 feeCoins,
147 )
148
149 return token.ID()
150}
151
152// Bank returns the GRC20 token instance.
153func Bank(symbol string) *grc20.Token {
154 return mustGetInstance(symbol).token
155}
156
157// TokenID returns the unique GRC20 token ID.
158func TokenID(symbol string) string {
159 return mustGetInstance(symbol).token.ID()
160}
161
162// TotalSupply returns the total token supply.
163func TotalSupply(symbol string) int64 {
164 return mustGetInstance(symbol).
165 token.
166 ReadonlyTeller().
167 TotalSupply()
168}
169
170// BalanceOf returns the token balance of an address.
171func BalanceOf(
172 symbol string,
173 owner address,
174) int64 {
175 return mustGetInstance(symbol).
176 token.
177 ReadonlyTeller().
178 BalanceOf(owner)
179}
180
181// Mint allows the token owner to mint additional tokens.
182func Mint(
183 cur realm,
184 symbol string,
185 to address,
186 amount int64,
187) {
188 if amount <= 0 {
189 panic("mint amount must be greater than zero")
190 }
191
192 inst := mustGetInstance(symbol)
193
194 inst.admin.AssertOwnedBy(
195 cur.Previous().Address(),
196 )
197
198 checkErr(
199 inst.ledger.Mint(
200 to,
201 amount,
202 ),
203 )
204}
205
206// Burn allows the token owner to burn tokens.
207func Burn(
208 cur realm,
209 symbol string,
210 from address,
211 amount int64,
212) {
213 if amount <= 0 {
214 panic("burn amount must be greater than zero")
215 }
216
217 inst := mustGetInstance(symbol)
218
219 inst.admin.AssertOwnedBy(
220 cur.Previous().Address(),
221 )
222
223 checkErr(
224 inst.ledger.Burn(
225 from,
226 amount,
227 ),
228 )
229}
230
231// TransferTokenOwnership transfers administration
232// of a token to another address.
233func TransferTokenOwnership(
234 cur realm,
235 symbol string,
236 newOwner address,
237) {
238 inst := mustGetInstance(symbol)
239
240 checkErr(
241 inst.admin.TransferOwnership(
242 0,
243 cur,
244 newOwner,
245 ),
246 )
247}
248
249// DropTokenOwnership permanently removes
250// the administrator of the token.
251func DropTokenOwnership(
252 cur realm,
253 symbol string,
254) {
255 inst := mustGetInstance(symbol)
256
257 checkErr(
258 inst.admin.DropOwnership(
259 0,
260 cur,
261 ),
262 )
263}
264
265func mustGetInstance(
266 symbol string,
267) *instance {
268 value := instances.Get(symbol)
269
270 if value == nil {
271 panic("token does not exist")
272 }
273
274 return value.(*instance)
275}
276
277func checkErr(err error) {
278 if err != nil {
279 panic(err.Error())
280 }
281}