Smart Contract
BloctoToken
A.0f9df91c9121c460.BloctoToken
1import FungibleToken from 0xf233dcee88fe0abe
2import MetadataViews from 0x1d7e57aa55817448
3import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
4
5// Token contract of Blocto Token (BLT)
6access(all)
7contract BloctoToken: FungibleToken{
8
9 // An entitlement for Administrator access
10 access(all) entitlement AdministratorEntitlement
11 // An entitlement for Minter access
12 access(all) entitlement MinterEntitlement
13 // An entitlement for Burner access
14 access(all) entitlement BurnerEntitlement
15
16 // Total supply of Blocto tokens in existence
17 access(all)
18 var totalSupply: UFix64
19
20 // Defines token vault storage path
21 access(all)
22 let TokenStoragePath: StoragePath
23
24 // Defines token vault public balance path
25 access(all)
26 let TokenPublicBalancePath: PublicPath
27
28 // Defines token vault public receiver path
29 access(all)
30 let TokenPublicReceiverPath: PublicPath
31
32 // Defines token minter storage path
33 access(all)
34 let TokenMinterStoragePath: StoragePath
35
36 // Event that is emitted when the contract is created
37 access(all)
38 event TokensInitialized(initialSupply: UFix64)
39
40 // Event that is emitted when tokens are withdrawn from a Vault
41 access(all)
42 event TokensWithdrawn(amount: UFix64, from: Address?)
43
44 // Event that is emitted when tokens are deposited to a Vault
45 access(all)
46 event TokensDeposited(amount: UFix64, to: Address?)
47
48 // Event that is emitted when new tokens are minted
49 access(all)
50 event TokensMinted(amount: UFix64)
51
52 // Event that is emitted when tokens are destroyed
53 access(all)
54 event TokensBurned(amount: UFix64)
55
56 // Event that is emitted when a new minter resource is created
57 access(all)
58 event MinterCreated(allowedAmount: UFix64)
59
60 // Event that is emitted when a new burner resource is created
61 access(all)
62 event BurnerCreated()
63
64
65 // Gets a list of the metadata views that this contract supports
66 access(all) view fun getContractViews(resourceType: Type?): [Type] {
67 return [
68 Type<FungibleTokenMetadataViews.FTView>(),
69 Type<FungibleTokenMetadataViews.FTDisplay>(),
70 Type<FungibleTokenMetadataViews.FTVaultData>(),
71 Type<FungibleTokenMetadataViews.TotalSupply>()
72 ]
73 }
74
75 /// Get a Metadata View from BloctoToken
76 ///
77 /// @param view: The Type of the desired view.
78 /// @return A structure representing the requested view.
79 ///
80 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
81 switch viewType {
82 case Type<FungibleTokenMetadataViews.FTView>():
83 return FungibleTokenMetadataViews.FTView(
84 ftDisplay: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTDisplay>()) as! FungibleTokenMetadataViews.FTDisplay?,
85 ftVaultData: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
86 )
87 case Type<FungibleTokenMetadataViews.FTDisplay>():
88 let media = MetadataViews.Media(
89 file: MetadataViews.HTTPFile(
90 url: "https://raw.githubusercontent.com/blocto/assets/main/color/flow/blt.svg"
91 ),
92 mediaType: "image/svgxml"
93 )
94 let medias = MetadataViews.Medias([media])
95 return FungibleTokenMetadataViews.FTDisplay(
96 name: "Blocto Token",
97 symbol: "BLT",
98 description: "Blocto token (BLT) is the utility and governance token of Blocto.",
99 externalURL: MetadataViews.ExternalURL("https://blocto.io/"),
100 logos: medias,
101 socials: {
102 "x": MetadataViews.ExternalURL("https://x.com/BloctoApp")
103 }
104 )
105 case Type<FungibleTokenMetadataViews.FTVaultData>():
106 let vaultRef = BloctoToken.account.storage.borrow<auth(FungibleToken.Withdraw) &BloctoToken.Vault>(from: BloctoToken.TokenStoragePath)
107 ?? panic("Could not borrow reference to the contract's Vault!")
108 return FungibleTokenMetadataViews.FTVaultData(
109 storagePath: self.TokenStoragePath,
110 receiverPath: self.TokenPublicReceiverPath,
111 metadataPath: self.TokenPublicBalancePath,
112 receiverLinkedType: Type<&{FungibleToken.Receiver, FungibleToken.Vault}>(),
113 metadataLinkedType: Type<&{FungibleToken.Balance, FungibleToken.Vault}>(),
114 createEmptyVaultFunction: (fun (): @{FungibleToken.Vault} {
115 return <-vaultRef.createEmptyVault()
116 })
117 )
118 case Type<FungibleTokenMetadataViews.TotalSupply>():
119 return FungibleTokenMetadataViews.TotalSupply(totalSupply: BloctoToken.totalSupply)
120 }
121 return nil
122 }
123 // Vault
124 //
125 // Each user stores an instance of only the Vault in their storage
126 // The functions in the Vault and governed by the pre and post conditions
127 // in FungibleToken when they are called.
128 // The checks happen at runtime whenever a function is called.
129 //
130 // Resources can only be created in the context of the contract that they
131 // are defined in, so there is no way for a malicious user to create Vaults
132 // out of thin air. A special Minter resource needs to be defined to mint
133 // new tokens.
134 //
135 access(all)
136 resource Vault: FungibleToken.Vault {
137
138 // holds the balance of a users tokens
139 access(all)
140 var balance: UFix64
141
142 // initialize the balance at resource creation time
143 init(balance: UFix64){
144 self.balance = balance
145 }
146
147 // withdraw
148 //
149 // Function that takes an integer amount as an argument
150 // and withdraws that amount from the Vault.
151 // It creates a new temporary Vault that is used to hold
152 // the money that is being transferred. It returns the newly
153 // created Vault to the context that called so it can be deposited
154 // elsewhere.
155 //
156 access(FungibleToken.Withdraw)
157 fun withdraw(amount: UFix64): @{FungibleToken.Vault}{
158 self.balance = self.balance - amount
159 emit TokensWithdrawn(amount: amount, from: self.owner?.address)
160 return <-create Vault(balance: amount)
161 }
162
163 // deposit
164 //
165 // Function that takes a Vault object as an argument and adds
166 // its balance to the balance of the owners Vault.
167 // It is allowed to destroy the sent Vault because the Vault
168 // was a temporary holder of the tokens. The Vault's balance has
169 // been consumed and therefore can be destroyed.
170 access(all)
171 fun deposit(from: @{FungibleToken.Vault}): Void{
172 let vault <- from as! @BloctoToken.Vault
173 self.balance = self.balance + vault.balance
174 emit TokensDeposited(amount: vault.balance, to: self.owner?.address)
175 vault.balance = 0.0
176 destroy vault
177 }
178
179 access(all)
180 fun createEmptyVault(): @{FungibleToken.Vault}{
181 return <-create Vault(balance: 0.0)
182 }
183
184 access(all)
185 view fun isAvailableToWithdraw(amount: UFix64): Bool{
186 return self.balance >= amount
187 }
188
189
190 // Called when a fungible token is burned via the `Burner.burn()` method
191 access(contract) fun burnCallback() {
192 if self.balance > 0.0 {
193 BloctoToken.totalSupply = BloctoToken.totalSupply - self.balance
194 }
195 self.balance = 0.0
196 }
197
198 // getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts
199 access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
200 return {self.getType(): true}
201 }
202
203 access(all) view fun isSupportedVaultType(type: Type): Bool {
204 if (type == self.getType()) { return true } else { return false }
205 }
206
207
208 // Get all the Metadata Views implemented by FlowToken
209 //
210 // @return An array of Types defining the implemented views. This value will be used by
211 // developers to know which parameter to pass to the resolveView() method.
212 //
213 access(all) view fun getViews(): [Type]{
214 return BloctoToken.getContractViews(resourceType: nil)
215 }
216
217 // Get a Metadata View from FlowToken
218 //
219 // @param view: The Type of the desired view.
220 // @return A structure representing the requested view.
221 //
222 access(all) fun resolveView(_ view: Type): AnyStruct? {
223 return BloctoToken.resolveContractView(resourceType: nil, viewType: view)
224 }
225 }
226
227 // createEmptyVault
228 //
229 // Function that creates a new Vault with a balance of zero
230 // and returns it to the calling context. A user must call this function
231 // and store the returned Vault in their storage in order to allow their
232 // account to be able to receive deposits of this token type.
233 //
234 access(all)
235 fun createEmptyVault(vaultType: Type): @BloctoToken.Vault {
236 return <-create Vault(balance: 0.0)
237 }
238
239 access(all)
240 resource Administrator{
241 // createNewMinter
242 //
243 // Function that creates and returns a new minter resource
244 //
245 access(AdministratorEntitlement)
246 fun createNewMinter(allowedAmount: UFix64): @Minter{
247 emit MinterCreated(allowedAmount: allowedAmount)
248 return <-create Minter(allowedAmount: allowedAmount)
249 }
250
251 // createNewBurner
252 //
253 // Function that creates and returns a new burner resource
254 //
255 access(AdministratorEntitlement)
256 fun createNewBurner(): @Burner{
257 emit BurnerCreated()
258 return <-create Burner()
259 }
260 }
261
262 // Minter
263 //
264 // Resource object that token admin accounts can hold to mint new tokens.
265 //
266 access(all)
267 resource Minter{
268
269 // the amount of tokens that the minter is allowed to mint
270 access(all)
271 var allowedAmount: UFix64
272
273 // mintTokens
274 //
275 // Function that mints new tokens, adds them to the total supply,
276 // and returns them to the calling context.
277 //
278 access(MinterEntitlement)
279 fun mintTokens(amount: UFix64): @BloctoToken.Vault{
280 pre{
281 amount > 0.0:
282 "Amount minted must be greater than zero"
283 amount <= self.allowedAmount:
284 "Amount minted must be less than the allowed amount"
285 }
286 BloctoToken.totalSupply = BloctoToken.totalSupply + amount
287 self.allowedAmount = self.allowedAmount - amount
288 emit TokensMinted(amount: amount)
289 return <-create Vault(balance: amount)
290 }
291
292 init(allowedAmount: UFix64){
293 self.allowedAmount = allowedAmount
294 }
295 }
296
297 // Burner
298 //
299 // Resource object that token admin accounts can hold to burn tokens.
300 //
301 access(all)
302 resource Burner{
303
304 // burnTokens
305 //
306 // Function that destroys a Vault instance, effectively burning the tokens.
307 //
308 // Note: the burned tokens are automatically subtracted from the
309 // total supply in the Vault destructor.
310 //
311 access(BurnerEntitlement)
312 fun burnTokens(from: @{FungibleToken.Vault}){
313 let vault <- from as! @BloctoToken.Vault
314 let amount = vault.balance
315 BloctoToken.totalSupply = BloctoToken.totalSupply - amount
316 destroy vault
317 emit TokensBurned(amount: amount)
318 }
319 }
320
321 init() {
322 // Total supply of BLT is 500M
323 // 70% is created at genesis but locked up
324 // 30% will minted from staking and mining
325 self.totalSupply = 350_000_000.0
326
327 self.TokenStoragePath = /storage/bloctoTokenVault
328 self.TokenPublicReceiverPath = /public/bloctoTokenReceiver
329 self.TokenPublicBalancePath = /public/bloctoTokenBalance
330 self.TokenMinterStoragePath = /storage/bloctoTokenMinter
331
332 // Create the Vault with the total supply of tokens and save it in storage
333 let vault <- create Vault(balance: self.totalSupply)
334 self.account.storage.save(<-vault, to: self.TokenStoragePath)
335
336 // Create a public capability to the stored Vault that only exposes
337 // the `deposit` method through the `Receiver` interface
338 var capability_1 = self.account.capabilities.storage.issue<&BloctoToken.Vault>(self.TokenStoragePath)
339 self.account.capabilities.publish(capability_1, at: self.TokenPublicReceiverPath)
340
341 // Create a public capability to the stored Vault that only exposes
342 // the `balance` field through the `Balance` interface
343 var capability_2 = self.account.capabilities.storage.issue<&BloctoToken.Vault>(self.TokenStoragePath)
344 self.account.capabilities.publish(capability_2, at: self.TokenPublicBalancePath)
345 let admin <- create Administrator()
346 self.account.storage.save(<-admin, to: /storage/bloctoTokenAdmin)
347
348 // Emit an event that shows that the contract was initialized
349 emit TokensInitialized(initialSupply: self.totalSupply)
350 }
351}
352