Smart Contract

FixesFungibleToken

A.2fc0d080618ee419.FixesFungibleToken

Valid From

86,129,451

Deployed

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

Dependents

0 imports
1/**
2
3> Author: Fixes Lab <https://github.com/fixes-world/>
4
5# FixesFungibleToken
6
7This is the fungible token contract for a Mintable Fungible tokens with FixesAssetMeta.
8It is the template contract that is used to deploy.
9
10*/
11
12import FungibleToken from 0xf233dcee88fe0abe
13import ViewResolver from 0x1d7e57aa55817448
14import MetadataViews from 0x1d7e57aa55817448
15import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
16import BlackHole from 0x4396883a58c3a2d1
17import TokenList from 0x15a918087ab12d86
18import Burner from 0xf233dcee88fe0abe
19// Fixes imports
20import Fixes from 0xd2abb5dbf5e08666
21import FixesInscriptionFactory from 0xd2abb5dbf5e08666
22import FixesFungibleTokenInterface from 0xd2abb5dbf5e08666
23import FixesTraits from 0xd2abb5dbf5e08666
24import FixesAssetMeta from 0xd2abb5dbf5e08666
25import FRC20FTShared from 0xd2abb5dbf5e08666
26import FRC20AccountsPool from 0xd2abb5dbf5e08666
27
28/// This is the template source for a Fixes Fungible Token
29/// The contract is deployed in the child account of the FRC20AccountsPool
30/// The Token is issued by Minter
31access(all) contract FixesFungibleToken: FixesFungibleTokenInterface, FungibleToken, ViewResolver {
32    // ------ Events -------
33
34    /// The event that is emitted when the contract is created
35    access(all) event TokensInitialized(initialSupply: UFix64)
36
37    /// The event that is emitted when new tokens are minted
38    access(all) event TokensMinted(amount: UFix64)
39
40    /// The event that is emitted when tokens are destroyed
41    access(all) event TokensBurned(amount: UFix64)
42
43    /// -------- Parameters --------
44
45    /// Total supply of FixesFungibleToken in existence
46    /// This value is only a record of the quantity existing in the form of Flow Fungible Tokens.
47    /// It does not represent the total quantity of the token that has been minted.
48    /// The total quantity of the token that has been minted is loaded from the FRC20Indexer.
49    access(all)
50    var totalSupply: UFix64
51
52    /// -------- Resources and Interfaces --------
53
54    /// Each user stores an instance of only the Vault in their storage
55    /// The functions in the Vault and governed by the pre and post conditions
56    /// in FungibleToken when they are called.
57    /// The checks happen at runtime whenever a function is called.
58    ///
59    /// Resources can only be created in the context of the contract that they
60    /// are defined in, so there is no way for a malicious user to create Vaults
61    /// out of thin air. A special Minter resource needs to be defined to mint
62    /// new tokens.
63    ///
64    access(all) resource Vault: FixesFungibleTokenInterface.Vault, FungibleToken.Vault {
65        /// The total balance of this vault
66        access(all)
67        var balance: UFix64
68        /// Metadata: Type of MergeableData => MergeableData
69        access(contract)
70        let metadata: {Type: {FixesTraits.MergeableData}}
71
72        /// Initialize the balance at resource creation time
73        init(balance: UFix64) {
74            self.balance = balance
75            self.metadata = {}
76
77            // init with ExclusiveMeta, this meta will be removed if the initialize method is called
78            let initmeta = FixesAssetMeta.ExclusiveMeta()
79            self.metadata[initmeta.getType()] = initmeta
80        }
81
82        /// Called when a fungible token is burned via the `Burner.burn()` method
83        ///
84        access(contract) fun burnCallback() {
85            if self.balance > 0.0 {
86                // update the total supply for the FungibleToken
87                FixesFungibleToken.totalSupply = FixesFungibleToken.totalSupply - self.balance
88            }
89            self.balance = 0.0
90        }
91
92        /// createEmptyVault
93        ///
94        /// Function that creates a new Vault with a balance of zero
95        /// and returns it to the calling context. A user must call this function
96        /// and store the returned Vault in their storage in order to allow their
97        /// account to be able to receive deposits of this token type.
98        ///
99        access(all) fun createEmptyVault(): @Vault {
100            return <- FixesFungibleToken.createEmptyVault(vaultType: Type<@Vault>())
101        }
102
103        /// ----- Internal Methods -----
104
105        /// The initialize method for the Vault
106        ///
107        access(contract)
108        fun initialize(
109            _ ins: &Fixes.Inscription?,
110            _ owner: Address?,
111        ) {
112            pre {
113                ins != nil || owner != nil: "The inscription or owner must be provided"
114            }
115            let fromAddr = (ins != nil ? ins!.owner?.address : owner)
116                ?? panic("Failed to get the owner address")
117
118            // remove the ExclusiveMeta
119            let exclusiveMetaType = Type<FixesAssetMeta.ExclusiveMeta>()
120            if self.metadata[exclusiveMetaType] != nil {
121                self.metadata.remove(key: exclusiveMetaType)
122            }
123
124            /// init DNA to the metadata
125            self.initializeMetadata(FixesAssetMeta.DNA(
126                self.getDNAIdentifier(),
127                fromAddr,
128                // if inscription exists, init the DNA with the mutatable attempts
129                ins != nil ? self.getMaxGenerateGeneAttempts() : 0
130            ))
131        }
132
133        /// Set the metadata by key
134        ///
135        access(contract)
136        fun initializeMetadata(_ data: {FixesTraits.MergeableData}) {
137            let key = data.getType()
138            self.metadata[key] = data
139        }
140
141        /// --------- Implement Metadata --------- ///
142
143        /// Get the symbol of the token
144        access(all)
145        view fun getSymbol(): String {
146            return FixesFungibleToken.getSymbol()
147        }
148
149        /// DNA charging
150        /// One inscription can activate DNA mutatable attempts.
151        ///
152        access(all)
153        fun chargeDNAMutatableAttempts(_ ins: auth(Fixes.Extractable) &Fixes.Inscription) {
154            let insOwner = ins.owner?.address ?? panic("The owner of the inscription is not found")
155            assert(
156                insOwner == self.owner?.address,
157                message: "The owner of the inscription is not matched"
158            )
159
160            if !self.isValidVault() {
161                // initialize the vault with the DNA metadata
162                self.initialize(ins, nil)
163            } else {
164                // borrow the DNA metadata
165                let dnaRef = self.borrowMergeableDataRef(Type<FixesAssetMeta.DNA>())
166                    ?? panic("The DNA metadata is not found")
167                let oldValue = (dnaRef.getValue("mutatableAmount") as! UInt64?) ?? 0
168                // update the DNA mutatable amount
169                let addAmt = self.getMaxGenerateGeneAttempts()
170                dnaRef.setValue("mutatableAmount", oldValue + addAmt)
171            }
172
173            // execute the inscription
174            FixesFungibleToken.executeInscription(ins: ins, usage: "charge")
175        }
176
177        /// --------- Implement FungibleToken.Provider --------- ///
178
179        /// Asks if the amount can be withdrawn from this vault
180        ///
181        access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
182            return amount <= self.balance
183        }
184
185        /// Function that takes an amount as an argument
186        /// and withdraws that amount from the Vault.
187        /// It creates a new temporary Vault that is used to hold
188        /// the money that is being transferred. It returns the newly
189        /// created Vault to the context that called so it can be deposited
190        /// elsewhere.
191        ///
192        /// @param amount: The amount of tokens to be withdrawn from the vault
193        /// @return The Vault resource containing the withdrawn funds
194        ///
195        access(FungibleToken.Withdraw)
196        fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
197            pre {
198                self.isAvailableToWithdraw(amount: amount): "The amount withdrawn must be less than or equal to the balance"
199            }
200            let oldBalance = self.balance
201            // update the balance
202            self.balance = self.balance - amount
203
204            // initialize the new vault with the amount
205            let newVault <- create Vault(balance: amount)
206
207            // if current vault is valid vault(with DNA)
208            if self.isValidVault() {
209                newVault.initialize(nil, self.getDNAOwner())
210            }
211
212            // check if the vault has PureVault
213            let isPureVault = self.borrowMergeableDataRef(Type<FixesAssetMeta.ExclusiveMeta>()) != nil
214            // update metadata if the vault is not PureVault
215            if !isPureVault {
216                // setup mergeable data, split from withdraw percentage
217                let percentage = amount / oldBalance
218                let mergeableKeys = self.getMergeableKeys()
219                for key in mergeableKeys {
220                    if let dataRef = self.borrowMergeableDataRef(key) {
221                        let splitData = dataRef.split(percentage)
222                        newVault.metadata[key] = splitData
223
224                        // emit the event
225                        FixesFungibleToken.emitMetadataUpdated(
226                            &self as auth(FixesFungibleTokenInterface.MetadataUpdate) &{FixesFungibleTokenInterface.Vault},
227                            dataRef
228                        )
229                    }
230                }
231
232                // Is the vault has DNA, then attempt to generate a new gene
233                self._attemptGenerateGene(amount, oldBalance)
234            }
235
236            // update the balance ranking
237            let ownerAddr = self.owner?.address
238            if ownerAddr != nil {
239                let adminRef = FixesFungibleToken.borrowAdminPublic()
240                let lastTopHolder = adminRef.getLastTopHolder()
241                if lastTopHolder != ownerAddr || lastTopHolder == nil {
242                    let lastTopBalance = lastTopHolder != nil ? FixesFungibleToken.getTokenBalance(lastTopHolder!) : 0.0
243                    if lastTopBalance == 0.0 || (lastTopBalance > self.balance && adminRef.isInTop100(ownerAddr!)) {
244                        adminRef.onBalanceChanged(ownerAddr!)
245                    }
246                }
247                // if balance is greater than 0, then update the token holder
248                if self.balance > 0.0 && !adminRef.isTokenHolder(ownerAddr!) {
249                    adminRef.onTokenDeposited(ownerAddr!)
250                }
251            }
252
253            return <- newVault
254        }
255
256        /// --------- Implement FungibleToken.Receiver --------- ///
257
258        /// Function that takes a Vault object as an argument and adds
259        /// its balance to the balance of the owners Vault.
260        /// It is allowed to Burner.burn(the sent Vault because the Vault
261        /// was a temporary holder of the tokens. The Vault's balance has
262        /// been consumed and therefore can be destroyed.
263        ///
264        /// @param from: The Vault resource containing the funds that will be deposited
265        ///
266        access(all)
267        fun deposit(from: @{FungibleToken.Vault}) {
268            // the interface ensured that the vault is of the same type
269            // so we can safely cast it
270            let vault <- from as! @FixesFungibleToken.Vault
271
272            // try to get the current owner of the DNA
273            let selfOwner = self.owner?.address
274            let dnaOwner = vault.getDNAOwner()
275            let currentOwner = selfOwner ?? dnaOwner
276
277            // initialize current vault if it is not initialized
278            if !self.isValidVault() && currentOwner != nil {
279                self.initialize(nil, currentOwner!)
280            }
281
282            // check if the vault has PureVault
283            let isPureVault = self.borrowMergeableDataRef(Type<FixesAssetMeta.ExclusiveMeta>()) != nil
284            // update metadata if the vault is not PureVault
285            if !isPureVault {
286                // merge the metadata
287                let keys = vault.getMergeableKeys()
288                for key in keys {
289                    let data = vault.getMergeableData(key)!
290                    if let selfData = self.borrowMergeableDataRef(key) {
291                        selfData.merge(data)
292                    } else {
293                        self.metadata[key] = data
294                    }
295
296                    let dataRef = self.borrowMergeableDataRef(key)!
297                    // emit the event
298                    FixesFungibleToken.emitMetadataUpdated(
299                        &self as auth(FixesFungibleTokenInterface.MetadataUpdate) &{FixesFungibleTokenInterface.Vault},
300                        dataRef
301                    )
302                }
303            }
304
305            // record the deposited balance
306            let depositedBalance = vault.balance
307
308            // update the balance
309            self.balance = self.balance + vault.balance
310            // reset the balance of the from vault
311            vault.balance = 0.0
312            // vault is useless now
313            Burner.burn(<- vault)
314
315            // update metadata if the vault is not PureVault
316            if !isPureVault {
317                // Is the vault has DNA, then attempt to generate a new gene
318                self._attemptGenerateGene(depositedBalance, self.balance)
319            }
320
321            // update the balance ranking
322            let ownerAddr = self.owner?.address
323            if ownerAddr != nil {
324                let adminRef = FixesFungibleToken.borrowAdminPublic()
325                let lastTopHolder = adminRef.getLastTopHolder()
326                if lastTopHolder != ownerAddr || lastTopHolder == nil {
327                    let lastTopBalance = lastTopHolder != nil ? FixesFungibleToken.getTokenBalance(lastTopHolder!) : 0.0
328                    if lastTopBalance < self.balance {
329                        adminRef.onBalanceChanged(ownerAddr!)
330                    }
331                }
332            }
333        }
334
335        /// --------- Implement ViewResolver.Resolver --------- ///
336
337        /// The way of getting all the Metadata Views implemented by FixesFungibleToken
338        ///
339        /// @return An array of Types defining the implemented views. This value will be used by
340        ///         developers to know which parameter to pass to the resolveView() method.
341        ///
342        access(all)
343        view fun getViews(): [Type] {
344            let contractViews = FixesFungibleToken.getContractViews(resourceType: Type<@Vault>())
345            return contractViews
346        }
347
348        /// The way of getting a Metadata View out of the FixesFungibleToken
349        ///
350        /// @param view: The Type of the desired view.
351        /// @return A structure representing the requested view.
352        ///
353        access(all)
354        fun resolveView(_ view: Type): AnyStruct? {
355            return FixesFungibleToken.resolveContractView(resourceType: nil, viewType: view)
356        }
357
358        /// --------- Internal Methods --------- ///
359
360        /// Borrow the mergeable data by key
361        ///
362        access(contract)
363        view fun borrowMergeableDataRef(_ type: Type): auth(FixesTraits.Write) &{FixesTraits.MergeableData}? {
364            return &self.metadata[type]
365        }
366
367        /// Attempt to generate a new gene, Max attempts is 10
368        ///
369        access(self)
370        fun _attemptGenerateGene(_ transactedAmt: UFix64, _ totalAmt: UFix64) {
371            log("-> Attempt to generate gene:"
372                .concat(" Owner: ").concat(self.owner?.address?.toString() ?? "Unknown")
373                .concat(" Valid: ").concat(self.isValidVault() ? "true" : "false")
374                .concat(" DNA: ").concat(self.getDNAIdentifier())
375                .concat(" Mutatable: ").concat(self.getDNAMutatableAmount().toString())
376                .concat(" Transacted: ").concat(transactedAmt.toString())
377                .concat(" Total: ").concat(totalAmt.toString()))
378
379            let remainingAmt = self.getDNAMutatableAmount()
380            if !self.isValidVault() || remainingAmt == 0 || transactedAmt == 0.0 || totalAmt == 0.0 {
381                return
382            }
383            // every 10% of balance change will get a new attempt to mutate the DNA
384            // attempt to generate a new gene
385            self.attemptGenerateGene(remainingAmt)
386        }
387    }
388
389    /// The interface for the FungibleTokenAdmin's global internal writable methods
390    ///
391    access(all) resource interface GlobalInternalWritable {
392        /// update the token holder
393        access(contract)
394        fun onTokenDeposited(_ address: Address): Bool
395
396        /// update the balance ranking
397        access(contract)
398        fun onBalanceChanged(_ address: Address): Bool
399    }
400
401    /// The admin resource for the FRC20 FT
402    ///
403    access(all) resource FungibleTokenAdmin: GlobalInternalWritable, FixesFungibleTokenInterface.IGlobalPublic, FixesFungibleTokenInterface.IMinterHolder, FixesFungibleTokenInterface.IAdminWritable {
404        access(self)
405        let minter: @Minter
406        /// The amount of tokens that all created minters are allowed to mint
407        access(self)
408        var grantedMintableAmount: UFix64
409        /// The top 100 accounts sorted by balance
410        access(self)
411        let top100Accounts: [Address]
412        /// All token holders
413        access(self)
414        let tokenHolders: {Address: Bool}
415        /// All authorized users
416        access(self)
417        let authorizedUsers: {Address: Bool}
418
419        init() {
420            self.minter <- create Minter(allowedAmount: nil)
421            self.grantedMintableAmount = 0.0
422            self.top100Accounts = []
423            self.authorizedUsers = {}
424            self.tokenHolders = {}
425        }
426
427        // ----- Implement AdminInterface -----
428
429        access(all)
430        view fun isAuthorizedUser(_ addr: Address): Bool {
431            return self.authorizedUsers[addr] == true
432        }
433
434        /// Mint new tokens
435        ///
436        access(all)
437        view fun getGrantedMintableAmount(): UFix64 {
438            return self.grantedMintableAmount
439        }
440
441        /// Get the top 100 sorted array of holders, descending by balance
442        ///
443        access(all)
444        view fun getEstimatedTop100Holders(): [Address]? {
445            return self.top100Accounts
446        }
447
448        /// Get the top 1 holder
449        ///
450        access(all)
451        view fun getTop1Holder(): Address? {
452            if self.top100Accounts.length > 0 {
453                return self.top100Accounts[0]
454            }
455            return nil
456        }
457
458        /// Get the last top holder
459        ///
460        access(all)
461        view fun getLastTopHolder(): Address? {
462            if self.top100Accounts.length > 0 {
463                return self.top100Accounts[self.top100Accounts.length - 1]
464            }
465            return nil
466        }
467
468        /// Check if the address is in the top 100
469        ///
470        access(all)
471        view fun isInTop100(_ address: Address): Bool {
472            return self.top100Accounts.contains(address)
473        }
474
475        /// Check if the address is the token holder
476        access(all)
477        view fun isTokenHolder(_ addr: Address): Bool {
478            return self.tokenHolders[addr] == true
479        }
480
481        /// Get the holders amount
482        access(all)
483        view fun getHoldersAmount(): UInt64 {
484            return UInt64(self.tokenHolders.length)
485        }
486
487        /// update the token holder
488        access(contract)
489        fun onTokenDeposited(_ address: Address): Bool {
490            var isUpdated = false
491            if self.tokenHolders[address] == nil {
492                self.tokenHolders[address] = true
493                isUpdated = true
494            }
495            return isUpdated
496        }
497
498        /// update the balance ranking
499        ///
500        access(contract)
501        fun onBalanceChanged(_ address: Address): Bool {
502            log("onBalanceChanged - Start - ".concat(address.toString()))
503            // remove the address from the top 100
504            if let idx = self.top100Accounts.firstIndex(of: address) {
505                self.top100Accounts.remove(at: idx)
506            }
507            // now address is not in the top 100, we need to check balance and insert it
508            let balance = FixesFungibleToken.getTokenBalance(address)
509            var highBalanceIdx = 0
510            var lowBalanceIdx = self.top100Accounts.length - 1
511            // use binary search to find the position
512            while lowBalanceIdx >= highBalanceIdx {
513                let mid = (lowBalanceIdx + highBalanceIdx) / 2
514                let midBalance = FixesFungibleToken.getTokenBalance(self.top100Accounts[mid])
515                // find the position
516                if balance > midBalance {
517                    lowBalanceIdx = mid - 1
518                } else if balance < midBalance {
519                    highBalanceIdx = mid + 1
520                } else {
521                    break
522                }
523            }
524            // insert the address
525            self.top100Accounts.insert(at: highBalanceIdx, address)
526            log("onBalanceChanged - End - ".concat(address.toString())
527                .concat(" balance: ").concat(balance.toString())
528                .concat(" rank: ").concat(highBalanceIdx.toString()))
529            // remove the last one if the length is greater than 100
530            if self.top100Accounts.length > 100 {
531                self.top100Accounts.removeLast()
532            }
533            return true
534        }
535
536        // ----- Implement IMinterHolder -----
537
538        /// Borrow the minter reference
539        ///
540        access(contract)
541        view fun borrowMinter(): auth(FixesFungibleTokenInterface.Manage) &Minter {
542            return self.borrowSuperMinter()
543        }
544
545        // ------ Implement IAdminWritable ------
546
547        /// Create a new Minter resource
548        ///
549        access(FixesFungibleTokenInterface.Manage)
550        fun createMinter(allowedAmount: UFix64): @Minter {
551            let minter <- create Minter(allowedAmount: allowedAmount)
552            self.grantedMintableAmount = self.grantedMintableAmount + allowedAmount
553            return <- minter
554        }
555
556        /// Update the authorized users
557        ///
558        access(FixesFungibleTokenInterface.Manage)
559        fun updateAuthorizedUsers(_ addr: Address, _ isAdd: Bool) {
560            self.authorizedUsers[addr] = isAdd
561        }
562
563        /// Borrow the super minter resource
564        ///
565        access(FixesFungibleTokenInterface.Manage)
566        view fun borrowSuperMinter(): auth(FixesFungibleTokenInterface.Manage) &Minter {
567            return &self.minter
568        }
569    }
570
571    /// Resource object that token admin accounts can hold to mint new tokens.
572    ///
573    access(all) resource Minter: FixesFungibleTokenInterface.IMinter {
574        /// The total allowed amount of the minting token, if nil means unlimited
575        access(all)
576        let totalAllowedAmount: UFix64?
577        /// The amount of tokens that the minter is allowed to mint
578        access(all)
579        var allowedAmount: UFix64?
580
581        init(allowedAmount: UFix64?) {
582            self.totalAllowedAmount = allowedAmount
583            self.allowedAmount = allowedAmount
584        }
585
586        // ----- Implement IMinter -----
587
588        /// Get the symbol of the minting token
589        ///
590        access(all)
591        view fun getSymbol(): String {
592            return FixesFungibleToken.getSymbol()
593        }
594
595        /// Get the type of the minting token
596        access(all)
597        view fun getTokenType(): Type {
598            return Type<@FixesFungibleToken.Vault>()
599        }
600
601        /// Get the key in the accounts pool
602        access(all)
603        view fun getAccountsPoolKey(): String? {
604            return "$".concat(self.getSymbol())
605        }
606
607        /// Get the contract address of the minting token
608        access(all)
609        view fun getContractAddress(): Address {
610            return FixesFungibleToken.getAccountAddress()
611        }
612
613        /// Get the max supply of the minting token
614        access(all)
615        view fun getMaxSupply(): UFix64 {
616            return FixesFungibleToken.getMaxSupply() ?? UFix64.max
617        }
618
619        /// Get the total supply of the minting token
620        ///
621        access(all)
622        view fun getTotalSupply(): UFix64 {
623            return FixesFungibleToken.getTotalSupply()
624        }
625
626        /// Get the current mintable amount
627        ///
628        access(all)
629        view fun getCurrentMintableAmount(): UFix64 {
630            return self.allowedAmount ?? self.getUnsuppliedAmount()
631        }
632
633        /// Get the total allowed mintable amount
634        ///
635        access(all)
636        view fun getTotalAllowedMintableAmount(): UFix64 {
637            return self.totalAllowedAmount ?? self.getMaxSupply()
638        }
639
640        /// Get the vault data of the minting token
641        ///
642        access(all)
643        fun getVaultData(): FungibleTokenMetadataViews.FTVaultData {
644            return FixesFungibleToken.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
645                ?? panic("The vault data is not found")
646        }
647
648        /// Function that mints new tokens, adds them to the total supply,
649        /// and returns them to the calling context.
650        ///
651        /// @param amount: The quantity of tokens to mint
652        /// @return The Vault resource containing the minted tokens
653        ///
654        access(FixesFungibleTokenInterface.Manage)
655        fun mintTokens(amount: UFix64): @FixesFungibleToken.Vault {
656            pre {
657                amount > 0.0: "Amount minted must be greater than zero"
658                self.allowedAmount == nil || amount <= self.allowedAmount!: "Amount minted must be less than the allowed amount"
659            }
660            if self.allowedAmount != nil {
661                self.allowedAmount = self.allowedAmount! - amount
662            }
663
664            let newVault <- create Vault(balance: amount)
665            FixesFungibleToken.totalSupply = FixesFungibleToken.totalSupply + amount
666            emit TokensMinted(amount: amount)
667            return <- newVault
668        }
669
670        /// Mint tokens with user's inscription
671        ///
672        access(FixesFungibleTokenInterface.Manage)
673        fun initializeVaultByInscription(
674            vault: @{FungibleToken.Vault},
675            ins: auth(Fixes.Extractable) &Fixes.Inscription
676        ): @{FungibleToken.Vault} {
677            pre {
678                vault.isInstance(Type<@FixesFungibleToken.Vault>()): "The vault must be an instance of FixesFungibleToken.Vault"
679            }
680            post {
681                before(vault.balance) == result.balance: "The vault balance must be the same"
682            }
683            let typedVault <- vault as! @FixesFungibleToken.Vault
684            // ensure vault is initialized
685            if !typedVault.isValidVault() {
686                if ins.isExtractable() {
687                    typedVault.initialize(ins, nil)
688                    // execute the inscription
689                    FixesFungibleToken.executeInscription(ins: ins, usage: "init")
690                } else {
691                    typedVault.initialize(nil, ins.owner?.address)
692                }
693            }
694            return <- typedVault
695        }
696
697        /// Burn tokens with user's inscription
698        ///
699        access(FixesFungibleTokenInterface.Manage)
700        fun burnTokenWithInscription(
701            vault: @{FungibleToken.Vault},
702            ins: auth(Fixes.Extractable) &Fixes.Inscription
703        ) {
704            pre {
705                vault.isInstance(Type<@FixesFungibleToken.Vault>()): "The vault must be an instance of FixesFungibleToken.Vault"
706            }
707            // execute the inscription
708            if ins.isExtractable() {
709                FixesFungibleToken.executeInscription(ins: ins, usage: "burn")
710            }
711            Burner.burn(<- vault)
712        }
713    }
714
715    /// ------------ Internal Methods ------------
716
717    /// Exuecte and extract FlowToken in the inscription
718    ///
719    access(contract)
720    fun executeInscription(ins: auth(Fixes.Extractable) &Fixes.Inscription, usage:String) {
721        let meta = FixesInscriptionFactory.parseMetadata(ins.borrowData())
722        assert(
723            meta["usage"] == usage || meta["usage"] == "*",
724            message: "The inscription usage must be ".concat(usage)
725        )
726        let tick = meta["tick"] ?? panic("The ticker name is not found")
727        assert(
728            tick.length > 0 && tick[0] == "$" && tick == "$".concat(self.getSymbol()),
729            message: "The ticker name is not matched"
730        )
731
732        // register token to the tokenlist
733        TokenList.ensureFungibleTokenRegistered(self.account.address, "FixesFungibleToken")
734
735        // execute the inscription
736        let acctsPool = FRC20AccountsPool.borrowAccountsPool()
737        acctsPool.executeInscription(type: FRC20AccountsPool.ChildAccountType.FungibleToken, ins)
738    }
739
740    /// ------------ General Functions ------------
741
742    /// Function that creates a new Vault with a balance of zero
743    /// and returns it to the calling context. A user must call this function
744    /// and store the returned Vault in their storage in order to allow their
745    /// account to be able to receive deposits of this token type.
746    ///
747    /// @return The new Vault resource
748    ///
749    access(all)
750    fun createEmptyVault(vaultType: Type): @Vault {
751        return <-create Vault(balance: 0.0)
752    }
753
754    /// Function that resolves a metadata view for this contract.
755    ///
756    /// @param view: The Type of the desired view.
757    /// @return A structure representing the requested view.
758    ///
759    access(all)
760    fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
761        // external url
762        let externalUrl = FixesFungibleToken.getExternalUrl()
763        switch viewType {
764            case Type<MetadataViews.ExternalURL>():
765                return externalUrl != nil
766                    ? MetadataViews.ExternalURL(externalUrl!)
767                    : MetadataViews.ExternalURL("https://fixes.world/")
768            case Type<FungibleTokenMetadataViews.FTView>():
769                return FungibleTokenMetadataViews.FTView(
770                    ftDisplay: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTDisplay>()) as! FungibleTokenMetadataViews.FTDisplay?,
771                    ftVaultData: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
772                )
773            case Type<FungibleTokenMetadataViews.FTDisplay>():
774                let store = FixesFungibleToken.borrowSharedStore()
775                let tick = FixesFungibleToken.getSymbol()
776
777                // medias
778                let medias: [MetadataViews.Media] = []
779                let logoKey = store.getKeyByEnum(FRC20FTShared.ConfigType.FungibleTokenLogoPrefix)!
780                if let iconUrl = store.get(logoKey) as! String? {
781                    medias.append(MetadataViews.Media(
782                        file: MetadataViews.HTTPFile(url: iconUrl),
783                        mediaType: "image/png" // default is png
784                    ))
785                }
786                if let iconUrl = store.get(logoKey.concat("png")) as! String? {
787                    medias.append(MetadataViews.Media(
788                        file: MetadataViews.HTTPFile(url: iconUrl),
789                        mediaType: "image/png"
790                    ))
791                }
792                if let iconUrl = store.get(logoKey.concat("svg")) as! String? {
793                    medias.append(MetadataViews.Media(
794                        file: MetadataViews.HTTPFile(url: iconUrl),
795                        mediaType: "image/svg+xml"
796                    ))
797                }
798                if let iconUrl = store.get(logoKey.concat("jpg")) as! String? {
799                    medias.append(MetadataViews.Media(
800                        file: MetadataViews.HTTPFile(url: iconUrl),
801                        mediaType: "image/jpeg"
802                    ))
803                }
804                if let iconUrl = store.get(logoKey.concat("gif")) as! String? {
805                    medias.append(MetadataViews.Media(
806                        file: MetadataViews.HTTPFile(url: iconUrl),
807                        mediaType: "image/gif"
808                    ))
809                }
810                // socials
811                let socialDict: {String: MetadataViews.ExternalURL} = {}
812                let socialKey = store.getKeyByEnum(FRC20FTShared.ConfigType.FungibleTokenSocialPrefix)!
813                // load social infos
814                if let socialUrl = store.get(socialKey.concat("twitter")) {
815                    socialDict["twitter"] = MetadataViews.ExternalURL((socialUrl as! String?)!)
816                }
817                if let socialUrl = store.get(socialKey.concat("telegram")) {
818                    socialDict["telegram"] = MetadataViews.ExternalURL((socialUrl as! String?)!)
819                }
820                if let socialUrl = store.get(socialKey.concat("discord")) {
821                    socialDict["discord"] = MetadataViews.ExternalURL((socialUrl as! String?)!)
822                }
823                if let socialUrl = store.get(socialKey.concat("github")) {
824                    socialDict["github"] = MetadataViews.ExternalURL((socialUrl as! String?)!)
825                }
826                // override all customized fields
827                return FungibleTokenMetadataViews.FTDisplay(
828                    name: FixesFungibleToken.getDisplayName() ?? tick,
829                    symbol: tick,
830                    description: FixesFungibleToken.getTokenDescription() ?? "No description",
831                    externalURL: externalUrl != nil
832                        ? MetadataViews.ExternalURL(externalUrl!)
833                        : MetadataViews.ExternalURL("https://linktr.ee/fixes.world/"),
834                    logos: medias.length > 0
835                        ? MetadataViews.Medias(medias)
836                        : MetadataViews.Medias([]),
837                    socials: socialDict
838                )
839            case Type<FungibleTokenMetadataViews.FTVaultData>():
840                let prefix = FixesFungibleToken.getPathPrefix()
841                return FungibleTokenMetadataViews.FTVaultData(
842                    storagePath: FixesFungibleToken.getVaultStoragePath(),
843                    receiverPath: FixesFungibleToken.getReceiverPublicPath(),
844                    metadataPath: FixesFungibleToken.getVaultPublicPath(),
845                    receiverLinkedType: Type<&{FungibleToken.Receiver}>(),
846                    metadataLinkedType: Type<&FixesFungibleToken.Vault>(),
847                    createEmptyVaultFunction: (fun (): @FixesFungibleToken.Vault {
848                        return <-FixesFungibleToken.createEmptyVault(vaultType: Type<@Vault>())
849                    })
850                )
851            case Type<FungibleTokenMetadataViews.TotalSupply>():
852                return FungibleTokenMetadataViews.TotalSupply(
853                    totalSupply: FixesFungibleToken.totalSupply
854                )
855        }
856        return nil
857    }
858
859    /// Function that returns all the Metadata Views implemented by a Fungible Token
860    ///
861    /// @return An array of Types defining the implemented views. This value will be used by
862    ///         developers to know which parameter to pass to the resolveView() method.
863    ///
864    access(all)
865    view fun getContractViews(resourceType: Type?): [Type] {
866        return [
867            Type<MetadataViews.ExternalURL>(),
868            Type<FungibleTokenMetadataViews.TotalSupply>(),
869            Type<FungibleTokenMetadataViews.FTView>(),
870            Type<FungibleTokenMetadataViews.FTDisplay>(),
871            Type<FungibleTokenMetadataViews.FTVaultData>()
872        ]
873    }
874
875    /// the real total supply is loaded from the FRC20Indexer
876    ///
877    access(all)
878    view fun getTotalSupply(): UFix64 {
879        return self.totalSupply
880    }
881
882    /// Get the prefix for the storage paths
883    ///
884    access(all)
885    view fun getPathPrefix(): String {
886        return "FixsStandardFT_".concat(self.account.address.toString()).concat(self.getSymbol()).concat("_")
887    }
888
889    /// Borrow the admin public reference
890    ///
891    access(contract)
892    view fun borrowAdminPublic(): &FungibleTokenAdmin {
893        return self.account
894            .capabilities.get<&FungibleTokenAdmin>(self.getAdminPublicPath())
895            .borrow() ?? panic("The FungibleToken Admin is not found")
896    }
897
898    /// Initialize the contract with ticker name
899    ///
900    init() {
901        // Initialize
902        self.totalSupply = 0.0
903
904        // Emit an event that shows that the contract was initialized
905        emit TokensInitialized(initialSupply: self.totalSupply)
906
907        // Singleton resources
908        let globalStore = FRC20FTShared.borrowGlobalStoreRef()
909        let acctsPool = FRC20AccountsPool.borrowAccountsPool()
910
911        let isSysmtemDeploy = self.account.address == globalStore.owner?.address
912        if isSysmtemDeploy {
913            // DO NOTHING, It will be a template contract for deploying new FRC20 Fungible tokens
914            return
915        }
916
917        // Step.0 Ensure shared store exists
918
919        // Initialize the shared store
920        if self.account.storage.borrow<&AnyResource>(from: FRC20FTShared.SharedStoreStoragePath) == nil {
921            let sharedStore <- FRC20FTShared.createSharedStore()
922            self.account.storage.save(<- sharedStore, to: FRC20FTShared.SharedStoreStoragePath)
923        }
924        // link the resource to the public path
925        if self.account
926            .capabilities.get<&FRC20FTShared.SharedStore>(FRC20FTShared.SharedStorePublicPath)
927            .borrow() == nil {
928            self.account.capabilities.unpublish(FRC20FTShared.SharedStorePublicPath)
929            self.account.capabilities.publish(
930                self.account.capabilities.storage.issue<&FRC20FTShared.SharedStore>(FRC20FTShared.SharedStoreStoragePath),
931                at: FRC20FTShared.SharedStorePublicPath
932            )
933        }
934        // borrow the shared store
935        let store = self.account.storage
936            .borrow<auth(FRC20FTShared.Write) &FRC20FTShared.SharedStore>(from: FRC20FTShared.SharedStoreStoragePath)
937            ?? panic("The shared store is not found")
938
939        // Step.1 Try get the ticker name from the shared store
940        var tickerName = store.getByEnum(FRC20FTShared.ConfigType.FungibleTokenSymbol) as! String?
941        if tickerName == nil {
942            var fixesFTKey: String? = nil
943            // try load the ticker name from AccountPools
944            let addrDict = acctsPool.getAddresses(type: FRC20AccountsPool.ChildAccountType.FungibleToken)
945            let contractAddr = self.account.address
946            addrDict.forEachKey(fun (key: String): Bool {
947                if let addr = addrDict[key] {
948                    if addr == contractAddr {
949                        fixesFTKey = key
950                        return false
951                    }
952                }
953                return true
954            })
955
956            // set the ticker name
957            if let foundKey = fixesFTKey {
958                assert(
959                    foundKey[0] == "$",
960                    message: "The ticker name is invalid."
961                )
962                tickerName = foundKey.slice(from: 1, upTo: foundKey.length)
963                store.setByEnum(FRC20FTShared.ConfigType.FungibleTokenSymbol, value: tickerName!)
964            }
965        }
966
967        assert(
968            tickerName != nil,
969            message: "The ticker name is not found"
970        )
971        log("Init the Fungible Token symbol:".concat(tickerName!))
972
973        // setup admin resource
974        let admin <- create FungibleTokenAdmin()
975        let adminStoragePath = self.getAdminStoragePath()
976        self.account.storage.save(<-admin, to: adminStoragePath)
977        // link the admin resource to the public path
978        let adminPublicPath = self.getAdminPublicPath()
979        self.account.capabilities.publish(
980            self.account.capabilities.storage.issue<&FungibleTokenAdmin>(adminStoragePath),
981            at: adminPublicPath
982        )
983
984        // ensure the admin resource exists
985        let adminRef = self.account.storage
986            .borrow<auth(FixesFungibleTokenInterface.Manage) &FungibleTokenAdmin>(from: adminStoragePath)
987            ?? panic("The FungibleToken Admin is not found")
988        let deployer = self.getDeployerAddress()
989        // add the deployer as the authorized user
990        adminRef.updateAuthorizedUsers(deployer, true)
991
992        // Step.2 Setup the vault and receiver for the contract account
993
994        let storagePath = self.getVaultStoragePath()
995        let publicPath = self.getVaultPublicPath()
996        let receiverPath = self.getReceiverPublicPath()
997
998        // Create the Vault with the total supply of tokens and save it in storage.
999        let vault <- create Vault(balance: self.totalSupply)
1000        self.account.storage.save(<-vault, to: storagePath)
1001
1002        // Create a public capability to the stored Vault that exposes
1003        // the `deposit` method through the `Receiver` interface.
1004        self.account.capabilities.publish(
1005            self.account.capabilities.storage.issue<&{FungibleToken.Receiver}>(storagePath),
1006            at: receiverPath
1007        )
1008        // Create a public capability to the stored Vault that only exposes
1009        // the `balance` field and the `resolveView` method through the `Balance` interface
1010        self.account.capabilities.publish(
1011            self.account.capabilities.storage.issue<&FixesFungibleToken.Vault>(storagePath),
1012            at: publicPath
1013        )
1014    }
1015}
1016