Smart Contract
FRC20Converter
A.d2abb5dbf5e08666.FRC20Converter
1/**
2> Author: Fixes Lab <https://github.com/fixes-world/>
3
4# FRC20 Token Converter
5
6This contract is used to convert FRC20 tokens to Fungible Tokens and vice versa.
7
8*/
9import FungibleToken from 0xf233dcee88fe0abe
10import FlowToken from 0x1654653399040a61
11// Fixes Imports
12import Fixes from 0xd2abb5dbf5e08666
13import FixesInscriptionFactory from 0xd2abb5dbf5e08666
14import FixesFungibleTokenInterface from 0xd2abb5dbf5e08666
15import FRC20FTShared from 0xd2abb5dbf5e08666
16import FRC20Indexer from 0xd2abb5dbf5e08666
17
18access(all) contract FRC20Converter {
19 /* --- Events --- */
20
21 /// Event emitted when the contract is initialized
22 access(all) event ContractInitialized()
23 /// Event emitted when the FRC20 Converter is created
24 access(all) event FRC20ConverterCreated(
25 _ ticker: String,
26 tokenType: Type
27 )
28 /// Event emitted when the FRC20 tokens are burned
29 access(all) event FRC20TokenBurned(
30 _ ticker: String,
31 amount: UFix64,
32 flowRefund: UFix64,
33 receiver: Type
34 )
35
36 /** --- Interfaces & Resources --- */
37
38 /// Interface for the FRC20 Treasury Receiver
39 ///
40 access(all) resource interface FRC20TreasuryReceiver {
41 access(all)
42 fun depositFlowToken(_ token: @{FungibleToken.Vault}) {
43 pre {
44 token.isInstance(Type<@FlowToken.Vault>()): "Invalid token type"
45 }
46 }
47 }
48
49 /// Interface for the FRC20 Burner
50 ///
51 access(all) resource interface IFRC20Burner {
52 /// check if the ticker is burnable
53 access(all)
54 view fun isTickerBurnable(_ tick: String): Bool
55 /// all the unburnable ticks
56 access(all)
57 view fun getUnburnableTicks(): [String]
58 /// burn and send the tokens
59 access(all)
60 fun burnAndSend(_ ins: auth(Fixes.Extractable) &Fixes.Inscription, recipient: &{FRC20TreasuryReceiver})
61 }
62
63 /// System Burner
64 ///
65 access(all) resource SystemBurner: IFRC20Burner {
66 /// all the unburnable ticks
67 ///
68 access(all)
69 view fun getUnburnableTicks(): [String] {
70 return [
71 FRC20FTShared.getPlatformStakingTickerName(),
72 FRC20FTShared.getPlatformUtilityTickerName()
73 ]
74 }
75
76 /// check if the ticker is burnable
77 ///
78 access(all)
79 view fun isTickerBurnable(_ tick: String): Bool {
80 return !self.getUnburnableTicks().contains(tick)
81 }
82
83 /// burn and send the tokens
84 ///
85 access(all)
86 fun burnAndSend(_ ins: auth(Fixes.Extractable) &Fixes.Inscription, recipient: &{FRC20TreasuryReceiver}) {
87 pre {
88 ins.isExtractable(): "Inscription is not extractable"
89 }
90 post {
91 ins.isExtracted(): "Inscription is not extracted"
92 }
93 let meta = FixesInscriptionFactory.parseMetadata(ins.borrowData())
94 let tick = meta["tick"]?.toLower() ?? panic("The token tick is not found")
95 assert(
96 self.isTickerBurnable(tick),
97 message: "The token is not burnable by the system burner."
98 )
99 let amt = UFix64.fromString(meta["amt"]!) ?? panic("The amount is not a valid UFix64")
100 // burn the tokens
101 let frc20Indexer = FRC20Indexer.getIndexer()
102 let flowVault <- frc20Indexer.burnFromTreasury(ins: ins)
103 let flowRefundAmount = flowVault.balance
104 // deposit the flow tokens
105 recipient.depositFlowToken(<- flowVault)
106
107 // emit the event
108 emit FRC20TokenBurned(
109 tick,
110 amount: amt,
111 flowRefund: flowRefundAmount,
112 receiver: recipient.getType()
113 )
114 }
115 }
116
117 /// Interface for the FRC20 Converter
118 ///
119 access(all) resource interface IConverter {
120 /// get the ticker name
121 access(all)
122 view fun getTickerName(): String
123 /// get the token type
124 access(all)
125 view fun getTokenType(): Type
126 /// convert the frc20 to the frc20 FT
127 access(all)
128 fun convertFromIndexer(ins: auth(Fixes.Extractable) &Fixes.Inscription, recipient: &{FungibleToken.Receiver}) {
129 pre {
130 ins.isExtractable(): "Inscription is not extractable"
131 recipient.getSupportedVaultTypes()[self.getTokenType()] == true: "Recipient does not support the token type"
132 }
133 post {
134 ins.isExtracted(): "Inscription is not extracted"
135 }
136 }
137 /// convert the frc20 FT to the frc20
138 access(all)
139 fun convertBackToIndexer(ins: auth(Fixes.Extractable) &Fixes.Inscription, vault: @{FungibleToken.Vault}) {
140 pre {
141 ins.isExtractable(): "Inscription is not extractable"
142 vault.getType() == self.getTokenType(): "Vault does not support the token type"
143 }
144 post {
145 ins.isExtracted(): "Inscription is not extracted"
146 }
147 }
148 }
149
150 /// The general FRC20 Converter for arbitrary FRC20 Token
151 ///
152 access(all) resource FTConverter: IConverter {
153 access(self)
154 let adminCap: Capability<auth(FixesFungibleTokenInterface.Manage) &{FixesFungibleTokenInterface.IAdminWritable}>
155
156 init(
157 _ cap: Capability<auth(FixesFungibleTokenInterface.Manage) &{FixesFungibleTokenInterface.IAdminWritable}>
158 ) {
159 self.adminCap = cap
160
161 // emit the event
162 let minter = self.borrowMinterRef()
163 emit FRC20ConverterCreated(
164 minter.getSymbol(),
165 tokenType: minter.getTokenType()
166 )
167 }
168
169 // ---- IConverter ----
170
171 /// get the ticker name
172 ///
173 access(all)
174 view fun getTickerName(): String {
175 let minter = self.borrowMinterRef()
176 return minter.getSymbol()
177 }
178
179 /// get the token type
180 access(all)
181 view fun getTokenType(): Type {
182 let minter = self.borrowMinterRef()
183 return minter.getTokenType()
184 }
185
186 /// convert the frc20 to the frc20 FT
187 ///
188 access(all)
189 fun convertFromIndexer(ins: auth(Fixes.Extractable) &Fixes.Inscription, recipient: &{FungibleToken.Receiver}) {
190 // borrow the minter reference
191 let minter = self.borrowMinterRef()
192
193 // parse the metadata
194 let meta = FixesInscriptionFactory.parseMetadata(ins.borrowData())
195 let tick = meta["tick"]?.toLower() ?? panic("The token tick is not found")
196 let minterTicker = minter.getSymbol()
197 assert(
198 minterTicker == tick,
199 message: "The token tick does not match the converter ticker"
200 )
201
202 let amt = UFix64.fromString(meta["amt"]!) ?? panic("The amount is not a valid UFix64")
203 let minterType = minter.getTokenType()
204 // convert the tokens
205 let emptyVault <- minter.mintTokens(amount: 0.0)
206 let convertedVault <- minter.initializeVaultByInscription(vault: <- emptyVault, ins: ins)
207 assert(
208 convertedVault.getType() == minterType,
209 message: "The converted vault type does not match the minter type"
210 )
211 assert(
212 amt == convertedVault.balance,
213 message: "The converted vault balance does not match the inscription amount"
214 )
215
216 recipient.deposit(from: <- convertedVault)
217 }
218
219 /// convert the frc20 FT to the frc20
220 ///
221 access(all)
222 fun convertBackToIndexer(ins: auth(Fixes.Extractable) &Fixes.Inscription, vault: @{FungibleToken.Vault}) {
223 // borrow the minter reference
224 let minter = self.borrowMinterRef()
225
226 // parse the metadata
227 let meta = FixesInscriptionFactory.parseMetadata(ins.borrowData())
228 let tick = meta["tick"]?.toLower() ?? panic("The token tick is not found")
229 let minterTicker = minter.getSymbol()
230 assert(
231 minterTicker == tick,
232 message: "The token tick does not match the converter ticker"
233 )
234 // burn the tokens
235 minter.burnTokenWithInscription(vault: <- vault, ins: ins)
236 }
237
238 // ----- Internal Methods ----
239
240 /// Borrow the admin reference
241 ///
242 access(self)
243 view fun borrowAdminRef(): auth(FixesFungibleTokenInterface.Manage) &{FixesFungibleTokenInterface.IAdminWritable} {
244 return self.adminCap.borrow()
245 ?? panic("Could not borrow the admin reference")
246 }
247
248 /// Borrow the super minter reference
249 ///
250 access(self)
251 view fun borrowMinterRef(): auth(FixesFungibleTokenInterface.Manage) &{FixesFungibleTokenInterface.IMinter} {
252 return self.borrowAdminRef().borrowSuperMinter()
253 }
254 }
255
256 /** ------- Public Methods ---- */
257
258 /// Borrow the system burner reference
259 ///
260 access(all)
261 view fun borrowSystemBurner(): &SystemBurner {
262 return getAccount(self.account.address)
263 .capabilities.get<&SystemBurner>(self.getSystemBurnerPublicPath())
264 .borrow()
265 ?? panic("Could not borrow the SystemBurner reference")
266 }
267
268 /// Create the FRC20 Converter
269 ///
270 access(all)
271 fun createConverter(
272 _ privCap: Capability<auth(FixesFungibleTokenInterface.Manage) &{FixesFungibleTokenInterface.IAdminWritable}>
273 ): @FTConverter {
274 return <- create FTConverter(privCap)
275 }
276
277 /// Borrow the FRC20 Converter
278 ///
279 access(all)
280 view fun borrowConverter(_ addr: Address): &FTConverter? {
281 return getAccount(addr)
282 .capabilities.get<&FTConverter>(self.getFTConverterPublicPath())
283 .borrow()
284 }
285
286 /// Get the prefix for the storage paths
287 ///
288 access(all)
289 view fun getPathPrefix(): String {
290 return "FRC20Converter_".concat(self.account.address.toString()).concat("_")
291 }
292
293 /// Get the system burner storage path
294 ///
295 access(all)
296 view fun getSystemBurnerStoragePath(): StoragePath {
297 let prefix = self.getPathPrefix()
298 return StoragePath(identifier: prefix.concat("SystemBurner"))!
299 }
300
301 /// Get the system burner public path
302 ///
303 access(all)
304 view fun getSystemBurnerPublicPath(): PublicPath {
305 let prefix = self.getPathPrefix()
306 return PublicPath(identifier: prefix.concat("SystemBurner"))!
307 }
308
309 /// Get the ft converter storage path
310 ///
311 access(all)
312 view fun getFTConverterStoragePath(): StoragePath {
313 let prefix = self.getPathPrefix()
314 return StoragePath(identifier: prefix.concat("FTConverter"))!
315 }
316
317 /// Get the ft converter public path
318 ///
319 access(all)
320 view fun getFTConverterPublicPath(): PublicPath {
321 let prefix = self.getPathPrefix()
322 return PublicPath(identifier: prefix.concat("FTConverter"))!
323 }
324
325 init() {
326 let storagePath = self.getSystemBurnerStoragePath()
327 self.account.storage.save(<- create SystemBurner(), to: storagePath)
328 // publish the public path
329 self.account.capabilities.publish(
330 self.account.capabilities.storage.issue<&SystemBurner>(storagePath),
331 at: self.getSystemBurnerPublicPath()
332 )
333
334 emit ContractInitialized()
335 }
336}
337