Smart Contract
FungibleTokenManager
A.d2abb5dbf5e08666.FungibleTokenManager
1/**
2> Author: Fixes Lab <https://github.com/fixes-world/>
3
4# Fungible Token Manager
5
6This contract is used to manage the account and contract of Fixes' Fungible Tokens
7
8*/
9// Third Party Imports
10import FungibleToken from 0xf233dcee88fe0abe
11import FlowToken from 0x1654653399040a61
12import StringUtils from 0xa340dc0a4ec828ab
13import FTViewUtils from 0x15a918087ab12d86
14import ViewResolver from 0x1d7e57aa55817448
15import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
16// Fixes imports
17import Fixes from 0xd2abb5dbf5e08666
18import FixesInscriptionFactory from 0xd2abb5dbf5e08666
19import FixesHeartbeat from 0xd2abb5dbf5e08666
20import FixesFungibleTokenInterface from 0xd2abb5dbf5e08666
21import FixesTradablePool from 0xd2abb5dbf5e08666
22import FixesTokenLockDrops from 0xd2abb5dbf5e08666
23import FixesTokenAirDrops from 0xd2abb5dbf5e08666
24import FRC20FTShared from 0xd2abb5dbf5e08666
25import FRC20Indexer from 0xd2abb5dbf5e08666
26import FRC20AccountsPool from 0xd2abb5dbf5e08666
27import FRC20TradingRecord from 0xd2abb5dbf5e08666
28import FRC20StakingManager from 0xd2abb5dbf5e08666
29import FRC20Agents from 0xd2abb5dbf5e08666
30import FRC20Converter from 0xd2abb5dbf5e08666
31import FGameLottery from 0xd2abb5dbf5e08666
32import FGameLotteryRegistry from 0xd2abb5dbf5e08666
33
34/// The Manager contract for Fungible Token
35///
36access(all) contract FungibleTokenManager {
37
38 access(all) entitlement Sudo
39
40 /* --- Events --- */
41 /// Event emitted when the contract is initialized
42 access(all) event ContractInitialized()
43 /// Event emitted when a new Fungible Token Account is created
44 access(all) event FungibleTokenAccountCreated(
45 symbol: String,
46 account: Address,
47 by: Address
48 )
49 /// Event emitted when the contract of Fungible Token is updated
50 access(all) event FungibleTokenManagerUpdated(
51 symbol: String,
52 manager: Address,
53 flag: Bool
54 )
55 /// Event emitted when the resources of a Fungible Token Account are updated
56 access(all) event FungibleTokenAccountResourcesUpdated(
57 symbol: String,
58 account: Address,
59 )
60 /// Event emitted when the contract of FRC20 Fungible Token is updated
61 access(all) event FungibleTokenContractUpdated(
62 symbol: String,
63 account: Address,
64 contractName: String
65 )
66
67 /* --- Variable, Enums and Structs --- */
68 access(all)
69 let AdminStoragePath: StoragePath
70 access(all)
71 let AdminPublicPath: PublicPath
72
73 /* --- Interfaces & Resources --- */
74
75 access(all) resource interface AdminPublic {
76 /// get the list of initialized fungible tokens
77 access(all)
78 view fun getFungibleTokens(): [String]
79 /// get the address of the fungible token account
80 access(all)
81 view fun getFungibleTokenAccount(tick: String): Address?
82 }
83
84 /// Admin Resource, represents an admin resource and store in admin's account
85 ///
86 access(all) resource Admin: AdminPublic {
87
88 // ---- Public Methods ----
89
90 /// get the list of initialized fungible tokens
91 ///
92 access(all)
93 view fun getFungibleTokens(): [String] {
94 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
95 let dict = acctsPool.getAddresses(type: FRC20AccountsPool.ChildAccountType.FungibleToken)
96 return dict.keys
97 }
98
99 /// get the address of the fungible token account
100 access(all)
101 view fun getFungibleTokenAccount(tick: String): Address? {
102 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
103 return acctsPool.getFTContractAddress(tick)
104 }
105
106 // ---- Developer Methods ----
107
108 /// update all children contracts
109 access(Sudo)
110 fun updateAllChildrenContracts() {
111 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
112 let dict = acctsPool.getAddresses(type: FRC20AccountsPool.ChildAccountType.FungibleToken)
113 let ticks = dict.keys
114 // update the contracts
115 for tick in ticks {
116 if tick[0] == "$" {
117 FungibleTokenManager._updateFungibleTokenContractInAccount(tick, contractName: "FixesFungibleToken")
118 } else {
119 FungibleTokenManager._updateFungibleTokenContractInAccount(tick, contractName: "FRC20FungibleToken")
120 }
121 }
122 }
123 }
124
125 // ---------- Manager Resource ----------
126
127 // Add deployer Resrouce to record all coins minted by the deployer
128
129 access(all) resource interface ManagerPublic {
130 access(all)
131 view fun getManagedFungibleTokens(): [String]
132 access(all)
133 view fun getManagedFungibleTokenAddresses(): [Address]
134 access(all)
135 view fun getManagedFungibleTokenAmount(): Int
136 access(all)
137 view fun getCreatedFungibleTokens(): [String]
138 access(all)
139 view fun getCreatedFungibleTokenAddresses(): [Address]
140 access(all)
141 view fun getCreatedFungibleTokenAmount(): Int
142 // ----- Internal Methods -----
143 access(contract)
144 fun setFungibleTokenManaged(
145 _ symbol: String,
146 _ storeCap: Capability<auth(FRC20FTShared.Write) &FRC20FTShared.SharedStore>?
147 )
148 access(contract)
149 fun addCreatedFungibleToken(
150 _ symbol: String,
151 _ storeCap: Capability<auth(FRC20FTShared.Write) &FRC20FTShared.SharedStore>
152 )
153 }
154
155 access(all) resource Manager: ManagerPublic {
156 access(self)
157 let createdSymbols: [String]
158 access(self)
159 let managedSymbols: [String]
160 access(self)
161 let managedStores: {String: Capability<auth(FRC20FTShared.Write) &FRC20FTShared.SharedStore>}
162
163 init() {
164 self.managedStores = {}
165 self.managedSymbols = []
166 self.createdSymbols = []
167 }
168
169 // ---- Public Methods ----
170
171 access(all)
172 view fun getManagedFungibleTokens(): [String] {
173 return self.managedSymbols
174 }
175
176 access(all)
177 view fun getManagedFungibleTokenAddresses(): [Address] {
178 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
179 var addrs: [Address] = []
180 for symbol in self.managedSymbols {
181 if let addr = acctsPool.getFTContractAddress(symbol) {
182 addrs = addrs.concat([addr])
183 }
184 }
185 return addrs
186 }
187
188 access(all)
189 view fun getManagedFungibleTokenAmount(): Int {
190 return self.managedSymbols.length
191 }
192
193 access(all)
194 view fun getCreatedFungibleTokens(): [String] {
195 return self.createdSymbols
196 }
197
198 access(all)
199 view fun getCreatedFungibleTokenAddresses(): [Address] {
200 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
201 var addrs: [Address] = []
202 for symbol in self.createdSymbols {
203 if let addr = acctsPool.getFTContractAddress(symbol) {
204 addrs = addrs.concat([addr])
205 }
206 }
207 return addrs
208 }
209
210 access(all)
211 view fun getCreatedFungibleTokenAmount(): Int {
212 return self.createdSymbols.length
213 }
214
215 // ---- Admin Methods ----
216
217 /// Borrow the shared store of the managed fungible token
218 ///
219 access(Sudo)
220 view fun borrowManagedFTStore(_ symbol: String): auth(FRC20FTShared.Write) &FRC20FTShared.SharedStore? {
221 if let cap = self.managedStores[symbol] {
222 return cap.borrow()
223 }
224 return nil
225 }
226
227 // ---- Internal Methods ----
228
229 /// set the managed fungible token
230 ///
231 access(contract)
232 fun setFungibleTokenManaged(
233 _ symbol: String,
234 _ storeCap: Capability<auth(FRC20FTShared.Write) &FRC20FTShared.SharedStore>?
235 ) {
236 pre {
237 storeCap == nil || storeCap!.check() == true: "The store capability is invalid"
238 }
239 let isManaged = self.managedSymbols.contains(symbol)
240 var isUpdated = false
241 if storeCap != nil && !isManaged {
242 self.managedSymbols.append(symbol)
243 self.managedStores[symbol] = storeCap!
244 isUpdated = true
245 } else if storeCap == nil && isManaged {
246 self.managedSymbols.remove(at: self.managedSymbols.firstIndex(of: symbol)!)
247 isUpdated = true
248 }
249
250 if isUpdated {
251 emit FungibleTokenManagerUpdated(symbol: symbol, manager: self.owner?.address!, flag: storeCap != nil)
252 }
253 }
254
255 /// set the created fungible token
256 ///
257 access(contract)
258 fun addCreatedFungibleToken(
259 _ symbol: String,
260 _ storeCap: Capability<auth(FRC20FTShared.Write) &FRC20FTShared.SharedStore>
261 ) {
262 pre {
263 storeCap.check() == true: "The store capability is invalid"
264 }
265 if !self.createdSymbols.contains(symbol) {
266 self.createdSymbols.append(symbol)
267 self.setFungibleTokenManaged(symbol, storeCap)
268 }
269 }
270 }
271
272 /// Create the Manager Resource
273 ///
274 access(all)
275 fun createManager(): @Manager {
276 return <- create Manager()
277 }
278
279 /// The storage path of Manager resource
280 ///
281 access(all)
282 view fun getManagerStoragePath(): StoragePath {
283 let identifier = "FungibleTokenManager_".concat(self.account.address.toString())
284 return StoragePath(identifier: identifier.concat("_manager"))!
285 }
286
287 /// The public path of Manager resource
288 ///
289 access(all)
290 view fun getManagerPublicPath(): PublicPath {
291 let identifier = "FungibleTokenManager_".concat(self.account.address.toString())
292 return PublicPath(identifier: identifier.concat("_manager"))!
293 }
294
295 /// Borrow the Manager Resource
296 ///
297 access(all)
298 view fun borrowFTManager(_ addr: Address): &Manager? {
299 return getAccount(addr)
300 .capabilities.get<&Manager>(self.getManagerPublicPath())
301 .borrow()
302 }
303
304 /** ------- Public Methods ---- */
305
306 /// Check if the Fungible Token Symbol is already enabled
307 ///
308 access(all)
309 view fun isTokenSymbolEnabled(_ tick: String): Bool {
310 return self.getFTContractAddress(tick) != nil
311 }
312
313 /// Get the Fungible Token Account Address
314 ///
315 access(all)
316 view fun getFTContractAddress(_ tick: String): Address? {
317 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
318 return acctsPool.getFTContractAddress(tick)
319 }
320
321 /// Borrow the global public of Fixes Fungible Token contract
322 ///
323 access(all)
324 view fun borrowFTGlobalPublic(_ tick: String): &{FixesFungibleTokenInterface.IGlobalPublic}? {
325 // singleton resources
326 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
327 // borrow the contract
328 if let contractRef = acctsPool.borrowFTContract(tick) {
329 return contractRef.borrowGlobalPublic()
330 }
331 return nil
332 }
333
334 /// Borrow the ft interface
335 ///
336 access(all)
337 view fun borrowFixesFTInterface(_ addr: Address): &{FixesFungibleTokenInterface}? {
338 let ftAcct = getAccount(addr)
339 var ftName = "FixesFungibleToken"
340 var ftContract = ftAcct.contracts.borrow<&{FixesFungibleTokenInterface}>(name: ftName)
341 if ftContract == nil {
342 ftName = "FRC20FungibleToken"
343 ftContract = ftAcct.contracts.borrow<&{FixesFungibleTokenInterface}>(name: ftName)
344 }
345 return ftContract
346 }
347
348 /// Check if the user is authorized to access the Fixes Fungible Token manager
349 ///
350 access(all)
351 view fun isFTContractAuthorizedUser(_ tick: String, _ callerAddr: Address): Bool {
352 let globalPublicRef = self.borrowFTGlobalPublic(tick)
353 return globalPublicRef?.isAuthorizedUser(callerAddr) ?? false
354 }
355
356 /// Build the Standard Token View
357 ///
358 access(all)
359 fun buildStandardTokenView(_ ftAddress: Address, _ ftName: String): FTViewUtils.StandardTokenView? {
360 if let viewResolver = getAccount(ftAddress).contracts.borrow<&{ViewResolver}>(name: ftName) {
361 let vaultData = viewResolver.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
362 let display = viewResolver.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTDisplay>()) as! FungibleTokenMetadataViews.FTDisplay?
363 if vaultData == nil || display == nil {
364 return nil
365 }
366 return FTViewUtils.StandardTokenView(
367 identity: FTViewUtils.FTIdentity(ftAddress, ftName),
368 decimals: 8,
369 tags: [],
370 dataSource: ftAddress,
371 paths: FTViewUtils.StandardTokenPaths(
372 vaultPath: vaultData!.storagePath,
373 balancePath: vaultData!.metadataPath,
374 receiverPath: vaultData!.receiverPath,
375 ),
376 display: FTViewUtils.FTDisplayWithSource(ftAddress, display!),
377 )
378 }
379 return nil
380 }
381
382 /// The struct of Fixes Token View
383 ///
384 access(all) struct FixesTokenView {
385 access(all) let standardView: FTViewUtils.StandardTokenView
386 access(all) let deployer: Address
387 access(all) let accountKey: String
388 access(all) let maxSupply: UFix64
389 access(all) let extra: {String: String}
390
391 init(
392 _ standardView: FTViewUtils.StandardTokenView,
393 _ deployer: Address,
394 _ accountKey: String,
395 _ maxSupply: UFix64,
396 _ extra: {String: String}
397 ) {
398 self.standardView = standardView
399 self.deployer = deployer
400 self.accountKey = accountKey
401 self.maxSupply = maxSupply
402 self.extra = extra
403 }
404 }
405
406 /// Build the Fixes Token View
407 ///
408 access(all)
409 fun buildFixesTokenView(_ ftAddress: Address, _ ftName: String): FixesTokenView? {
410 if let ftInterface = getAccount(ftAddress).contracts.borrow<&{FixesFungibleTokenInterface}>(name: ftName) {
411 let deployer = ftInterface.getDeployerAddress()
412 let symbol = ftInterface.getSymbol()
413 let accountKey = ftName == "FixesFungibleToken" ? "$".concat(symbol) : symbol
414 let maxSupply = ftInterface.getMaxSupply() ?? UFix64.max
415 if let tokenView = self.buildStandardTokenView(ftAddress, ftName) {
416 return FixesTokenView(tokenView, deployer, accountKey, maxSupply, {})
417 }
418 }
419 return nil
420 }
421
422 /// The struct of Fixes Token Modules
423 ///
424 access(all) struct FixesTokenModules {
425 access(all) let address: Address
426 access(all) let supportedMinters: [Type]
427
428 init(
429 _ address: Address,
430 ) {
431 self.address = address
432 self.supportedMinters = []
433
434 self.sync()
435 }
436
437 access(all)
438 fun sync() {
439 // Try to add tradable pool
440 let tradablePool = FixesTradablePool.borrowTradablePool(self.address)
441 if tradablePool != nil {
442 self.supportedMinters.append(tradablePool!.getType())
443 }
444 // Try to add lockdrops pool
445 let lockdropsPool = FixesTokenLockDrops.borrowDropsPool(self.address)
446 if lockdropsPool != nil {
447 self.supportedMinters.append(lockdropsPool!.getType())
448 }
449 // Try to add airdrops pool
450 let airdropsPool = FixesTokenAirDrops.borrowAirdropPool(self.address)
451 if airdropsPool != nil {
452 self.supportedMinters.append(airdropsPool!.getType())
453 }
454 // Try to add lottery pool
455 let lotteryPool = FGameLottery.borrowLotteryPool(self.address)
456 if lotteryPool != nil {
457 self.supportedMinters.append(lotteryPool!.getType())
458 }
459 }
460
461 access(all)
462 view fun isTradablePoolSupported(): Bool {
463 return self.supportedMinters.contains(Type<@FixesTradablePool.TradableLiquidityPool>())
464 }
465
466 access(all)
467 view fun isLockdropsPoolSupported(): Bool {
468 return self.supportedMinters.contains(Type<@FixesTokenLockDrops.DropsPool>())
469 }
470
471 access(all)
472 view fun isAirdropsPoolSupported(): Bool {
473 return self.supportedMinters.contains(Type<@FixesTokenAirDrops.AirdropPool>())
474 }
475
476 access(all)
477 view fun isLotteryPoolSupported(): Bool {
478 return self.supportedMinters.contains(Type<@FGameLottery.LotteryPool>())
479 }
480 }
481
482 /// The Fixes Token Info
483 ///
484 access(all) struct FixesTokenInfo {
485 access(all) let view: FixesTokenView
486 access(all) let modules: FixesTokenModules
487 access(all) let extra: {String: AnyStruct}
488
489 init(
490 _ view: FixesTokenView,
491 _ modules: FixesTokenModules
492 ) {
493 self.view = view
494 self.modules = modules
495 self.extra = {}
496 }
497
498 access(contract)
499 fun setExtra(_ key: String, _ value: AnyStruct) {
500 self.extra[key] = value
501 }
502 }
503
504 /// Build the Fixes Token Info
505 ///
506 access(all)
507 fun buildFixesTokenInfo(_ ftAddress: Address, _ acctKey: String?): FixesTokenInfo? {
508 let ftAcct = getAccount(ftAddress)
509 var ftName = "FixesFungibleToken"
510 var ftContract: &{FixesFungibleTokenInterface}? = nil
511 if acctKey != nil {
512 ftName = acctKey![0] == "$" ? "FixesFungibleToken" : "FRC20FungibleToken"
513 ftContract = ftAcct.contracts.borrow<&{FixesFungibleTokenInterface}>(name: ftName)
514 } else {
515 ftContract = ftAcct.contracts.borrow<&{FixesFungibleTokenInterface}>(name: ftName)
516 if ftContract == nil {
517 ftName = "FRC20FungibleToken"
518 ftContract = ftAcct.contracts.borrow<&{FixesFungibleTokenInterface}>(name: ftName)
519 }
520 }
521 if ftContract == nil {
522 return nil
523 }
524 if let tokenView = self.buildFixesTokenView(ftAddress, ftName) {
525 let modules = FixesTokenModules(ftAddress)
526 let info = FixesTokenInfo(tokenView, modules)
527 var totalAllocatedSupply = 0.0
528 var totalCirculatedSupply = 0.0
529 // update modules info with extra fields
530 if modules.isTradablePoolSupported() {
531 let tradablePool = FixesTradablePool.borrowTradablePool(ftAddress)!
532 info.setExtra("tradable:allocatedSupply", tradablePool.getTotalAllowedMintableAmount())
533 info.setExtra("tradable:supplied", tradablePool.getTradablePoolCirculatingSupply())
534 info.setExtra("tradable:flowInPool", tradablePool.getFlowBalanceInPool())
535 info.setExtra("tradable:liquidityMcap", tradablePool.getLiquidityMarketCap())
536 info.setExtra("tradable:burnedLPAmount", tradablePool.getBurnedLP())
537 info.setExtra("tradable:burnedSupply", tradablePool.getBurnedTokenAmount())
538 info.setExtra("tradable:burnedLiquidityValue", tradablePool.getBurnedLiquidityValue())
539 info.setExtra("tradable:targetMcap", FixesTradablePool.getTargetMarketCap())
540 info.setExtra("tradable:isLocalActive", tradablePool.isLocalActive())
541 info.setExtra("tradable:isHandovered", tradablePool.isLiquidityHandovered())
542 info.setExtra("tradable:handoveringTime", tradablePool.getHandoveredAt())
543 info.setExtra("tradable:freeAmount", tradablePool.getFreeAmount())
544 info.setExtra("tradable:subjectFeePerc", tradablePool.getSubjectFeePercentage())
545 info.setExtra("tradable:swapPairAddr", tradablePool.getSwapPairAddress())
546 totalAllocatedSupply = totalAllocatedSupply + tradablePool.getTotalAllowedMintableAmount()
547 totalCirculatedSupply = totalCirculatedSupply + tradablePool.getTotalMintedAmount()
548 // update the total token market cap
549 info.setExtra("token:totalValue", tradablePool.getTotalTokenValue())
550 info.setExtra("token:totalMcap", tradablePool.getTotalTokenMarketCap())
551 info.setExtra("token:price", tradablePool.getTokenPriceInFlow())
552 info.setExtra("token:priceByLiquidity", tradablePool.getTokenPriceByInPoolLiquidity())
553 }
554 if modules.isLockdropsPoolSupported() {
555 let lockdropsPool = FixesTokenLockDrops.borrowDropsPool(ftAddress)!
556 info.setExtra("lockdrops:allocatedSupply", lockdropsPool.getTotalAllowedMintableAmount())
557 info.setExtra("lockdrops:supplied", lockdropsPool.getTotalMintedAmount())
558 info.setExtra("lockdrops:lockingTicker", lockdropsPool.getLockingTokenTicker())
559 info.setExtra("lockdrops:isClaimable", lockdropsPool.isClaimable())
560 info.setExtra("lockdrops:isActivated", lockdropsPool.isActivated())
561 info.setExtra("lockdrops:activatingTime", lockdropsPool.getActivatingTime())
562 info.setExtra("lockdrops:isDeprecated", lockdropsPool.isDeprecated())
563 info.setExtra("lockdrops:deprecatingTime", lockdropsPool.getDeprecatingTime())
564 info.setExtra("lockdrops:currentMintableAmount", lockdropsPool.getCurrentMintableAmount())
565 info.setExtra("lockdrops:unclaimedSupply", lockdropsPool.getUnclaimedBalanceInPool())
566 info.setExtra("lockdrops:totalLockedAmount",lockdropsPool.getTotalLockedTokenBalance())
567 let lockingPeriods = lockdropsPool.getLockingPeriods()
568 for i, period in lockingPeriods {
569 let periodKey = "lockdrops:lockingChoice.".concat(i.toString()).concat(".")
570 info.setExtra(periodKey.concat("period"), period)
571 info.setExtra(periodKey.concat("rate"), lockdropsPool.getExchangeRate(period))
572 }
573 totalAllocatedSupply = totalAllocatedSupply + lockdropsPool.getTotalAllowedMintableAmount()
574 totalCirculatedSupply = totalCirculatedSupply + lockdropsPool.getTotalMintedAmount()
575 }
576 if modules.isAirdropsPoolSupported() {
577 let airdropsPool = FixesTokenAirDrops.borrowAirdropPool(ftAddress)!
578 info.setExtra("airdrops:allocatedSupply", airdropsPool.getTotalAllowedMintableAmount())
579 info.setExtra("airdrops:supplied", airdropsPool.getTotalMintedAmount())
580 info.setExtra("airdrops:isClaimable", airdropsPool.isClaimable())
581 info.setExtra("airdrops:currentMintableAmount", airdropsPool.getCurrentMintableAmount())
582 info.setExtra("airdrops:totalClaimableAmount", airdropsPool.getTotalClaimableAmount())
583 totalAllocatedSupply = totalAllocatedSupply + airdropsPool.getTotalAllowedMintableAmount()
584 totalCirculatedSupply = totalCirculatedSupply + airdropsPool.getTotalMintedAmount()
585 }
586 if modules.isLotteryPoolSupported() {
587 let lotteryPool = FGameLottery.borrowLotteryPool(ftAddress)!
588 info.setExtra("lottery:currentEpochIndex", lotteryPool.getCurrentEpochIndex()) // UInt64
589 info.setExtra("lottery:epochInternal", lotteryPool.getEpochInterval()) // UFix64
590 info.setExtra("lottery:lotteryToken", lotteryPool.getLotteryToken()) // String
591 info.setExtra("lottery:ticketPrice", lotteryPool.getTicketPrice()) // UFix64
592 info.setExtra("lottery:jackpotAmount", lotteryPool.getJackpotPoolBalance()) // UFix64
593 info.setExtra("lottery:totalPoolAmount", lotteryPool.getPoolTotalBalance()) // UFix64
594 info.setExtra("lottery:currentParticipants", lotteryPool.borrowCurrentLottery()?.getParticipantAmount() ?? 0) // UInt64
595 }
596
597 // Total Supply Metadata
598 info.setExtra("total:allocatedSupply", totalAllocatedSupply)
599 info.setExtra("total:supplied", totalCirculatedSupply)
600 // Token Metadata
601 info.setExtra("token:deployedAt", ftContract!.getDeployedAt())
602 // borrow trading records
603 if let records = FRC20TradingRecord.borrowTradingRecords(ftAddress) {
604 let status = records.getStatus()
605 info.setExtra("token:transactions", status.sales)
606 info.setExtra("token:totalTradedVolume", status.volume)
607 info.setExtra("token:totalTradedAmount", status.dealAmount)
608 } else {
609 info.setExtra("token:transactions", 0)
610 info.setExtra("token:totalTradedVolume", 0.0)
611 info.setExtra("token:totalTradedAmount", 0.0)
612 }
613 return info
614 }
615 return nil
616 }
617
618 /// Enable the Fixes Fungible Token
619 ///
620 access(all)
621 fun initializeFixesFungibleTokenAccount(
622 _ ins: auth(Fixes.Extractable) &Fixes.Inscription,
623 newAccount: Capability<auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account>,
624 ) {
625 post {
626 ins.isValueEmpty(): "The inscription is not empty"
627 }
628 // singletoken resources
629 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
630
631 // inscription data
632 let meta = self.verifyExecutingInscription(ins, usage: "init-ft")
633 let tick = meta["tick"] ?? panic("The token symbol is not found")
634
635 // The reserved symbol for Fixes Fungible Token
636 let reservedLowerSymbols = ["flow", "flows", "fixes"]
637 let lowerTicker = tick.toLower()
638
639 // Avoid using the reserved symbol
640 assert(
641 !reservedLowerSymbols.contains(lowerTicker),
642 message: "The token symbol is reserved"
643 )
644
645 // ticker should be 2~7 characters
646 assert(
647 tick.length >= 2 && tick.length <= 7,
648 message: "The token symbol should be 2~7 characters"
649 )
650
651 /// Check if the account is already enabled
652 assert(
653 acctsPool.getFTContractAddress(tick) == nil,
654 message: "The Fungible Token account is already created"
655 )
656
657 // execute the inscription
658 acctsPool.executeInscription(type: FRC20AccountsPool.ChildAccountType.FungibleToken, ins)
659
660 // Get the caller address
661 let callerAddr = ins.owner!.address
662 let newAddr = newAccount.address
663
664 // create the account for the fungible token at the accounts pool
665 acctsPool.setupNewChildForFungibleToken(tick: tick, newAccount)
666
667 // update the resources in the account
668 self._ensureFungibleTokenAccountResourcesAvailable(tick, caller: callerAddr)
669 // deploy the contract of Fixes Fungible Token to the account
670 self._updateFungibleTokenContractInAccount(tick, contractName: "FixesFungibleToken")
671
672 // emit the event
673 emit FungibleTokenAccountCreated(
674 symbol: tick,
675 account: newAddr,
676 by: callerAddr
677 )
678 }
679
680 /// Setup Tradable Pool Resources
681 ///
682 access(all)
683 fun setupTradablePoolResources(_ ins: auth(Fixes.Extractable) &Fixes.Inscription) {
684 post {
685 ins.isValueEmpty(): "The inscription is not empty"
686 }
687 // singletoken resources
688 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
689
690 // inscription data
691 let meta = FixesInscriptionFactory.parseMetadata(ins.borrowData())
692 let tick = meta["tick"] ?? panic("The token symbol is not found")
693
694 let tokenAdminRef = self.borrowWritableTokenAdmin(tick: tick)
695 // check if the caller is authorized
696 let callerAddr = ins.owner?.address ?? panic("The owner of the inscription is not found")
697 assert(
698 tokenAdminRef.isAuthorizedUser(callerAddr),
699 message: "You are not authorized to setup the tradable pool resources"
700 )
701
702 // try to borrow the account to check if it was created
703 let childAcctRef = acctsPool.borrowChildAccount(type: FRC20AccountsPool.ChildAccountType.FungibleToken, tick)
704 ?? panic("The child account was not created")
705
706 // Get the caller address
707 let ftContractAddr = childAcctRef.address
708
709 // create a new minter from the account
710 let minter <- self._initializeMinter(
711 ins,
712 usage: "setup-tradable-pool",
713 extrafields: ["supply", "feePerc", "freeAmount"]
714 )
715
716 // - Add Tradable Pool Resource
717 // - Add Heartbeat Hook
718 // - Relink Flow Token Resource
719
720 // add tradable pool resource
721 let poolStoragePath = FixesTradablePool.getLiquidityPoolStoragePath()
722 assert(
723 childAcctRef.storage.borrow<&AnyResource>(from: poolStoragePath) == nil,
724 message: "The tradable pool is already created"
725 )
726
727 // create the tradable pool
728 let tradablePool <- FixesTradablePool.createTradableLiquidityPool(
729 ins: ins,
730 <- minter
731 )
732 childAcctRef.storage.save(<- tradablePool, to: poolStoragePath)
733
734 // link the tradable pool to the public path
735 let poolPublicPath = FixesTradablePool.getLiquidityPoolPublicPath()
736 childAcctRef.capabilities.publish(
737 childAcctRef.capabilities.storage.issue<&FixesTradablePool.TradableLiquidityPool>(poolStoragePath),
738 at: poolPublicPath
739 )
740
741 let tradablePoolRef = childAcctRef.storage
742 .borrow<auth(FixesTradablePool.Manage) &FixesTradablePool.TradableLiquidityPool>(from: poolStoragePath)
743 ?? panic("The tradable pool was not created")
744 // Initialize the tradable pool
745 tradablePoolRef.initialize()
746
747 // Check if the tradable pool is active
748 assert(
749 tradablePoolRef.isLocalActive(),
750 message: "The tradable pool is not active"
751 )
752
753 // -- Add the heartbeat hook to the tradable pool
754
755 // Register to FixesHeartbeat
756 let heartbeatScope = "TradablePool"
757 if !FixesHeartbeat.hasHook(scope: heartbeatScope, hookAddr: ftContractAddr) {
758 FixesHeartbeat.addHook(
759 scope: heartbeatScope,
760 hookAddr: ftContractAddr,
761 hookPath: poolPublicPath
762 )
763 }
764
765 // Reset Flow Receiver
766 // This is the standard receiver path of FlowToken
767 let flowReceiverPath = /public/flowTokenReceiver
768
769 // Unlink the existing receiver capability for flowReceiverPath
770 if childAcctRef.capabilities.get<&{FungibleToken.Receiver}>(flowReceiverPath).check() {
771 // link the forwarder to the public path
772 childAcctRef.capabilities.unpublish(flowReceiverPath)
773 // Link the new forwarding receiver capability
774 childAcctRef.capabilities.publish(
775 childAcctRef.capabilities.storage.issue<&{FungibleToken.Receiver}>(poolStoragePath),
776 at: flowReceiverPath
777 )
778
779 // link the FlowToken to the forwarder fallback path
780 let fallbackPath = Fixes.getFallbackFlowTokenPublicPath()
781 childAcctRef.capabilities.unpublish(fallbackPath)
782 childAcctRef.capabilities.publish(
783 childAcctRef.capabilities.storage.issue<&FlowToken.Vault>(/storage/flowTokenVault),
784 at: fallbackPath
785 )
786 }
787
788 // emit the event
789 emit FungibleTokenAccountResourcesUpdated(symbol: tick, account: ftContractAddr)
790 }
791
792 /// Setup the lottery pool for some coin
793 ///
794 access(all)
795 fun setupLotteryPool(
796 _ ins: auth(Fixes.Extractable) &Fixes.Inscription,
797 epochDays: UInt8,
798 ) {
799 pre {
800 epochDays > 0 && epochDays <= 7: "The interval days should be 1~15"
801 }
802 post {
803 ins.isValueEmpty(): "The inscription is not empty"
804 }
805
806 // singletoken resources
807 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
808
809 // inscription data
810 let meta = self.verifyExecutingInscription(ins, usage: "setup-lottery")
811 let tick = meta["tick"] ?? panic("The token symbol is not found")
812
813 let tokenAdminRef = self.borrowWritableTokenAdmin(tick: tick)
814 // check if the caller is authorized
815 let callerAddr = ins.owner?.address ?? panic("The owner of the inscription is not found")
816 assert(
817 tokenAdminRef.isAuthorizedUser(callerAddr),
818 message: "You are not authorized to setup the tradable pool resources"
819 )
820
821 // borrow the super minter
822 let superMinter = tokenAdminRef.borrowSuperMinter()
823 assert(
824 tick == "$".concat(superMinter.getSymbol()),
825 message: "The token symbol is not valid"
826 )
827
828 // try to borrow the account to check if it was created
829 let childAcctRef = acctsPool.borrowChildAccount(type: FRC20AccountsPool.ChildAccountType.FungibleToken, tick)
830 ?? panic("The child account was not created")
831
832 // Get the caller address
833 let ftContractAddr = childAcctRef.address
834
835 let tokenType = superMinter.getTokenType()
836 let maxSupply = superMinter.getMaxSupply()
837 // ticket price is MaxSupply/500_000
838 let ticketPice = maxSupply / 500_000.0
839 let epochInterval = UFix64(UInt64(epochDays) * 24 * 60 * 60)
840
841 FGameLotteryRegistry.createLotteryPool(
842 operatorAddr: callerAddr,
843 childAcctRef: childAcctRef,
844 name: "FIXES_LOTTERY_POOL_FOR_".concat(tick),
845 rewardTick: FRC20FTShared.buildTicker(tokenType) ?? panic("The token type is not valid"),
846 ticketPrice: ticketPice,
847 epochInterval: epochInterval,
848 )
849
850 // execute the inscription
851 acctsPool.executeInscription(type: FRC20AccountsPool.ChildAccountType.FungibleToken, ins)
852
853 // emit the event
854 emit FungibleTokenAccountResourcesUpdated(symbol: tick, account: ftContractAddr)
855 }
856
857 access(all)
858 fun setupLockDropsPool(_ ins: auth(Fixes.Extractable) &Fixes.Inscription, lockingExchangeRates: {UFix64: UFix64}) {
859 post {
860 ins.isValueEmpty(): "The inscription is not empty"
861 }
862 // singletoken resources
863 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
864
865 // inscription data
866 let meta = self.verifyExecutingInscription(ins, usage: "setup-lockdrops")
867 let tick = meta["tick"] ?? panic("The token symbol is not found")
868
869 let tokenAdminRef = self.borrowWritableTokenAdmin(tick: tick)
870 // check if the caller is authorized
871 let callerAddr = ins.owner?.address ?? panic("The owner of the inscription is not found")
872 assert(
873 tokenAdminRef.isAuthorizedUser(callerAddr),
874 message: "You are not authorized to setup the lockdrops pool resources"
875 )
876
877 // try to borrow the account to check if it was created
878 let childAcctRef = acctsPool.borrowChildAccount(type: FRC20AccountsPool.ChildAccountType.FungibleToken, tick)
879 ?? panic("The child account was not created")
880
881 // Get the caller address
882 let ftContractAddr = childAcctRef.address
883
884 // - Add Lock Drop Resource
885
886 let lockdropsStoragePath = FixesTokenLockDrops.getDropsPoolStoragePath()
887 assert(
888 childAcctRef.storage.borrow<&AnyResource>(from: lockdropsStoragePath) == nil,
889 message: "The lockdrops pool is already created"
890 )
891
892 // create a new minter from the account
893 let minter <- self._initializeMinter(
894 ins,
895 usage: "setup-lockdrops",
896 extrafields: ["supply", "lockingTick"]
897 )
898
899 var activateTime: UFix64? = nil
900 if let activateAt = meta["activateAt"] {
901 activateTime = UFix64.fromString(activateAt)
902 }
903 var failureDeprecatedTime: UFix64? = nil
904 if let deprecatedAt = meta["deprecatedAt"] {
905 failureDeprecatedTime = UFix64.fromString(deprecatedAt)
906 }
907
908 // create the lockdrops pool
909 let lockdrops <- FixesTokenLockDrops.createDropsPool(
910 ins,
911 <- minter,
912 lockingExchangeRates,
913 activateTime,
914 failureDeprecatedTime
915 )
916 childAcctRef.storage.save(<- lockdrops, to: lockdropsStoragePath)
917
918 // link the lockdrops pool to the public path
919 let lockdropsPublicPath = FixesTokenLockDrops.getDropsPoolPublicPath()
920 childAcctRef.capabilities.publish(
921 childAcctRef.capabilities.storage.issue<&FixesTokenLockDrops.DropsPool>(lockdropsStoragePath),
922 at: lockdropsPublicPath
923 )
924
925 // emit the event
926 emit FungibleTokenAccountResourcesUpdated(symbol: tick, account: ftContractAddr)
927 }
928
929 /// Enable the Airdrop pool for the Fungible Token
930 ///
931 access(all)
932 fun setupAirdropsPool(_ ins: auth(Fixes.Extractable) &Fixes.Inscription) {
933 post {
934 ins.isValueEmpty(): "The inscription is not empty"
935 }
936 // singletoken resources
937 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
938
939 // inscription data
940 let meta = self.verifyExecutingInscription(ins, usage: "setup-airdrop")
941 let tick = meta["tick"] ?? panic("The token symbol is not found")
942
943 let tokenAdminRef = self.borrowWritableTokenAdmin(tick: tick)
944 // check if the caller is authorized
945 let callerAddr = ins.owner?.address ?? panic("The owner of the inscription is not found")
946 assert(
947 tokenAdminRef.isAuthorizedUser(callerAddr),
948 message: "You are not authorized to setup the airdrops pool resources"
949 )
950
951 // try to borrow the account to check if it was created
952 let childAcctRef = acctsPool.borrowChildAccount(type: FRC20AccountsPool.ChildAccountType.FungibleToken, tick)
953 ?? panic("The child account was not created")
954
955 // Get the caller address
956 let ftContractAddr = childAcctRef.address
957
958 // - Add Airdrop Resource
959
960 let storagePath = FixesTokenAirDrops.getAirdropPoolStoragePath()
961 assert(
962 childAcctRef.storage.borrow<&AnyResource>(from: storagePath) == nil,
963 message: "The airdrop pool is already created"
964 )
965
966 // create a new minter from the account
967 let minter <- self._initializeMinter(
968 ins,
969 usage: "setup-airdrop",
970 extrafields: ["supply"]
971 )
972
973 // create the airdrops pool
974 let airdrops <- FixesTokenAirDrops.createDropsPool(ins, <- minter)
975 childAcctRef.storage.save(<- airdrops, to: storagePath)
976
977 // link the airdrops pool to the public path
978 let publicPath = FixesTokenAirDrops.getAirdropPoolPublicPath()
979 childAcctRef.capabilities.publish(
980 childAcctRef.capabilities.storage.issue<&FixesTokenAirDrops.AirdropPool>(storagePath),
981 at: publicPath
982 )
983
984 // emit the event
985 emit FungibleTokenAccountResourcesUpdated(symbol: tick, account: ftContractAddr)
986 }
987
988 access(self)
989 fun _initializeMinter(
990 _ ins: auth(Fixes.Extractable) &Fixes.Inscription,
991 usage: String,
992 extrafields: [String]
993 ): @{FixesFungibleTokenInterface.IMinter} {
994 // singletoken resources
995 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
996
997 // inscription data
998 let meta = self.verifyExecutingInscription(ins, usage: usage)
999 let tick = meta["tick"] ?? panic("The token symbol is not found")
1000
1001 // try to borrow the account to check if it was created
1002 let childAcctRef = acctsPool.borrowChildAccount(type: FRC20AccountsPool.ChildAccountType.FungibleToken, tick)
1003 ?? panic("The child account was not created")
1004
1005 // Get the caller address
1006 let ftContractAddr = childAcctRef.address
1007 let callerAddr = ins.owner!.address
1008
1009 // get the token admin reference
1010 let tokenAdminRef = self.borrowWritableTokenAdmin(tick: tick)
1011 // check if the caller is authorized
1012 assert(
1013 tokenAdminRef.isAuthorizedUser(callerAddr),
1014 message: "You are not authorized to setup the tradable pool resources"
1015 )
1016
1017 // borrow the super minter
1018 let superMinter = tokenAdminRef.borrowSuperMinter()
1019 assert(
1020 tick == "$".concat(superMinter.getSymbol()),
1021 message: "The token symbol is not valid"
1022 )
1023
1024 // calculate the new minter supply
1025 let maxSupply = superMinter.getMaxSupply()
1026 let grantedSupply = tokenAdminRef.getGrantedMintableAmount()
1027
1028 // check if the caller is advanced
1029 let isAdvancedCaller = FixesTradablePool.isAdvancedTokenPlayer(callerAddr)
1030
1031 // new minter supply
1032 let maxSupplyForNewMinter = maxSupply.saturatingSubtract(grantedSupply)
1033 var newGrantedAmount = maxSupplyForNewMinter
1034 if let supplyStr = meta["supply"] {
1035 assert(
1036 isAdvancedCaller,
1037 message: "You are not eligible to setup custimzed supply amount for the tradable pool"
1038 )
1039 newGrantedAmount = UFix64.fromString(supplyStr)
1040 ?? panic("The supply amount is not valid")
1041 }
1042 assert(
1043 newGrantedAmount <= maxSupplyForNewMinter && newGrantedAmount > 0.0,
1044 message: "The supply amount of the minter is more than the all unused supply or less than 0.0"
1045 )
1046 var isExtraFieldsExist = false
1047 for fields in extrafields {
1048 if let value = meta[fields] {
1049 isExtraFieldsExist = true
1050 break
1051 }
1052 }
1053 /// Check if the caller is eligible to configure the minter with extra fields
1054 if isExtraFieldsExist {
1055 assert(
1056 isAdvancedCaller,
1057 message: "You are not eligible to configure the minter with extra fields"
1058 )
1059 }
1060 // create a new minter from the account
1061 return <- tokenAdminRef.createMinter(allowedAmount: newGrantedAmount)
1062 }
1063
1064 /// Enable the FRC20 Fungible Token
1065 ///
1066 access(all)
1067 fun initializeFRC20FungibleTokenAccount(
1068 _ ins: auth(Fixes.Extractable) &Fixes.Inscription,
1069 newAccount: Capability<auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account>
1070 ) {
1071 post {
1072 ins.isValueEmpty(): "The inscription is not empty"
1073 }
1074 // singletoken resources
1075 let frc20Indexer = FRC20Indexer.getIndexer()
1076 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
1077
1078 // inscription data
1079 let meta = FixesInscriptionFactory.parseMetadata(ins.borrowData())
1080 assert(
1081 meta["op"] == "exec" && meta["usage"] == "init-ft",
1082 message: "The inscription is not for initialize a Fungible Token account"
1083 )
1084
1085 let tickerName = meta["tick"]?.toLower() ?? panic("The token tick is not found")
1086
1087 /// Check if the account is already enabled
1088 assert(
1089 acctsPool.getFTContractAddress(tickerName) == nil,
1090 message: "The Fungible Token account is already created"
1091 )
1092
1093 // Get the caller address
1094 let callerAddr = ins.owner!.address
1095 let newAddr = newAccount.address
1096
1097 // Check if the the caller is valid
1098 let tokenMeta = frc20Indexer.getTokenMeta(tick: tickerName) ?? panic("The token is not registered")
1099 assert(
1100 tokenMeta.deployer == callerAddr,
1101 message: "You are not allowed to create the Fungible Token account"
1102 )
1103
1104 // execute the inscription to ensure you are the deployer of the token
1105 let ret = frc20Indexer.executeByDeployer(ins: ins)
1106 assert(
1107 ret == true,
1108 message: "The inscription execution failed"
1109 )
1110
1111 // create the account for the fungible token at the accounts pool
1112 acctsPool.setupNewChildForFungibleToken(
1113 tick: tokenMeta.tick,
1114 newAccount
1115 )
1116
1117 // update the resources in the account
1118 self._ensureFungibleTokenAccountResourcesAvailable(tickerName, caller: callerAddr)
1119
1120 // try to borrow the account to check if it was created
1121 let childAcctRef = acctsPool.borrowChildAccount(type: FRC20AccountsPool.ChildAccountType.FungibleToken, tickerName)
1122 ?? panic("The staking account was not created")
1123
1124 // Create the FRC20Agents.IndexerController and save it in the account
1125 // This is required for FRC20FungibleToken
1126 let ctrlStoragePath = FRC20Agents.getIndexerControllerStoragePath()
1127 if childAcctRef.storage.borrow<&AnyResource>(from: ctrlStoragePath) == nil {
1128 let indexerController <- FRC20Agents.createIndexerController([tickerName])
1129 childAcctRef.storage.save(<- indexerController, to: ctrlStoragePath)
1130 }
1131
1132 // deploy the contract of FRC20 Fungible Token to the account
1133 self._updateFungibleTokenContractInAccount(tickerName, contractName: "FRC20FungibleToken")
1134
1135 // emit the event
1136 emit FungibleTokenAccountCreated(
1137 symbol: tickerName,
1138 account: newAddr,
1139 by: callerAddr
1140 )
1141 }
1142
1143 /// Setup Tradable Pool Resources
1144 ///
1145 access(all)
1146 fun setupFRC20ConverterResources(_ ins: auth(Fixes.Extractable) &Fixes.Inscription) {
1147 post {
1148 ins.isValueEmpty(): "The inscription is not empty"
1149 }
1150 // singletoken resources
1151 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
1152 let frc20Indexer = FRC20Indexer.getIndexer()
1153
1154 // inscription data
1155 let meta = FixesInscriptionFactory.parseMetadata(ins.borrowData())
1156 let tickerName = meta["tick"]?.toLower() ?? panic("The token tick is not found")
1157
1158 let callerAddr = ins.owner!.address
1159
1160 // Check if the the caller is valid
1161 let tokenMeta = frc20Indexer.getTokenMeta(tick: tickerName) ?? panic("The token is not registered")
1162 assert(
1163 tokenMeta.deployer == callerAddr,
1164 message: "You are not allowed to create the Fungible Token account"
1165 )
1166
1167 // execute the inscription to ensure you are the deployer of the token
1168 let ret = frc20Indexer.executeByDeployer(ins: ins)
1169 assert(
1170 ret == true,
1171 message: "The inscription execution failed"
1172 )
1173
1174 // try to borrow the account to check if it was created
1175 let childAcctRef = acctsPool.borrowChildAccount(type: FRC20AccountsPool.ChildAccountType.FungibleToken, tickerName)
1176 ?? panic("The child account was not created")
1177
1178 // Get the caller address
1179 let ftContractAddr = childAcctRef.address
1180
1181 // --- Create the FRC20 Converter ---
1182
1183 // Check if the admin resource is available
1184 let contractRef = acctsPool.borrowFTContract(tickerName)
1185 ?? panic("The Fungible Token account was not created")
1186 let adminStoragePath = contractRef.getAdminStoragePath()
1187
1188 let adminCap = childAcctRef.capabilities.storage
1189 .issue<auth(FixesFungibleTokenInterface.Manage) &{FixesFungibleTokenInterface.IAdminWritable}>(adminStoragePath)
1190 assert(
1191 adminCap.check(),
1192 message: "The admin resource is not available"
1193 )
1194 let converterStoragePath = FRC20Converter.getFTConverterStoragePath()
1195 childAcctRef.storage.save(<- FRC20Converter.createConverter(adminCap), to: converterStoragePath)
1196 // link the converter to the public path
1197 childAcctRef.capabilities.publish(
1198 childAcctRef.capabilities.storage.issue<&FRC20Converter.FTConverter>(converterStoragePath),
1199 at: FRC20Converter.getFTConverterPublicPath()
1200 )
1201
1202 // emit the event
1203 emit FungibleTokenAccountResourcesUpdated(symbol: tickerName, account: ftContractAddr)
1204 }
1205
1206 /** ---- Internal Methods ---- */
1207
1208 /// Verify the inscription for executing the Fungible Token
1209 ///
1210 access(self)
1211 fun verifyExecutingInscription(
1212 _ ins: auth(Fixes.Extractable) &Fixes.Inscription,
1213 usage: String
1214 ): {String: String} {
1215 // inscription data
1216 let meta = FixesInscriptionFactory.parseMetadata(ins.borrowData())
1217 assert(
1218 meta["op"] == "exec",
1219 message: "The inscription operation must be 'exec'"
1220 )
1221 let tick = meta["tick"] ?? panic("The token symbol is not found")
1222 assert(
1223 tick[0] == "$",
1224 message: "The token symbol must start with '$'"
1225 )
1226 let usageInMeta = meta["usage"] ?? panic("The token operation is not found")
1227 assert(
1228 usageInMeta == usage || usage == "*",
1229 message: "The inscription is not for initialize a Fungible Token account"
1230 )
1231 return meta
1232 }
1233
1234 /// Borrow the Fixes Fungible Token Admin Resource
1235 ///
1236 access(self)
1237 view fun borrowWritableTokenAdmin(tick: String): auth(FixesFungibleTokenInterface.Manage) &{FixesFungibleTokenInterface.IAdminWritable} {
1238 // try to borrow the account to check if it was created
1239 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
1240 let childAcctRef = acctsPool.borrowChildAccount(type: FRC20AccountsPool.ChildAccountType.FungibleToken, tick)
1241 ?? panic("The Fungible token account was not created")
1242 let contractRef = acctsPool.borrowFTContract(tick)
1243 ?? panic("The Fungible Token contract is not deployed")
1244 // Check if the admin resource is available
1245 let adminStoragePath = contractRef.getAdminStoragePath()
1246 return childAcctRef.storage
1247 .borrow<auth(FixesFungibleTokenInterface.Manage) &{FixesFungibleTokenInterface.IAdminWritable}>(from: adminStoragePath)
1248 ?? panic("The admin resource is not available")
1249 }
1250
1251 /// Ensure all resources are available
1252 ///
1253 access(self)
1254 fun _ensureFungibleTokenAccountResourcesAvailable(_ tick: String, caller: Address) {
1255 // singleton resources
1256 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
1257
1258 // try to borrow the account to check if it was created
1259 let childAcctRef = acctsPool.borrowChildAccount(type: FRC20AccountsPool.ChildAccountType.FungibleToken, tick)
1260 ?? panic("The staking account was not created")
1261 let childAddr = childAcctRef.address
1262
1263 var isUpdated = false
1264 // The fungible token should have the following resources in the account:
1265 // - FRC20FTShared.SharedStore: configuration
1266 // - Store the Symbol, Name for the token
1267 // - Store the Deployer of the token
1268 // - Store the Deploying Time of the token
1269
1270 // create the shared store and save it in the account
1271 if childAcctRef.storage.borrow<&AnyResource>(from: FRC20FTShared.SharedStoreStoragePath) == nil {
1272 let sharedStore <- FRC20FTShared.createSharedStore()
1273 childAcctRef.storage.save(<- sharedStore, to: FRC20FTShared.SharedStoreStoragePath)
1274
1275 // link the shared store to the public path
1276 // childAcctRef.capabilities.unpublish(FRC20FTShared.SharedStorePublicPath)
1277 childAcctRef.capabilities.publish(
1278 childAcctRef.capabilities.storage.issue<&FRC20FTShared.SharedStore>(FRC20FTShared.SharedStoreStoragePath),
1279 at: FRC20FTShared.SharedStorePublicPath
1280 )
1281
1282 // Add token symbol to the managed list
1283 let managerRef = self.borrowFTManager(caller) ?? panic("The manager resource is not found")
1284 // create a private cap for shared store for the fungible token
1285 let cap = childAcctRef.capabilities
1286 .storage.issue<auth(FRC20FTShared.Write) &FRC20FTShared.SharedStore>(FRC20FTShared.SharedStoreStoragePath)
1287 assert(cap.check(), message: "The shared store is not valid")
1288 managerRef.addCreatedFungibleToken(tick, cap)
1289
1290 isUpdated = true || isUpdated
1291 }
1292
1293 // borrow the shared store
1294 if let store = childAcctRef.storage
1295 .borrow<auth(FRC20FTShared.Write) &FRC20FTShared.SharedStore>(from: FRC20FTShared.SharedStoreStoragePath) {
1296 // ensure the symbol is without the '$' sign
1297 var symbol = tick
1298 if symbol[0] == "$" {
1299 symbol = symbol.slice(from: 1, upTo: symbol.length)
1300 }
1301 // set the configuration
1302 store.setByEnum(FRC20FTShared.ConfigType.FungibleTokenDeployer, value: caller)
1303 store.setByEnum(FRC20FTShared.ConfigType.FungibleTokenSymbol, value: symbol)
1304 store.setByEnum(FRC20FTShared.ConfigType.FungibleTokenDeployedAt, value: getCurrentBlock().timestamp)
1305
1306 isUpdated = true || isUpdated
1307 }
1308
1309 isUpdated = self._ensureTradingRecordResourcesAvailable(childAcctRef, tick: tick) || isUpdated
1310
1311 if isUpdated {
1312 emit FungibleTokenAccountResourcesUpdated(symbol: tick, account: childAddr)
1313 }
1314 }
1315
1316 /// Utility method to ensure the trading record resources are available
1317 ///
1318 access(self)
1319 fun _ensureTradingRecordResourcesAvailable(_ acctRef: auth(Storage, Capabilities) &Account, tick: String?): Bool {
1320 var isUpdated = false
1321
1322 // - FRC20FTShared.Hooks
1323 // - TradingRecord
1324
1325 // create the hooks and save it in the account
1326 if acctRef.storage.borrow<&AnyResource>(from: FRC20FTShared.TransactionHookStoragePath) == nil {
1327 let hooks <- FRC20FTShared.createHooks()
1328 acctRef.storage.save(<- hooks, to: FRC20FTShared.TransactionHookStoragePath)
1329
1330 isUpdated = true || isUpdated
1331 }
1332
1333 // link the hooks to the public path
1334 if acctRef
1335 .capabilities.get<&FRC20FTShared.Hooks>(FRC20FTShared.TransactionHookPublicPath)
1336 .borrow() == nil {
1337 // link the hooks to the public path
1338 acctRef.capabilities.unpublish(FRC20FTShared.TransactionHookPublicPath)
1339 acctRef.capabilities.publish(
1340 acctRef.capabilities.storage.issue<&FRC20FTShared.Hooks>(FRC20FTShared.TransactionHookStoragePath),
1341 at: FRC20FTShared.TransactionHookPublicPath
1342 )
1343
1344 isUpdated = true || isUpdated
1345 }
1346
1347 // ensure trading records are available
1348 if acctRef.storage.borrow<&AnyResource>(from: FRC20TradingRecord.TradingRecordsStoragePath) == nil {
1349 let tradingRecords <- FRC20TradingRecord.createTradingRecords(tick)
1350 acctRef.storage.save(<- tradingRecords, to: FRC20TradingRecord.TradingRecordsStoragePath)
1351
1352 // link the trading records to the public path
1353 acctRef.capabilities.unpublish(FRC20TradingRecord.TradingRecordsPublicPath)
1354 acctRef.capabilities.publish(
1355 acctRef.capabilities.storage.issue<&FRC20TradingRecord.TradingRecords>(FRC20TradingRecord.TradingRecordsStoragePath),
1356 at: FRC20TradingRecord.TradingRecordsPublicPath
1357 )
1358
1359 isUpdated = true || isUpdated
1360 }
1361
1362 // borrow the hooks reference
1363 let hooksRef = acctRef.storage
1364 .borrow<auth(FRC20FTShared.Manage) &FRC20FTShared.Hooks>(from: FRC20FTShared.TransactionHookStoragePath)
1365 ?? panic("The hooks were not created")
1366
1367 // add the trading records to the hooks, if it is not added yet
1368 // get the public capability of the trading record hook
1369 let tradingRecordsCap = acctRef
1370 .capabilities.get<&FRC20TradingRecord.TradingRecords>(
1371 FRC20TradingRecord.TradingRecordsPublicPath
1372 )
1373 assert(tradingRecordsCap.check(), message: "The trading record hook is not valid")
1374 // get the reference of the trading record hook
1375 let recordsRef = tradingRecordsCap.borrow()
1376 ?? panic("The trading record hook is not valid")
1377 if !hooksRef.hasHook(recordsRef.getType()) {
1378 hooksRef.addHook(tradingRecordsCap)
1379 }
1380
1381 return isUpdated
1382 }
1383
1384 /// Update the FRC20 Fungible Token contract in the account
1385 ///
1386 access(self)
1387 fun _updateFungibleTokenContractInAccount(_ tick: String, contractName: String) {
1388 // singleton resources
1389 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
1390
1391 // try to borrow the account to check if it was created
1392 let childAcctRef = acctsPool.borrowChildAccount(type: FRC20AccountsPool.ChildAccountType.FungibleToken, tick)
1393 ?? panic("The staking account was not created")
1394 let childAddr = childAcctRef.address
1395
1396 // Load contract from the account
1397 if let ftContract = self.account.contracts.get(name: contractName) {
1398 // try to deploy the contract of FRC20 Fungible Token to the child account
1399 let deployedContracts = childAcctRef.contracts.names
1400 if deployedContracts.contains(contractName) {
1401 log("Updating the contract in the account: ".concat(childAddr.toString()))
1402 // update the contract
1403 childAcctRef.contracts.update(name: contractName, code: ftContract.code)
1404 } else {
1405 log("Deploying the contract to the account: ".concat(childAddr.toString()))
1406 // add the contract
1407 childAcctRef.contracts.add(name: contractName, code: ftContract.code)
1408 }
1409 } else {
1410 panic("The contract of Fungible Token is not deployed")
1411 }
1412
1413 // emit the event
1414 emit FungibleTokenContractUpdated(symbol: tick, account: childAddr, contractName: contractName)
1415 }
1416
1417 init() {
1418 let identifier = "FungibleTokenManager_".concat(self.account.address.toString())
1419 self.AdminStoragePath = StoragePath(identifier: identifier.concat("_admin"))!
1420 self.AdminPublicPath = PublicPath(identifier: identifier.concat("_admin"))!
1421
1422 // create the admin account
1423 let admin <- create Admin()
1424 self.account.storage.save(<-admin, to: self.AdminStoragePath)
1425 self.account.capabilities.publish(
1426 self.account.capabilities.storage.issue<&Admin>(self.AdminStoragePath),
1427 at: self.AdminPublicPath
1428 )
1429
1430 // Setup FungibleToken Shared FRC20TradingRecord
1431 self._ensureTradingRecordResourcesAvailable(self.account, tick: nil)
1432
1433 emit ContractInitialized()
1434 }
1435}
1436