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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}