Smart Contract
FRC20SemiNFT
A.d2abb5dbf5e08666.FRC20SemiNFT
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