Smart Contract

FixesAssetMeta

A.d2abb5dbf5e08666.FixesAssetMeta

Valid From

86,128,779

Deployed

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

Dependents

0 imports
1/**
2
3> Author: Fixes Lab <https://github.com/fixes-world/>
4
5# FixesFungibleTokenGenes
6
7This is a sub-feature contract for Fixes Asset, which implements a way to generate on-chain genes.
8It is used by all Fixes' Asset.
9
10*/
11import FungibleToken from 0xf233dcee88fe0abe
12import StringUtils from 0xa340dc0a4ec828ab
13import FixesTraits from 0xd2abb5dbf5e08666
14
15/// The Fixes Asset Genes contract
16///
17access(all) contract FixesAssetMeta {
18
19    // ----------------- DNA and Gene -----------------
20
21    /// The gene quality level
22    ///
23    access(all) enum GeneQuality: UInt8 {
24        // Quality scopes 0~4
25        access(all) case Nascent
26        access(all) case Basic
27        access(all) case Enhanced
28        access(all) case Augmented
29        access(all) case Empowered
30        // Quality scopes 5~9
31        access(all) case Breakthrough
32        access(all) case Advanced
33        access(all) case Potent
34        access(all) case Exemplary
35        access(all) case Evolution
36        // Quality scopes 10~15
37        access(all) case Mystic
38        access(all) case Arcane
39        access(all) case Miraculous
40        access(all) case Celestial
41        access(all) case Cosmic
42        access(all) case Eternal
43    }
44
45    /// Get the quality level up threshold
46    /// @param from The quality level
47    access(all)
48    view fun getQualityLevelUpThreshold(_ from: GeneQuality): UInt64 {
49        // The quality up threshold, using a 0~15 scale
50        if from.rawValue < GeneQuality.Breakthrough.rawValue {
51            // 0~4 Basic linear: Threshold = QualityLevel * 100 + 100 -> 100, 200, 300, 400, 500
52            return UInt64(from.rawValue) * 100 + 100
53        } else if from.rawValue < GeneQuality.Mystic.rawValue {
54            // 5~9 Square: Threshold = (QualityLevel - 4) ^ 2 * 1000 + 1000 -> 2000, 5000, 10000, 17000, 26000
55            let argValue = from.rawValue - GeneQuality.Empowered.rawValue
56            return UInt64(argValue * argValue) * 1000 + 1000
57        } else {
58            // 10~15 Cubic: Threshold = (QualityLevel - 9) ^ 3 * 10000 + 20000
59            let argValue = from.rawValue - GeneQuality.Evolution.rawValue
60            return UInt64(argValue * argValue * argValue) * 10000 + 20000
61        }
62    }
63
64    /// Get the quality level down threshold
65    /// @param from The quality level
66    /// @param to The quality level
67    access(all)
68    view fun getGeneMergeLossOrGain(_ from: GeneQuality, _ to: GeneQuality): UFix64 {
69        // Larger quality level merge to lower quality level means gain rate
70        // The gain rate = LevelDiff * 6% + 10%
71        if from.rawValue > to.rawValue {
72            let levelDiff = from.rawValue.saturatingSubtract(to.rawValue)
73            return 1.1 + UFix64(levelDiff) * 0.06
74        } else if from.rawValue < to.rawValue {
75            // Otherwise, loss rate
76            // The loss rate = LevelDiff * 3% + 5%
77            let levelDiff = to.rawValue.saturatingSubtract(from.rawValue)
78            return 0.95 - UFix64(levelDiff) * 0.03
79        } else {
80            return 1.0
81        }
82    }
83
84    /// The gene data structure
85    ///
86    access(all) struct Gene: FixesTraits.MergeableData {
87        access(all) let id: [Character;4]
88        access(all) var quality: GeneQuality
89        access(all) var exp: UInt64
90
91        init(
92            id: [Character;4]?,
93            quality: GeneQuality?,
94            exp: UInt64?
95        ) {
96            if id != nil {
97                self.id = id!
98            } else {
99                let dnaKeys: [Character] = ["A", "C", "G", "T"]
100                self.id = [
101                    dnaKeys[revertibleRandom<UInt32>(modulo: 4)],
102                    dnaKeys[revertibleRandom<UInt32>(modulo: 4)],
103                    dnaKeys[revertibleRandom<UInt32>(modulo: 4)],
104                    dnaKeys[revertibleRandom<UInt32>(modulo: 4)]
105                ]
106            }
107            self.quality = quality ?? GeneQuality.Nascent
108            self.exp = exp ?? 0
109            // Log the gene creation
110            log("Created Gene["
111                .concat(self.id[0].toString()).concat(self.id[1].toString()).concat(self.id[2].toString()).concat(self.id[3].toString())
112                .concat("]: quality=").concat(self.quality.rawValue.toString()).concat(", exp=").concat(self.exp.toString())
113            )
114        }
115
116        /// Get the id of the data
117        ///
118        access(all)
119        view fun getId(): String {
120            return String.fromCharacters([self.id[0], self.id[1], self.id[2], self.id[3]])
121        }
122
123        /// Get the string value of the data
124        ///
125        access(all)
126        view fun toString(): String {
127            return self.getId().concat("|")
128            .concat(self.quality.rawValue.toString()).concat("=")
129            .concat(self.exp.toString())
130        }
131
132        /// Get the data keys
133        ///
134        access(all)
135        view fun getKeys(): [String] {
136            return ["id", "quality", "exp"]
137        }
138
139        /// Get the value of the data
140        ///
141        access(all)
142        view fun getValue(_ key: String): AnyStruct? {
143            if key == "id" {
144                return self.getId()
145            } else if key == "quality" {
146                return self.quality
147            } else if key == "exp" {
148                return self.exp
149            }
150            return nil
151        }
152
153        /// Split the data into another instance
154        access(FixesTraits.Write)
155        fun split(_ perc: UFix64): {FixesTraits.MergeableData} {
156            post {
157                self.getId() == result.getId(): "The gene id is not the same so cannot split"
158            }
159            let withdrawexp = UInt64(UInt256(self.exp) * UInt256(perc * 100000.0) / 100000)
160            if withdrawexp > 0 {
161                self.exp = self.exp - withdrawexp
162            }
163
164            log("Split Gene["
165                .concat(self.id[0].toString()).concat(self.id[1].toString()).concat(self.id[2].toString()).concat(self.id[3].toString())
166                .concat("]: A=").concat(self.exp.toString()).concat(", B=").concat(withdrawexp.toString())
167                .concat(", quality=").concat(self.quality.rawValue.toString()))
168
169            // Create a new struct
170            let newGenes: FixesAssetMeta.Gene = Gene(
171                id: self.id,
172                quality: self.quality, // same quality
173                exp: withdrawexp, // splited the exp
174            )
175            return newGenes
176        }
177
178        /// Merge the data from another instance
179        /// From and Self must have the same id and same type(Ensured by interface)
180        ///
181        access(FixesTraits.Write)
182        fun merge(_ from: {FixesTraits.MergeableData}): Void {
183            pre {
184                self.getId() == from.getId(): "The gene id is not the same so cannot merge"
185            }
186            let oldExp = self.exp
187            let fromGenes = from as! Gene
188            let convertRate = FixesAssetMeta.getGeneMergeLossOrGain(fromGenes.quality, self.quality)
189            let convertexp = UInt64(UInt128(fromGenes.exp) * UInt128(convertRate * 10000.0) / 10000)
190            self.exp = self.exp + convertexp
191
192            log("Merge Gene["
193                .concat(self.id[0].toString()).concat(self.id[1].toString()).concat(self.id[2].toString()).concat(self.id[3].toString())
194                .concat("]: ExpOld=").concat(oldExp.toString()).concat(", ExpNew=").concat(self.exp.toString()
195                .concat(", quality=").concat(self.quality.rawValue.toString())))
196
197            // check if the quality can be upgraded
198            var upgradeThreshold = FixesAssetMeta.getQualityLevelUpThreshold(self.quality)
199            var isUpgradable = upgradeThreshold <= self.exp && self.quality.rawValue < GeneQuality.Eternal.rawValue
200            while isUpgradable {
201                self.quality = GeneQuality(rawValue: self.quality.rawValue + 1)!
202                self.exp = self.exp - upgradeThreshold
203                // check upgrade again
204                upgradeThreshold = FixesAssetMeta.getQualityLevelUpThreshold(self.quality)
205                isUpgradable = upgradeThreshold <= self.exp && self.quality.rawValue < GeneQuality.Eternal.rawValue
206
207                log("Upgrade Gene["
208                    .concat(self.id[0].toString()).concat(self.id[1].toString()).concat(self.id[2].toString()).concat(self.id[3].toString())
209                    .concat("]: Exp=").concat(self.exp.toString()).concat(", quality=").concat(self.quality.rawValue.toString()))
210            }
211        }
212    }
213
214    /// The DNA data structure
215    ///
216    access(all) struct DNA: FixesTraits.MergeableData {
217        access(all) let identifier: String
218        access(all) let owner: Address
219        access(all) let genes: {String: Gene}
220        access(all) var mutatableAmount: UInt64
221
222        view init(
223            _ identifier: String,
224            _ owner: Address,
225            _ mutatableAmount: UInt64?
226        ) {
227            self.owner = owner
228            self.identifier = identifier
229            self.genes = {}
230            self.mutatableAmount = mutatableAmount ?? 0
231        }
232
233        /// Get the id of the data
234        ///
235        access(all)
236        view fun getId(): String {
237            return self.identifier.concat("@").concat(self.owner.toString())
238        }
239
240        /// Get the string value of the data
241        ///
242        access(all)
243        view fun toString(): String {
244            return "genes.count=".concat(self.genes.length.toString())
245            .concat(", mutatableAmount=").concat(self.mutatableAmount.toString())
246        }
247
248        /// Get the data keys
249        ///
250        access(all)
251        view fun getKeys(): [String] {
252            return ["identifier", "owner", "genes", "mutatableAmount"]
253        }
254
255        /// Get the value of the data
256        /// It means genes keys of the DNA
257        ///
258        access(all)
259        view fun getValue(_ key: String): AnyStruct? {
260            if key == "identifier" {
261                return self.identifier
262            } else if key == "owner" {
263                return self.owner
264            } else if key == "genes" {
265                return self.genes.keys
266            } else if key == "mutatableAmount" {
267                return self.mutatableAmount
268            }
269            return nil
270        }
271
272        /// Get the writable keys
273        ///
274        access(all)
275        view fun getWritableKeys(): [String] {
276            return ["mutatableAmount"]
277        }
278
279        /// Set the value of the data
280        ///
281        access(FixesTraits.Write)
282        fun setValue(_ key: String, _ value: AnyStruct) {
283            if key == "mutatableAmount" {
284                self.mutatableAmount = value as! UInt64
285            }
286        }
287
288        /// Split the data into another instance
289        ///
290        access(FixesTraits.Write)
291        fun split(_ perc: UFix64): {FixesTraits.MergeableData} {
292            post {
293                self.getId() == result.getId(): "The result id is not the same so cannot split"
294            }
295            let newDna = DNA(self.identifier, self.owner, nil)
296            // No need to split
297            if perc == 0.0 {
298                return newDna
299            }
300            // Mutate the mutatable amount
301            if perc >= 1.0 {
302                newDna.mutatableAmount = self.mutatableAmount
303                self.mutatableAmount = 0
304            }
305            // Split the genes
306            for key in self.genes.keys {
307                // Split the gene, use reference to ensure data consistency
308                if let geneRef = &self.genes[key] as auth(FixesTraits.Write) &Gene? {
309                    let geneId = geneRef.getId()
310                    // add the new gene to the new DNA
311                    let newGene = geneRef.split(perc) as! Gene
312                    // Don't add the gene if the exp is 0
313                    if newGene.exp > 0 {
314                        newDna.genes[geneId] = newGene
315                    }
316                }
317            }
318            return newDna
319        }
320
321        /// Merge the data from another instance
322        /// The type of the data must be the same(Ensured by interface)
323        ///
324        access(FixesTraits.Write)
325        fun merge(_ from: {FixesTraits.MergeableData}): Void {
326            let fromDna = from as! DNA
327            // identifer must be the same
328            assert(
329                self.identifier == fromDna.identifier,
330                message: "The DNA identifier is not the same so cannot merge"
331            )
332            for key in fromDna.genes.keys {
333                if let fromGene = fromDna.genes[key] {
334                    if fromGene.exp == 0 {
335                        continue
336                    }
337                    self.mergeGene(fromGene)
338                }
339            } // end for
340            // merge mutatable amount
341            self.mutatableAmount = self.mutatableAmount + fromDna.mutatableAmount
342        }
343
344        // ---- Customized Methods ----
345
346        /// Check if the DNA is mutatable
347        ///
348        access(all)
349        view fun isMutatable(): Bool {
350            return self.mutatableAmount > 0
351        }
352
353        /// Add a gene to the DNA
354        ///
355        access(account)
356        fun addGene(_ gene: Gene): Void {
357            pre {
358                self.isMutatable(): "The DNA is not mutatable"
359            }
360            self.mergeGene(gene)
361            // Decrease the mutatable amount
362            self.mutatableAmount = self.mutatableAmount - 1
363        }
364
365        /// Merge a gene to the DNA
366        ///
367        access(self)
368        fun mergeGene(_ gene: Gene): Void {
369            let geneId = gene.getId()
370            // Merge the gene, use reference to ensure data consistency
371            if let geneRef = &self.genes[geneId] as auth(FixesTraits.Write) &Gene? {
372                geneRef.merge(gene)
373            } else {
374                // If the gene is not exist, just copy it
375                self.genes[geneId] = gene
376            }
377        }
378    }
379
380    /// Try mutate or generate a new gene
381    ///
382    access(all)
383    fun attemptToGenerateGene(): Gene? {
384        // 30% to generate a gene
385        let rand = revertibleRandom<UInt64>(modulo: 10000)
386        var quality = GeneQuality.Nascent
387        if rand < 2 {
388            // 0.02% to generate a gene with quality Miraculous
389            quality = GeneQuality.Miraculous
390        } else if rand < 5 {
391            // 0.03% to generate a gene with quality Arcane
392            quality = GeneQuality.Arcane
393        } else if rand < 10 {
394            // 0.05% to generate a gene with quality Mystic
395            quality = GeneQuality.Mystic
396        } else if rand < 20 {
397            // 0.10% to generate a gene with quality Evolution
398            quality = GeneQuality.Evolution
399        } else if rand < 40 {
400            // 0.20% to generate a gene with quality Exemplary
401            quality = GeneQuality.Exemplary
402        } else if rand < 70 {
403            // 0.30% to generate a gene with quality Potent
404            quality = GeneQuality.Potent
405        } else if rand < 120 {
406            // 0.50% to generate a gene with quality Advanced
407            quality = GeneQuality.Advanced
408        } else if rand < 200 {
409            // 0.80% to generate a gene with quality Breakthrough
410            quality = GeneQuality.Breakthrough
411        } else if rand < 350 {
412            // 1.50% to generate a gene with quality Empowered
413            quality = GeneQuality.Empowered
414        } else if rand < 600 {
415            // 2.50% to generate a gene with quality Augmented
416            quality = GeneQuality.Augmented
417        } else if rand < 1000 {
418            // 4.00% to generate a gene with quality Enhanced
419            quality = GeneQuality.Enhanced
420        } else if rand < 1800 {
421            // 8.00% to generate a gene with quality Basic
422            quality = GeneQuality.Basic
423        } else if rand < 3000 {
424            // 12.00% to generate a gene with quality Nascent
425            quality = GeneQuality.Nascent
426        } else {
427            return nil
428        }
429        let threshold = FixesAssetMeta.getQualityLevelUpThreshold(quality)
430        let exp = revertibleRandom<UInt64>(modulo: threshold / 5) // random exp from 20% of the threshold
431        return Gene(id: nil, quality: quality, exp: exp)
432    }
433
434    // ----------------- End of DNA and Gene -----------------
435
436    // ----------------- Deposit Tax (Deprecated) -----------------
437
438    /// The DepositTax data structure
439    ///
440    access(all) struct DepositTax: FixesTraits.MergeableData {
441        access(all) var flags: {String: Bool}
442
443        init(
444            _ flags: {String: Bool}?,
445        ) {
446            self.flags = flags ?? { "enabled": true }
447        }
448
449        /// Get the id of the data
450        ///
451        access(all)
452        view fun getId(): String {
453            return "DepositTax"
454        }
455
456        /// Get the string value of the data
457        ///
458        access(all)
459        view fun toString(): String {
460            var flags: String = ""
461            for key in self.flags.keys {
462                let str = key.concat("=").concat(self.flags[key] == true ? "1" : "0")
463                if flags != "" {
464                    flags = flags.concat(",")
465                }
466                flags = flags.concat(str)
467            }
468            return flags
469        }
470
471        /// Get the data keys
472        ///
473        access(all)
474        view fun getKeys(): [String] {
475            return ["enabled"]
476        }
477
478        /// Get the value of the data
479        /// It means genes keys of the DNA
480        ///
481        access(all)
482        view fun getValue(_ key: String): AnyStruct? {
483            if key == "enabled" {
484                return self.flags["enabled"]
485            }
486            return nil
487        }
488
489        /// Get the writable keys
490        ///
491        access(all)
492        view fun getWritableKeys(): [String] {
493            return ["enabled"]
494        }
495
496        /// Set the value of the data
497        ///
498        access(FixesTraits.Write)
499        fun setValue(_ key: String, _ value: AnyStruct) {
500            if key == "enabled" {
501                self.flags["enabled"] = value as! Bool
502            }
503        }
504
505        /// Split the data into another instance
506        ///
507        access(FixesTraits.Write)
508        fun split(_ perc: UFix64): {FixesTraits.MergeableData} {
509            post {
510                self.getId() == result.getId(): "The result id is not the same so cannot split"
511            }
512            return DepositTax(self.flags)
513        }
514
515        /// Merge the data from another instance
516        /// The type of the data must be the same(Ensured by interface)
517        ///
518        access(FixesTraits.Write)
519        fun merge(_ from: {FixesTraits.MergeableData}): Void {
520            // Nothing to merge
521        }
522    }
523
524    // ----------------- End of Deposit Tax -----------------
525
526    // ----------------- ExclusiveMeta -----------------
527
528    /// The DepositTax data structure
529    ///
530    access(all) struct ExclusiveMeta: FixesTraits.MergeableData {
531
532        /// Get the id of the data
533        ///
534        access(all)
535        view fun getId(): String {
536            return "ExclusiveMeta"
537        }
538
539        /// Get the string value of the data
540        ///
541        access(all)
542        view fun toString(): String {
543            return self.getId()
544        }
545
546        /// Get the data keys
547        ///
548        access(all)
549        view fun getKeys(): [String] {
550            return []
551        }
552
553        /// Get the value of the data
554        /// It means genes keys of the DNA
555        ///
556        access(all)
557        view fun getValue(_ key: String): AnyStruct? {
558            return nil
559        }
560
561        /// Split the data into another instance
562        ///
563        access(FixesTraits.Write)
564        fun split(_ perc: UFix64): {FixesTraits.MergeableData} {
565            return ExclusiveMeta()
566        }
567
568        /// Merge the data from another instance
569        /// The type of the data must be the same(Ensured by interface)
570        ///
571        access(FixesTraits.Write)
572        fun merge(_ from: {FixesTraits.MergeableData}): Void {
573            // Nothing to merge
574        }
575    }
576
577    // ----------------- End of ExclusiveMeta -----------------
578
579}
580