Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}