Smart Contract

FRC20SemiNFT

A.d2abb5dbf5e08666.FRC20SemiNFT

Valid From

86,128,713

Deployed

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

Dependents

92 imports
1/**
2> Author: Fixes Lab <https://github.com/fixes-world/>
3
4# FRC20SemiNFT
5
6This is a semi-fungible token contract that wraps a FRC20FTShared.Change resource and provides additional functionalities to it.
7The contract is a Non-Fungible Token (NFT) that can be used to represent a semi-fungible token.
8It is a resource that represents a unique token that can be owned by a single account at a time.
9The contract provides the ability to wrap and unwrap a FRC20FTShared.Change resource, merge and split the token,
10and claim rewards from staked tokens.
11
12*/
13// Third party imports
14import NonFungibleToken from 0x1d7e57aa55817448
15import MetadataViews from 0x1d7e57aa55817448
16import ViewResolver from 0x1d7e57aa55817448
17import FungibleToken from 0xf233dcee88fe0abe
18import FlowToken from 0x1654653399040a61
19import Burner from 0xf233dcee88fe0abe
20// Fixes Import
21import Fixes from 0xd2abb5dbf5e08666
22import FRC20FTShared from 0xd2abb5dbf5e08666
23
24access(all) contract FRC20SemiNFT: NonFungibleToken, ViewResolver {
25
26    /* --- Events --- */
27
28    /// Total supply of FRC20SemiNFTs in existence
29    access(all) var totalSupply: UInt64
30
31    /// The event that is emitted when the contract is created
32    access(all) event ContractInitialized()
33
34    /// The event that is emitted when an NFT is wrapped
35    access(all) event Wrapped(id: UInt64, pool: Address, tick: String, balance: UFix64)
36
37    /// The event that is emitted when an NFT is unwrapped
38    access(all) event Unwrapped(id: UInt64, pool: Address, tick: String, balance: UFix64)
39
40    /// The event that is emitted when an NFT is merged
41    access(all) event Merged(id: UInt64, mergedId: UInt64, pool: Address, tick: String, mergedBalance: UFix64)
42
43    /// The event that is emitted when an NFT is splitted
44    access(all) event Split(id: UInt64, splittedId: UInt64, pool: Address, tick: String, splitBalance: UFix64)
45
46    /// The event that is emitted when the claiming record is updated
47    access(all) event ClaimingRecordUpdated(id: UInt64, tick: String, pool: Address, strategy: String, time: UInt64, globalYieldRate: UFix64, totalClaimedAmount: UFix64)
48
49    /* --- Variable, Enums and Structs --- */
50
51    /// Storage and Public Paths
52    access(all) let CollectionStoragePath: StoragePath
53    access(all) let CollectionPublicPath: PublicPath
54    access(all) let CollectionPrivatePath: PrivatePath
55
56    /* --- Interfaces & Resources --- */
57
58    /// Reward Claiming Record Struct, stored in SemiNFT
59    ///
60    access(all) struct RewardClaimRecord {
61        // The pool address
62        access(all)
63        let poolAddress: Address
64        // The reward strategy name
65        access(all)
66        let rewardTick: String
67        // The last claimed time
68        access(all)
69        var lastClaimedTime: UInt64
70        // The last global yield rate
71        access(all)
72        var lastGlobalYieldRate: UFix64
73        // The total claimed amount by this record
74        access(all)
75        var totalClaimedAmount: UFix64
76
77        init (
78            address: Address,
79            rewardTick: String,
80        ) {
81            self.poolAddress = address
82            self.rewardTick = rewardTick
83            self.lastClaimedTime = 0
84            self.lastGlobalYieldRate = 0.0
85            self.totalClaimedAmount = 0.0
86        }
87
88        /// Update the claiming record
89        ///
90        access(contract)
91        fun updateClaiming(currentGlobalYieldRate: UFix64, time: UInt64?) {
92            self.lastClaimedTime = time ?? UInt64(getCurrentBlock().timestamp)
93            self.lastGlobalYieldRate = currentGlobalYieldRate
94        }
95
96        access(contract)
97        fun addClaimedAmount(amount: UFix64) {
98            self.totalClaimedAmount = self.totalClaimedAmount.saturatingAdd(amount)
99        }
100
101        access(contract)
102        fun subtractClaimedAmount(amount: UFix64) {
103            self.totalClaimedAmount = self.totalClaimedAmount.saturatingSubtract(amount)
104        }
105    }
106
107    /// Public Interface of the FRC20SemiNFT
108    access(all) resource interface IFRC20SemiNFT {
109        /// The unique ID that each NFT has
110        access(all) let id: UInt64
111
112        access(all)
113        view fun getOriginalTick(): String
114
115        access(all)
116        view fun getTickerName(): String
117
118        access(all)
119        view fun isStakedTick(): Bool
120
121        access(all)
122        view fun isBackedByVault(): Bool
123
124        access(all)
125        view fun getVaultType(): Type?
126
127        access(all)
128        view fun getFromAddress(): Address
129
130        access(all)
131        view fun getBalance(): UFix64
132
133        access(all)
134        view fun getRewardStrategies(): [String]
135
136        access(all)
137        view fun getClaimingRecord(_ uniqueName: String): RewardClaimRecord?
138
139        access(all)
140        view fun buildUniqueName(_ addr: Address, _ strategy: String): String
141    }
142
143    /// The core resource that represents a Non Fungible Token.
144    /// New instances will be created using the NFTMinter resource
145    /// and stored in the Collection resource
146    ///
147    access(all) resource NFT: IFRC20SemiNFT, NonFungibleToken.NFT, Burner.Burnable {
148        /// The unique ID that each NFT has
149        access(all) let id: UInt64
150
151        /// Wrapped FRC20FTShared.Change
152        access(self)
153        var wrappedChange: @FRC20FTShared.Change
154        /// Claiming Records for staked FRC20FTShared.Change
155        /// Unique Name => Reward Claim Record
156        access(self)
157        let claimingRecords: {String: RewardClaimRecord}
158
159        init(
160            _ change: @FRC20FTShared.Change,
161            initialYieldRates: {String: UFix64}
162        ) {
163            pre {
164                change.isBackedByVault() == false: "Cannot wrap a vault backed FRC20 change"
165            }
166            self.id = self.uuid
167            self.wrappedChange <- change
168            self.claimingRecords = {}
169
170            // initialize the claiming records
171            if self.wrappedChange.isStakedTick() {
172                let strategies = initialYieldRates.keys
173                for name in strategies {
174                    let recordRef = self._borrowOrCreateClaimingRecord(
175                        poolAddress: self.wrappedChange.from,
176                        rewardTick: name
177                    )
178                    let yieldRate = initialYieldRates[name]!
179                    // update the initial record
180                    recordRef.updateClaiming(currentGlobalYieldRate: yieldRate, time: nil)
181                    log("Updated the initial claiming record for ".concat(name).concat(" with yield rate ").concat(yieldRate.toString()))
182                }
183            } // end if
184
185            FRC20SemiNFT.totalSupply = FRC20SemiNFT.totalSupply + 1
186
187            // emit the event
188            emit Wrapped(
189                id: self.id,
190                pool: self.wrappedChange.from,
191                tick: self.wrappedChange.tick,
192                balance: self.wrappedChange.getBalance()
193            )
194        }
195
196        access(contract)
197        fun burnCallback() {
198            pre {
199                self.wrappedChange.getBalance() == 0.0: "Cannot burn a non-empty NFT"
200            }
201            // decrease the total supply
202            FRC20SemiNFT.totalSupply = FRC20SemiNFT.totalSupply - 1
203        }
204
205        /// createEmptyCollection creates an empty Collection
206        /// and returns it to the caller so that they can own NFTs
207        /// @{NonFungibleToken.Collection}
208        access(all)
209        fun createEmptyCollection(): @{NonFungibleToken.Collection} {
210            return <- FRC20SemiNFT.createEmptyCollection(nftType: Type<@FRC20SemiNFT.NFT>())
211        }
212
213        /** ----- ViewResolver.Resolver ----- */
214
215        /// Function that returns all the Metadata Views implemented by a Non Fungible Token
216        ///
217        /// @return An array of Types defining the implemented views. This value will be used by
218        ///         developers to know which parameter to pass to the resolveView() method.
219        ///
220        access(all)
221        view fun getViews(): [Type] {
222            var nftViews: [Type] = [
223                // collection data
224                Type<MetadataViews.ExternalURL>(),
225                Type<MetadataViews.NFTCollectionData>(),
226                Type<MetadataViews.NFTCollectionDisplay>(),
227                // nft view data
228                Type<MetadataViews.Display>(),
229                Type<MetadataViews.Traits>(),
230                Type<MetadataViews.Royalties>()
231            ]
232            return nftViews
233        }
234
235        /// Function that resolves a metadata view for this token.
236        ///
237        /// @param view: The Type of the desired view.
238        /// @return A structure representing the requested view.
239        ///
240        access(all)
241        fun resolveView(_ view: Type): AnyStruct? {
242            let colViews = [
243                Type<MetadataViews.ExternalURL>(),
244                Type<MetadataViews.NFTCollectionData>(),
245                Type<MetadataViews.NFTCollectionDisplay>()
246            ]
247            if colViews.contains(view) {
248                return FRC20SemiNFT.resolveContractView(resourceType: Type<@FRC20SemiNFT.NFT>(), viewType: view)
249            } else {
250                switch view {
251                case Type<MetadataViews.Display>():
252                    let tick = self.getOriginalTick()
253                    let isStaked = self.isStakedTick()
254                    let fullName = (isStaked ? "Staked " : "").concat(tick)
255                    let balance = self.getBalance()
256
257                    let tickNameSizeIcon = 80 + (10 - tick.length > 0 ? 10 - tick.length : 0) * 12
258                    let tickNameSizeTitle = 245 + (10 - tick.length > 0 ? 10 - tick.length : 0) * 15
259                    var balanceStr = UInt64(balance).toString()
260                    if balanceStr.length > 8 {
261                        balanceStr = balanceStr.slice(from: 0, upTo: 7).concat("+")
262                    }
263                    var svgStr = "data:image/svg+xml;utf8,"
264                        .concat("%3Csvg%20width%3D'512'%20height%3D'512'%20viewBox%3D'0%200%202048%202048'%20style%3D'shape-rendering%3A%20crispedges%3B'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E")
265                        .concat("%3Cdefs%3E%3ClinearGradient%20gradientUnits%3D'userSpaceOnUse'%20x1%3D'0'%20y1%3D'-240'%20x2%3D'0'%20y2%3D'240'%20id%3D'gradient-0'%20gradientTransform%3D'matrix(0.908427%2C%20-0.41805%2C%200.320369%2C%200.696163%2C%20-855.753265%2C%20312.982639)'%3E%3Cstop%20offset%3D'0'%20style%3D'stop-color%3A%20rgb(244%2C%20246%2C%20246)%3B'%3E%3C%2Fstop%3E%3Cstop%20offset%3D'1'%20style%3D'stop-color%3A%20rgb(35%2C%20133%2C%2091)%3B'%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E")
266                        .concat("%3Cg%20transform%3D'matrix(1%2C%200%2C%200%2C%201%2C%200%2C%200)'%3E%3Cpath%20d%3D'M%20842%201104%20L%20944%20525%20L%20232%20930%20Z'%20style%3D'fill%3A%20rgb(74%2C145%2C122)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%20944%20525%20L%20199%20444%20L%20232%20930%20Z'%20style%3D'fill%3A%20rgb(56%2C130%2C100)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%20199%20444%20L%200%20576.1824817518248%20L%200%20700.2671009771987%20L%20232%20930%20Z'%20style%3D'fill%3A%20rgb(63%2C116%2C106)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%200%20700.2671009771987%20L%200%201020.8865979381443%20L%20232%20930%20Z'%20style%3D'fill%3A%20rgb(74%2C118%2C119)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%20232%20930%20L%20384%201616%20L%20842%201104%20Z'%20style%3D'fill%3A%20rgb(92%2C152%2C118)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%200%201020.8865979381443%20L%200%201236.2666666666667%20L%20384%201616%20L%20232%20930%20Z'%20style%3D'fill%3A%20rgb(95%2C137%2C121)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%20384%201616%20L%20825%201732%20L%20842%201104%20Z'%20style%3D'fill%3A%20rgb(120%2C171%2C120)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%201646%201114%20L%201540%20198%20L%20944%20525%20Z'%20style%3D'fill%3A%20rgb(109%2C164%2C119)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%201540%20198%20L%201292.8235294117646%200%20L%201121.4881516587677%200%20L%20944%20525%20Z'%20style%3D'fill%3A%20rgb(95%2C140%2C121)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%20944%20525%20L%20483.8322147651007%200%20L%20260.51807228915663%200%20L%20199%20444%20Z'%20style%3D'fill%3A%20rgb(55%2C115%2C96)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%201121.4881516587677%200%20L%20483.8322147651007%200%20L%20944%20525%20Z'%20style%3D'fill%3A%20rgb(70%2C118%2C114)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%200%20576.1824817518249%20L%20199%20444%20L%200%20190.04738562091504%20Z'%20style%3D'fill%3A%20rgb(60%2C95%2C100)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%201646%201114%20L%20944%20525%20L%20842%201104%20Z'%20style%3D'fill%3A%20rgb(104%2C164%2C143)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%201590%201475%20L%201646%201114%20L%20842%201104%20Z'%20style%3D'fill%3A%20rgb(143%2C186%2C139)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%20825%201732%20L%201590%201475%20L%20842%201104%20Z'%20style%3D'fill%3A%20rgb(138%2C183%2C141)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%200%201236.2666666666667%20L%200%201755.6363636363637%20L%20384%201616%20Z'%20style%3D'fill%3A%20rgb(117%2C144%2C115)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%20384%201616%20L%20327.74624373956595%202048%20L%20485.4472049689441%202048%20L%20825%201732%20Z'%20style%3D'fill%3A%20rgb(129%2C165%2C110)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%202048%201084.9638854296388%20L%202048%20639.9035294117647%20L%202024%20615%20L%201646%201114%20Z'%20style%3D'fill%3A%20rgb(156%2C179%2C136)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%201646%201114%20L%202024%20615%20L%201540%20198%20Z'%20style%3D'fill%3A%20rgb(130%2C171%2C117)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%202024%2018%20L%201989.1464968152866%200%20L%201645.7566765578636%200%20L%201540%20198%20Z'%20style%3D'fill%3A%20rgb(128%2C144%2C115)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%201292.8235294117646%200%20L%201540%20198%20L%201645.7566765578636%200%20Z'%20style%3D'fill%3A%20rgb(115%2C141%2C116)%3B'%3E%3C%2Fpath%3E%3Cpath%20%20d%3D'M%200%201755.6363636363635%20L%200%201907.5323741007194%20L%20139.79713603818618%202048%20L%20327.74624373956595%202048%20L%20384%201616%20Z'%20style%3D'fill%3A%20rgb(132%2C151%2C114)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%20825%201732%20L%201186.9410609037327%202048%20L%201453.8563968668407%202048%20L%201590%201475%20Z'%20style%3D'fill%3A%20rgb(169%2C194%2C133)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%20485.4472049689441%202048%20L%20921.2605042016806%202048%20L%20825%201732%20Z'%20style%3D'fill%3A%20rgb(145%2C171%2C122)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%20260.51807228915663%200%20L%200%200%20L%200%20190.04738562091507%20L%20199%20444%20Z'%20style%3D'fill%3A%20rgb(61%2C86%2C102)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%202024%20615%20L%202024%2018%20L%201540%20198%20Z'%20style%3D'fill%3A%20rgb(131%2C155%2C113)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%20139.79713603818618%202048%20L%200%201907.5323741007194%20L%200%202048%20Z'%20style%3D'fill%3A%20rgb(138%2C141%2C116)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%20921.2605042016806%202048%20L%201186.9410609037327%202048%20L%20825%201732%20Z'%20style%3D'fill%3A%20rgb(164%2C184%2C140)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%201590%201475%20L%202048%201663.6997929606625%20L%202048%201641.2131147540983%20L%201646%201114%20Z'%20style%3D'fill%3A%20rgb(177%2C201%2C129)%3B'%3E%3C%2Fpath%3E%3Cpath%20%20d%3D'M%201453.8563968668407%202048%20L%201634.358024691358%202048%20L%202048%201695.3157894736842%20L%202048%201663.6997929606625%20L%201590%201475%20Z'%20style%3D'fill%3A%20rgb(191%2C205%2C126)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%202048%2023.50859453993933%20L%202048%206.199445983379519%20L%202024%2018%20Z'%20style%3D'fill%3A%20rgb(138%2C141%2C116)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%202048%201641.2131147540983%20L%202048%201084.9638854296388%20L%201646%201114%20Z'%20style%3D'fill%3A%20rgb(178%2C194%2C134)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%201989.1464968152866%200%20L%202024%2018%20L%202027.8046647230321%200%20Z'%20style%3D'fill%3A%20rgb(136%2C141%2C116)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%202048%206.199445983379501%20L%202048%200%20L%202027.8046647230321%200%20L%202024%2018%20Z'%20style%3D'fill%3A%20rgb(138%2C141%2C116)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%202048%20606.0212335692619%20L%202048%2023.508594539939338%20L%202024%2018%20L%202024%20615%20Z'%20style%3D'fill%3A%20rgb(137%2C155%2C113)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%202048%20606.0212335692619%20L%202024%20615%20L%202048%20639.9035294117647%20Z'%20style%3D'fill%3A%20rgb(142%2C169%2C118)%3B'%3E%3C%2Fpath%3E%3Cpath%20d%3D'M%202048%202048%20L%202048%201695.3157894736842%20L%201634.358024691358%202048%20Z'%20style%3D'fill%3A%20rgb(207%2C203%2C127)%3B'%3E%3C%2Fpath%3E%3C%2Fg%3E")
267                        .concat("%3Cg%20transform%3D'matrix(1%2C%200%2C%200%2C%201%2C%201200%2C%20320)'%3E%3Cellipse%20style%3D'fill%3A%20rgb(149%2C%20225%2C%20192)%3B%20stroke-width%3A%201rem%3B%20paint-order%3A%20fill%3B%20stroke%3A%20url(%23gradient-0)%3B'%20ry%3D'240'%20rx%3D'240'%20cx%3D'-800'%20cy%3D'400'%3E%3C%2Fellipse%3E%3Ctext%20style%3D'dominant-baseline%3A%20middle%3B%20fill%3A%20rgb(80%2C%20213%2C%20155)%3B%20font-family%3A%20system-ui%2C%20sans-serif%3B%20text-anchor%3A%20middle%3B%20white-space%3A%20pre%3B%20font-size%3A%20420px%3B'%20fill-opacity%3D'0.2'%20y%3D'400'%20font-size%3D'420'%20x%3D'-800'%3E%F0%9D%94%89%3C%2Ftext%3E")
268                        .concat("%3Ctext%20style%3D'dominant-baseline%3A%20middle%3B%20fill%3A%20rgb(244%2C%20246%2C%20246)%3B%20font-family%3A%20system-ui%2C%20sans-serif%3B%20text-anchor%3A%20middle%3B%20font-style%3A%20italic%3B%20font-weight%3A%20700%3B%20white-space%3A%20pre%3B'%20x%3D'-800'%20y%3D'400'%20font-size%3D'").concat(tickNameSizeIcon.toString()).concat("'%3E").concat(tick).concat("%3C%2Ftext%3E%3C%2Fg%3E")
269                        .concat("%3Ctext%20style%3D'dominant-baseline%3A%20middle%3B%20fill%3A%20rgb(244%2C%20246%2C%20246)%3B%20font-family%3A%20system-ui%2C%20sans-serif%3B%20font-style%3A%20italic%3B%20font-weight%3A%20700%3B%20paint-order%3A%20fill%3B%20text-anchor%3A%20middle%3B%20white-space%3A%20pre%3B'%20x%3D'1300'%20y%3D'720'%20font-size%3D'").concat(tickNameSizeTitle.toString()).concat("'%3E").concat(tick).concat("%3C%2Ftext%3E")
270                        .concat("%3Ctext%20style%3D'dominant-baseline%3A%20middle%3B%20fill%3A%20rgb(244%2C%20246%2C%20246)%3B%20font-family%3A%20system-ui%2C%20sans-serif%3B%20font-style%3A%20italic%3B%20font-weight%3A%20700%3B%20paint-order%3A%20fill%3B%20text-anchor%3A%20middle%3B%20white-space%3A%20pre%3B'%20x%3D'1024'%20y%3D'1500'%20font-size%3D'360'%3E").concat(balanceStr).concat("%3C%2Ftext%3E")
271                    // add staked tag
272                    if isStaked {
273                        svgStr = svgStr.concat("%3Cg%20transform%3D'matrix(1%2C%200%2C%200%2C%201%2C%20128%2C%20192)'%3E%3Ctext%20style%3D'dominant-baseline%3A%20middle%3B%20fill%3A%20rgb(244%2C%20246%2C%20246)%3B%20font-family%3A%20system-ui%2C%20sans-serif%3B%20font-size%3A%20160px%3B%20font-style%3A%20italic%3B%20font-weight%3A%20700%3B%20letter-spacing%3A%204px%3B%20paint-order%3A%20fill%3B%20white-space%3A%20pre%3B'%20x%3D'0'%20y%3D'0'%3EStaked%20%F0%9D%94%89rc20%3C%2Ftext%3E%3Ctext%20style%3D'dominant-baseline%3A%20middle%3B%20fill%3A%20rgb(242%2C%20201%2C%20125)%3B%20font-family%3A%20system-ui%2C%20sans-serif%3B%20font-size%3A%20160px%3B%20font-style%3A%20italic%3B%20font-weight%3A%20700%3B%20letter-spacing%3A%204px%3B%20paint-order%3A%20fill%3B%20white-space%3A%20pre%3B'%20x%3D'-4'%20y%3D'-6'%3EStaked%20%F0%9D%94%89rc20%3C%2Ftext%3E%3C%2Fg%3E")
274                    }
275                    // end of svg
276                    svgStr = svgStr.concat("%3C%2Fsvg%3E")
277
278                    return MetadataViews.Display(
279                        name: "𝔉rc20 - ".concat(fullName),
280                        description: "This is a 𝔉rc20 Semi-NFT that contains a certain number of ".concat(fullName).concat(" tokens. \n")
281                            .concat("The balance of this Semi-NFT is ").concat(balance.toString()).concat(". \n"),
282                        thumbnail: MetadataViews.HTTPFile(url: svgStr),
283                    )
284                case Type<MetadataViews.Traits>():
285                    let traits = MetadataViews.Traits([])
286                    let changeRef: &FRC20FTShared.Change = self.borrowChange()
287                    traits.addTrait(MetadataViews.Trait(name: "originTick", value: changeRef.getOriginalTick(), displayType: nil, rarity: nil))
288                    traits.addTrait(MetadataViews.Trait(name: "tick", value: changeRef.tick, displayType: nil, rarity: nil))
289                    traits.addTrait(MetadataViews.Trait(name: "balance", value: changeRef.getBalance(), displayType: nil, rarity: nil))
290                    let isVault = changeRef.isBackedByVault()
291                    traits.addTrait(MetadataViews.Trait(name: "isFlowFT", value: isVault, displayType: nil, rarity: nil))
292                    if isVault {
293                        traits.addTrait(MetadataViews.Trait(name: "ftType", value: changeRef.getVaultType()!.identifier, displayType: nil, rarity: nil))
294                    }
295                    return traits
296                case Type<MetadataViews.Royalties>():
297                    // Royalties for FRC20SemiNFT is 5% to Deployer account
298                    let deployerAddr = FRC20SemiNFT.account.address
299                    let flowCap = getAccount(deployerAddr)
300                        .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
301                    return MetadataViews.Royalties([
302                        MetadataViews.Royalty(
303                            receiver: flowCap,
304                            cut: 0.05,
305                            description: "5% of the sale price of this NFT goes to the FIXeS platform account"
306                        )
307                    ])
308                }
309                return nil
310            }
311        }
312
313        /** ----- Semi-NFT Methods ----- */
314
315        access(all)
316        view fun getOriginalTick(): String {
317            return self.wrappedChange.getOriginalTick()
318        }
319
320        access(all)
321        view fun getTickerName(): String {
322            return self.wrappedChange.tick
323        }
324
325        access(all)
326        view fun isStakedTick(): Bool {
327            return self.wrappedChange.isStakedTick()
328        }
329
330        access(all)
331        view fun isBackedByVault(): Bool {
332            return self.wrappedChange.isBackedByVault()
333        }
334
335        access(all)
336        view fun getVaultType(): Type? {
337            return self.wrappedChange.getVaultType()
338        }
339
340        access(all)
341        view fun getFromAddress(): Address {
342            return self.wrappedChange.from
343        }
344
345        access(all)
346        view fun getBalance(): UFix64 {
347            return self.wrappedChange.getBalance()
348        }
349
350        /// Get the reward strategies
351        ///
352        access(all)
353        view fun getRewardStrategies(): [String] {
354            return self.claimingRecords.keys
355        }
356
357        /// Get the the claiming record(copy) by the unique name
358        ///
359        access(all)
360        view fun getClaimingRecord(_ uniqueName: String): RewardClaimRecord? {
361            pre {
362                self.isStakedTick(): "The tick must be a staked 𝔉rc20 token"
363            }
364            return self.claimingRecords[uniqueName]
365        }
366
367        /// Get the unique name of the reward strategy
368        ///
369        access(all)
370        view fun buildUniqueName(_ addr: Address, _ strategy: String): String {
371            let ref = self.borrowChange()
372            return addr.toString().concat("_").concat(ref.getOriginalTick()).concat("_").concat(strategy)
373        }
374
375        // Merge the NFT
376        //
377        access(NonFungibleToken.Update)
378        fun merge(_ other: @FRC20SemiNFT.NFT) {
379            pre {
380                self.getTickerName() == other.getTickerName(): "The tick must be the same"
381            }
382            // check tick and pool address
383            let otherChangeRef = other.borrowChange()
384            assert(
385                self.wrappedChange.from == otherChangeRef.from,
386                message: "The pool address must be the same"
387            )
388
389            let otherId = other.id
390            let otherBalance = otherChangeRef.getBalance()
391
392            // calculate the new balance
393            let newBalance = self.getBalance() + otherChangeRef.getBalance()
394
395            // merge the claiming records
396            if self.isStakedTick() && other.isStakedTick() {
397                let strategies = self.getRewardStrategies()
398                // merge each strategy
399                for name in strategies {
400                    if let otherRecordRef = other._borrowClaimingRecord(name) {
401                        // update claiming record
402                        let recordRef = self._borrowOrCreateClaimingRecord(
403                            poolAddress: otherRecordRef.poolAddress,
404                            rewardTick: otherRecordRef.rewardTick
405                        )
406                        // calculate the new claiming record
407                        var newGlobalYieldRate = 0.0
408                        // Weighted average
409                        if newBalance > 0.0 {
410                            newGlobalYieldRate = (recordRef.lastGlobalYieldRate * self.getBalance() + otherRecordRef.lastGlobalYieldRate * otherChangeRef.getBalance()) / newBalance
411                        }
412                        let newLastClaimedTime = recordRef.lastClaimedTime > otherRecordRef.lastClaimedTime ? recordRef.lastClaimedTime : otherRecordRef.lastClaimedTime
413
414                        // update the record
415                        self._updateClaimingRecord(
416                            poolAddress: otherRecordRef.poolAddress,
417                            rewardTick: otherRecordRef.rewardTick,
418                            currentGlobalYieldRate: newGlobalYieldRate,
419                            currentTime: newLastClaimedTime,
420                            amount: otherRecordRef.totalClaimedAmount,
421                            isSubtract: false
422                        )
423                    }
424                }
425            }
426
427            // unwrap and merge the wrapped change
428            let unwrappedChange <- FRC20SemiNFT.unwrapStakedFRC20(nftToUnwrap: <- other)
429            self.wrappedChange.merge(from: <- unwrappedChange)
430
431            assert(
432                newBalance == self.getBalance(),
433                message: "The merged balance must be correct"
434            )
435
436            // call hook method
437            self.onMetadataUpdated()
438
439            // emit event
440            emit Merged(
441                id: self.id,
442                mergedId: otherId,
443                pool: self.wrappedChange.from,
444                tick: self.wrappedChange.tick,
445                mergedBalance: otherBalance
446            )
447        }
448
449        // Split the NFT
450        access(NonFungibleToken.Update)
451        fun split(_ percent: UFix64): @FRC20SemiNFT.NFT {
452            pre {
453                percent > 0.0: "The split percent must be greater than 0"
454                percent < 1.0: "The split percent must be less than 1"
455            }
456            let oldBalance = self.getBalance()
457            // calculate the new balance
458            let splitBalance = oldBalance * percent
459
460            // split the wrapped change
461            let splitChange <- self.wrappedChange.withdrawAsChange(amount: splitBalance)
462
463            // create a new NFT
464            let newNFT <- create NFT(<- splitChange, initialYieldRates: {})
465
466            // check balance of the new NFT and the old NFT
467            assert(
468                self.getBalance() + newNFT.getBalance() == oldBalance,
469                message: "The splitted balance must be correct"
470            )
471
472            // split the claiming records
473            if self.isStakedTick() {
474                let strategies = self.getRewardStrategies()
475                // split each strategy
476                for name in strategies {
477                    if let recordRef = self._borrowClaimingRecord(name) {
478                        let splitAmount = recordRef.totalClaimedAmount * percent
479                        // update the record for current NFT
480                        self._updateClaimingRecord(
481                            poolAddress: recordRef.poolAddress,
482                            rewardTick: recordRef.rewardTick,
483                            currentGlobalYieldRate: recordRef.lastGlobalYieldRate,
484                            currentTime: recordRef.lastClaimedTime,
485                            amount: splitAmount,
486                            isSubtract: true
487                        )
488                        // update the record for new NFT
489                        newNFT._updateClaimingRecord(
490                            poolAddress: recordRef.poolAddress,
491                            rewardTick: recordRef.rewardTick,
492                            currentGlobalYieldRate: recordRef.lastGlobalYieldRate,
493                            currentTime: recordRef.lastClaimedTime,
494                            amount: splitAmount,
495                            isSubtract: false
496                        )
497                    }
498                }
499            }
500
501            // call hook method
502            self.onMetadataUpdated()
503
504
505            // emit event
506            emit Split(
507                id: self.id,
508                splittedId: newNFT.id,
509                pool: self.wrappedChange.from,
510                tick: self.wrappedChange.tick,
511                splitBalance: splitBalance
512            )
513
514            return <- newNFT
515        }
516
517        /** ---- Account level methods ---- */
518
519        /// Hook method: Update the claiming record
520        ///
521        access(account)
522        fun onClaimingReward(
523            poolAddress: Address,
524            rewardTick: String,
525            amount: UFix64,
526            currentGlobalYieldRate: UFix64
527        ) {
528            self._updateClaimingRecord(
529                poolAddress: poolAddress,
530                rewardTick: rewardTick,
531                currentGlobalYieldRate: currentGlobalYieldRate,
532                currentTime: nil,
533                amount: amount,
534                isSubtract: false
535            )
536
537            // call hook method
538            self.onMetadataUpdated()
539        }
540
541        /** Internal Method */
542
543        /// Borrow the wrapped FRC20FTShared.Change
544        ///
545        access(contract)
546        view fun borrowChange(): auth(FRC20FTShared.Write) &FRC20FTShared.Change {
547            return &self.wrappedChange
548        }
549
550        /// Invoke when the metadata is updated
551        access(contract)
552        fun onMetadataUpdated() {
553            let ref = &self as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT}
554            FRC20SemiNFT.emitNFTUpdated(ref)
555        }
556
557        /// Update the claiming record
558        ///
559        access(self)
560        fun _updateClaimingRecord(
561            poolAddress: Address,
562            rewardTick: String,
563            currentGlobalYieldRate: UFix64,
564            currentTime: UInt64?,
565            amount: UFix64,
566            isSubtract: Bool,
567        ) {
568            pre {
569                self.isStakedTick(): "The tick must be a staked 𝔉rc20 token"
570            }
571            // update claiming record
572            let recordRef = self._borrowOrCreateClaimingRecord(
573                poolAddress: poolAddress,
574                rewardTick: rewardTick
575            )
576            recordRef.updateClaiming(currentGlobalYieldRate: currentGlobalYieldRate, time: currentTime)
577
578            if isSubtract {
579                recordRef.subtractClaimedAmount(amount: amount)
580            } else {
581                recordRef.addClaimedAmount(amount: amount)
582            }
583
584            // emit event
585            emit ClaimingRecordUpdated(
586                id: self.id,
587                tick: self.getOriginalTick(),
588                pool: poolAddress,
589                strategy: rewardTick,
590                time: recordRef.lastClaimedTime,
591                globalYieldRate: recordRef.lastGlobalYieldRate,
592                totalClaimedAmount: recordRef.totalClaimedAmount
593            )
594        }
595
596        /// Borrow or create the claiming record(writeable reference) by the unique name
597        ///
598        access(self)
599        fun _borrowOrCreateClaimingRecord(
600            poolAddress: Address,
601            rewardTick: String
602        ): &RewardClaimRecord {
603            let uniqueName = self.buildUniqueName(poolAddress, rewardTick)
604            if self.claimingRecords[uniqueName] == nil {
605                self.claimingRecords[uniqueName] = RewardClaimRecord(
606                    address: self.wrappedChange.from,
607                    rewardTick: rewardTick,
608                )
609            }
610            return self._borrowClaimingRecord(uniqueName) ?? panic("Claiming record must exist")
611        }
612
613        /// Borrow the claiming record(writeable reference) by the unique name
614        ///
615        access(self)
616        view fun _borrowClaimingRecord(_ uniqueName: String): &RewardClaimRecord? {
617            return &self.claimingRecords[uniqueName]
618        }
619    }
620
621    /// Defines the public methods that are particular to this NFT contract collection
622    ///
623    access(all)
624    resource interface FRC20SemiNFTCollectionPublic: NonFungibleToken.CollectionPublic {
625        access(all)
626        view fun getIDsByTick(tick: String): [UInt64]
627        /// Gets the staked balance of the tick
628        access(all)
629        view fun getStakedBalance(tick: String): UFix64
630        /// Borrow the FRC20SemiNFT reference by the ID
631        access(all)
632        fun borrowFRC20SemiNFTPublic(id: UInt64): &FRC20SemiNFT.NFT? {
633            post {
634                (result == nil) || (result?.id == id):
635                    "Cannot borrow FRC20SemiNFT reference: the ID of the returned reference is incorrect"
636            }
637        }
638    }
639
640    /// Defines the private methods that are particular to this NFT contract collection
641    ///
642    access(all) resource interface FRC20SemiNFTBorrowable {
643        /** ----- Specific Methods For SemiNFT ----- */
644        access(NonFungibleToken.Update)
645        fun borrowFRC20SemiNFT(id: UInt64): auth(NonFungibleToken.Update) &FRC20SemiNFT.NFT? {
646            post {
647                (result == nil) || (result?.id == id):
648                    "Cannot borrow FRC20SemiNFT reference: the ID of the returned reference is incorrect"
649            }
650        }
651    }
652
653    /// The resource that will be holding the NFTs inside any account.
654    /// In order to be able to manage NFTs any account will need to create
655    /// an empty collection first
656    ///
657    access(all) resource Collection: FRC20SemiNFTCollectionPublic, FRC20SemiNFTBorrowable, NonFungibleToken.Collection {
658        // dictionary of NFT conforming tokens
659        // NFT is a resource type with an `UInt64` ID field
660        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
661        // Tick => NFT ID Array
662        access(self)
663        let tickIDsMapping: {String: [UInt64]}
664
665        init () {
666            self.ownedNFTs <- {}
667            self.tickIDsMapping = {}
668        }
669
670        /// createEmptyCollection creates an empty Collection of the same type
671        /// and returns it to the caller
672        /// @return A an empty collection of the same type
673        access(all)
674        fun createEmptyCollection(): @{NonFungibleToken.Collection} {
675            return <- FRC20SemiNFT.createEmptyCollection(nftType: Type<@FRC20SemiNFT.NFT>())
676        }
677
678        // ---------- Implement NonFungibleToken.Receiver ----------
679
680        /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
681        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
682            let supportedTypes: {Type: Bool} = {}
683            supportedTypes[Type<@FRC20SemiNFT.NFT>()] = true
684            return supportedTypes
685        }
686
687        /// Returns whether or not the given type is accepted by the collection
688        /// A collection that can accept any type should just return true by default
689        access(all) view fun isSupportedNFTType(type: Type): Bool {
690            return type == Type<@FRC20SemiNFT.NFT>()
691        }
692
693        // ---------- Implement NonFungibleToken.Collection ----------
694
695        /// Removes an NFT from the collection and moves it to the caller
696        ///
697        /// @param withdrawID: The ID of the NFT that wants to be withdrawn
698        /// @return The NFT resource that has been taken out of the collection
699        ///
700        access(NonFungibleToken.Withdraw)
701        fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
702            let token <- (self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")) as! @FRC20SemiNFT.NFT
703
704            // remove from tickIDsMapping
705            let tick = token.getOriginalTick()
706            let tickIDs = self._borrowTickIDs(tick) ?? panic("Tick IDs must exist")
707            let index = tickIDs.firstIndex(of: token.id) ?? panic("Token ID must exist in tickIDs")
708            tickIDs.remove(at: index)
709
710            return <-token
711        }
712
713        /// Adds an NFT to the collections dictionary and adds the ID to the id array
714        ///
715        /// @param token: The NFT resource to be included in the collection
716        ///
717        access(all)
718        fun deposit(token: @{NonFungibleToken.NFT}) {
719            let token <- token as! @FRC20SemiNFT.NFT
720
721            let id: UInt64 = token.id
722            let tick = token.getOriginalTick()
723
724            // add the new token to the dictionary which removes the old one
725            let oldToken <- self.ownedNFTs[id] <- token
726
727            // add to tickIDsMapping
728            let tickIDs = self._borrowOrCreateTickIDs(tick)
729            tickIDs.append(id)
730
731            Burner.burn(<- oldToken)
732        }
733
734        /// Helper method for getting the collection IDs
735        ///
736        /// @return An array containing the IDs of the NFTs in the collection
737        ///
738        access(all)
739        view fun getIDs(): [UInt64] {
740            return self.ownedNFTs.keys
741        }
742
743        /// Gets the amount of NFTs stored in the collection
744        access(all)
745        view fun getLength(): Int {
746            return self.ownedNFTs.length
747        }
748
749        /// Gets a reference to an NFT in the collection so that
750        /// the caller can read its metadata and call its methods
751        ///
752        /// @param id: The ID of the wanted NFT
753        /// @return A reference to the wanted NFT resource
754        ///
755        access(all)
756        view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
757            return &self.ownedNFTs[id]
758        }
759
760        /// Gets an array of NFT IDs in the collection by the tick
761        ///
762        access(all)
763        view fun getIDsByTick(tick: String): [UInt64] {
764            if let ids = self.tickIDsMapping[tick] {
765                return ids
766            }
767            return []
768        }
769
770        /// Gets the staked balance of the tick
771        ///
772        access(all)
773        view fun getStakedBalance(tick: String): UFix64 {
774            let tickIds = self.getIDsByTick(tick: tick)
775            if tickIds.length > 0 {
776                var totalBalance = 0.0
777                for id in tickIds {
778                    if let nft = self.borrowFRC20SemiNFT(id: id) {
779                        if nft.getOriginalTick() != tick {
780                            continue
781                        }
782                        if !nft.isStakedTick() {
783                            continue
784                        }
785                        totalBalance = totalBalance + nft.getBalance()
786                    }
787                }
788                return totalBalance
789            }
790            return 0.0
791        }
792
793        /// Gets a reference to an NFT in the collection with the public interface
794        ///
795        access(all)
796        view fun borrowFRC20SemiNFTPublic(id: UInt64): &FRC20SemiNFT.NFT? {
797            return self.borrowFRC20SemiNFT(id: id)
798        }
799
800        /// Gets a reference to an NFT in the collection for detailed operations
801        ///
802        access(NonFungibleToken.Update)
803        view fun borrowFRC20SemiNFT(id: UInt64): auth(NonFungibleToken.Update) &FRC20SemiNFT.NFT? {
804            if let ref = &self.ownedNFTs[id] as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT}? {
805                // Create an authorized reference to allow downcasting
806                return ref as? auth(NonFungibleToken.Update) &FRC20SemiNFT.NFT
807            }
808            return nil
809        }
810
811        /** ----- ViewResolver ----- */
812
813        /// Gets a reference to the NFT only conforming to the `{ViewResolver.Resolver}`
814        /// interface so that the caller can retrieve the views that the NFT
815        /// is implementing and resolve them
816        ///
817        /// @param id: The ID of the wanted NFT
818        /// @return The resource reference conforming to the Resolver interface
819        ///
820        access(all)
821        view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
822            if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
823                return nft as &{ViewResolver.Resolver}
824            }
825            return nil
826        }
827
828        /** ------ Internal Methods ------ */
829
830        /// Borrow the tick IDs mapping
831        ///
832        access(self)
833        view fun _borrowTickIDs(_ tick: String): auth(Mutate) &[UInt64]? {
834            return &self.tickIDsMapping[tick]
835        }
836
837        /// Borrow or create the tick IDs mapping
838        ///
839        access(self)
840        fun _borrowOrCreateTickIDs(_ tick: String): auth(Mutate) &[UInt64] {
841            if self.tickIDsMapping[tick] == nil {
842                self.tickIDsMapping[tick] = []
843            }
844            return self._borrowTickIDs(tick) ?? panic("Tick IDs must exist")
845        }
846    }
847
848    /// Allows anyone to create a new empty collection
849    ///
850    /// @return The new Collection resource
851    ///
852    access(all)
853    fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
854        return <- create Collection()
855    }
856
857    /// Mints a new NFT with a new ID and deposit it in the
858    /// recipients collection using their collection reference
859    /// -- recipient, the collection of FRC20SemiNFT
860    ///
861    access(account)
862    fun wrap(
863        recipient: &FRC20SemiNFT.Collection,
864        change: @FRC20FTShared.Change,
865        initialYieldRates: {String: UFix64}
866    ): UInt64 {
867        pre {
868            change.isStakedTick(): "The tick must be a staked 𝔉rc20 token"
869            change.isBackedByVault() == false: "Cannot wrap a vault backed 𝔉rc20 change"
870        }
871        let poolAddress = change.from
872        let tick = change.tick
873        let balance = change.getBalance()
874
875        // create a new NFT
876        // Set the initial Global yield rate to the current one, so that users cannot obtain previous profits.
877        var newNFT <- create NFT(<- change, initialYieldRates: initialYieldRates)
878        let nftId = newNFT.id
879        // deposit it in the recipient's account using their reference
880        recipient.deposit(token: <-newNFT)
881        return nftId
882    }
883
884    /// Unwraps the SemiNFT and returns the wrapped FRC20FTShared.Change
885    /// Account level method
886    ///
887    access(account)
888    fun unwrapStakedFRC20(
889        nftToUnwrap: @FRC20SemiNFT.NFT,
890    ): @FRC20FTShared.Change {
891        pre {
892            nftToUnwrap.isStakedTick() == true: "Cannot unwrap a non-staked 𝔉rc20 token by this method."
893        }
894        return <- self._unwrap(<- nftToUnwrap)
895    }
896
897    /// Unwraps the SemiNFT and returns the wrapped FRC20FTShared.Change
898    /// Contract level method
899    ///
900    access(contract)
901    fun _unwrap(
902        _ nftToUnwrap: @FRC20SemiNFT.NFT,
903    ): @FRC20FTShared.Change {
904        let nftId = nftToUnwrap.id
905
906        let changeRef = nftToUnwrap.borrowChange()
907        let poolAddr = changeRef.from
908        let tick = changeRef.tick
909        let allBalance = changeRef.getBalance()
910        // withdraw all balance from the wrapped change
911        let newChange <- changeRef.withdrawAsChange(amount: allBalance)
912
913        // Burner.burn(the FRC20SemiNFT
914        Burner.burn(<- nftToUnwrap)
915
916        // emit the event
917        emit Unwrapped(
918            id: nftId,
919            pool: poolAddr,
920            tick: tick,
921            balance: allBalance
922        )
923        // return the inscription
924        return <- newChange
925    }
926
927
928    /// Function that resolves a metadata view for this contract.
929    ///
930    /// @param view: The Type of the desired view.
931    /// @return A structure representing the requested view.
932    ///
933    access(all)
934    fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
935        switch viewType {
936            case Type<MetadataViews.ExternalURL>():
937                return MetadataViews.ExternalURL("https://fixes.world/")
938            case Type<MetadataViews.NFTCollectionData>():
939                return MetadataViews.NFTCollectionData(
940                    storagePath: FRC20SemiNFT.CollectionStoragePath,
941                    publicPath: FRC20SemiNFT.CollectionPublicPath,
942                    publicCollection: Type<&FRC20SemiNFT.Collection>(),
943                    publicLinkedType: Type<&FRC20SemiNFT.Collection>(),
944                    createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
945                        return <- FRC20SemiNFT.createEmptyCollection(nftType: Type<@FRC20SemiNFT.NFT>())
946                    })
947                )
948            case Type<MetadataViews.NFTCollectionDisplay>():
949                let bannerMedia = MetadataViews.Media(
950                    file: MetadataViews.HTTPFile(
951                        url: "https://i.imgur.com/4DOuqFf.jpeg"
952                    ),
953                    mediaType: "image/jpeg"
954                )
955                let squareMedia = MetadataViews.Media(
956                    file: MetadataViews.HTTPFile(
957                        url: "https://i.imgur.com/hs3U5CY.png"
958                    ),
959                    mediaType: "image/png"
960                )
961                let twitter = MetadataViews.ExternalURL("https://twitter.com/fixesWorld")
962                return MetadataViews.NFTCollectionDisplay(
963                    name: "FIXeS 𝔉rc20 Semi-NFT",
964                    description: "This collection is used to wrap 𝔉rc20 token as semi-NFTs.",
965                    externalURL: MetadataViews.ExternalURL("https://fixes.world/"),
966                    squareImage: squareMedia,
967                    bannerImage: bannerMedia,
968                    socials: {
969                        "linktree": MetadataViews.ExternalURL("https://linktr.ee/fixes.world"),
970                        "github": MetadataViews.ExternalURL("https://github.com/fixes-world"),
971                        "twitter": twitter,
972                        "x": twitter
973                    }
974                )
975        }
976        return nil
977    }
978
979    /// Function that returns all the Metadata Views implemented by a Non Fungible Token
980    ///
981    /// @return An array of Types defining the implemented views. This value will be used by
982    ///         developers to know which parameter to pass to the resolveView() method.
983    ///
984    access(all)
985    view fun getContractViews(resourceType: Type?): [Type] {
986        return [
987            Type<MetadataViews.ExternalURL>(),
988            Type<MetadataViews.NFTCollectionData>(),
989            Type<MetadataViews.NFTCollectionDisplay>()
990        ]
991    }
992
993    init() {
994        // Initialize the total supply
995        self.totalSupply = 0
996
997        // Set the named paths
998        let identifier = "FRC20SemiNFT_".concat(self.account.address.toString())
999        self.CollectionStoragePath = StoragePath(identifier: identifier.concat("collection"))!
1000        self.CollectionPublicPath = PublicPath(identifier: identifier.concat("collection"))!
1001        self.CollectionPrivatePath = PrivatePath(identifier: identifier.concat("collection"))!
1002
1003        // Create a Collection resource and save it to storage
1004        let collection <- create Collection()
1005        self.account.storage.save(<-collection, to: self.CollectionStoragePath)
1006
1007        // create a public capability for the collection
1008        self.account.capabilities.publish(
1009            self.account.capabilities.storage.issue<&FRC20SemiNFT.Collection>(self.CollectionStoragePath),
1010            at: self.CollectionPublicPath
1011        )
1012
1013        emit ContractInitialized()
1014    }
1015}
1016