Smart Contract

FungibleTokenManager

A.d2abb5dbf5e08666.FungibleTokenManager

Valid From

86,129,271

Deployed

3d ago
Feb 24, 2026, 11:54:13 PM UTC

Dependents

9 imports
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