Smart Contract
EVMTokenList
A.15a918087ab12d86.EVMTokenList
1/**
2> Author: Fixes World <https://fixes.world/>
3
4# EVMTokenList - A on-chain list of all the EVM compatible tokens on the Flow blockchain.
5
6This contract is a list of all the bridged ERC20 / ERC721 tokens for Flow(EVM) on the Flow blockchain.
7
8*/
9import FungibleToken from 0xf233dcee88fe0abe
10import NonFungibleToken from 0x1d7e57aa55817448
11import MetadataViews from 0x1d7e57aa55817448
12import ViewResolver from 0x1d7e57aa55817448
13import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
14// EVM and VMBridge related contracts
15import EVM from 0xe467b9dd11fa00df
16import FlowEVMBridge from 0x1e4aa0b87d10b141
17import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141
18import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141
19// TokenList contract
20import TokenList from 0x15a918087ab12d86
21import NFTList from 0x15a918087ab12d86
22import FTViewUtils from 0x15a918087ab12d86
23import NFTViewUtils from 0x15a918087ab12d86
24
25/// The EVMTokenList contract
26/// The List only contains the basic EVM Address information for the registered tokens.
27///
28access(all) contract EVMTokenList {
29 /* --- Entitlement --- */
30
31 access(all) entitlement SuperAdmin
32
33 /* --- Events --- */
34
35 /// Event emitted when the contract is initialized
36 access(all) event ContractInitialized()
37
38 /// Event emitted when a new EVM compatible token is registered
39 access(all) event EVMBridgedAssetRegistered(
40 _ evmAddress: String,
41 _ isERC721: Bool,
42 _ bridgedAssetAddress: Address,
43 _ bridgedAssetContractName: String,
44 )
45 /// Event emitted when a EVM compatible token is removed
46 access(all) event EVMBridgedAssetRemoved(
47 _ evmAddress: String,
48 _ isERC721: Bool,
49 _ bridgedAssetAddress: Address,
50 _ bridgedAssetContractName: String,
51 )
52 /* --- Variable, Enums and Structs --- */
53
54 access(all) let registryStoragePath: StoragePath
55 access(all) let registryPublicPath: PublicPath
56
57 /* --- Interfaces & Resources --- */
58
59 /// Interface for the Token List Viewer
60 ///
61 access(all) resource interface IListViewer {
62 // --- Read functions ---
63
64 /// Check if the EVM Address is registered
65 access(all)
66 view fun isEVMAddressRegistered(_ evmContractAddressHex: String): Bool
67
68 // --- ERC20 functions ---
69
70 /// Get the total number of registered ERC20 tokens
71 access(all)
72 view fun getERC20Amount(): Int
73 /// Get the ERC20 addresses with pagination
74 /// The method will copy the addresses to the result array
75 access(all)
76 fun getERC20Addresses(_ page: Int, _ size: Int): [EVM.EVMAddress]
77 /// Get the ERC20 addresses with pagination
78 /// The method will copy the addresses to the result array
79 access(all)
80 fun getERC20AddressesHex(_ page: Int, _ size: Int): [String]
81 /// Get the ERC20 addresses by for each
82 /// The method will call the function for each address
83 access(all)
84 fun forEachERC20Address(_ f: fun (EVM.EVMAddress): Bool)
85 /// Get the ERC20 addresses(String) by for each
86 access(all)
87 fun forEachERC20AddressString(_ f: fun (String): Bool)
88 /// Borrow the Bridged Token's TokenList Entry
89 access(all)
90 view fun borrowFungibleTokenEntry(_ evmContractAddressHex: String): &{TokenList.FTEntryInterface}?
91
92 // --- ERC721 functions ---
93
94 /// Get the total number of registered ERC721 tokens
95 access(all)
96 view fun getERC721Amount(): Int
97 /// Get the ERC721 addresses with pagination
98 /// The method will copy the addresses to the result array
99 access(all)
100 fun getERC721Addresses(_ page: Int, _ size: Int): [EVM.EVMAddress]
101 /// Get the ERC721 addresses with pagination
102 /// The method will copy the addresses to the result array
103 access(all)
104 fun getERC721AddressesHex(_ page: Int, _ size: Int): [String]
105 /// Get the ERC721 addresses by for each
106 /// The method will call the function for each address
107 access(all)
108 fun forEachERC721Address(_ f: fun (EVM.EVMAddress): Bool)
109 /// Get the ERC721 addresses(String) by for each
110 access(all)
111 fun forEachERC721AddressString(_ f: fun (String): Bool)
112 /// Borrow the Bridged Token's TokenList Entry
113 access(all)
114 view fun borrowNonFungibleTokenEntry(_ evmContractAddressHex: String): &NFTList.NFTCollectionEntry?
115
116 // --- Register functions ---
117
118 /// Register and onboard a new EVM compatible token (ERC20 or ERC721) to the EVM Token List
119 access(all)
120 fun registerEVMAsset(_ evmContractAddressHex: String, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}?)
121
122 /// Register and onboard a new Cadence Asset to the EVM Token List
123 access(all)
124 fun registerCadenceAsset(_ ftOrNftType: Type, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}?)
125 }
126
127 /// Resource for the Token List Registry
128 ///
129 access(all) resource Registry: IListViewer {
130 // EVM Address Hex => Bridged Token Identity
131 access(self)
132 let regsiteredErc20s: {String: FTViewUtils.FTIdentity}
133 // EVM Address Hex => Bridged Token Identity
134 access(self)
135 let regsiteredErc721s: {String: NFTViewUtils.NFTIdentity}
136
137 init() {
138 self.regsiteredErc20s = {}
139 self.regsiteredErc721s = {}
140 }
141
142 /* --- Implement the IListViewer interface --- */
143
144 /// Check if the EVM Address is registered
145 access(all)
146 view fun isEVMAddressRegistered(_ evmContractAddressHex: String): Bool {
147 return self.regsiteredErc20s[evmContractAddressHex] != nil
148 || self.regsiteredErc721s[evmContractAddressHex] != nil
149 }
150
151 /// Get the total number of registered ERC20 tokens
152 access(all)
153 view fun getERC20Amount(): Int {
154 return self.regsiteredErc20s.keys.length
155 }
156
157 /// Get the ERC20 addresses with pagination
158 /// The method will copy the addresses to the result array
159 access(all)
160 fun getERC20Addresses(_ page: Int, _ size: Int): [EVM.EVMAddress] {
161 return self.getERC20AddressesHex(page, size)
162 .map(fun (key: String): EVM.EVMAddress { return EVM.addressFromString(key) })
163 }
164
165 /// Get the ERC20 addresses with pagination
166 /// The method will copy the addresses to the result array
167 access(all)
168 fun getERC20AddressesHex(_ page: Int, _ size: Int): [String] {
169 pre {
170 page >= 0: "Invalid page"
171 size > 0: "Invalid size"
172 }
173 let max = self.getERC20Amount()
174 let start = page * size
175 if start > max {
176 return []
177 }
178 var end = start + size
179 if end > max {
180 end = max
181 }
182 return self.regsiteredErc20s.keys.slice(from: start, upTo: end)
183 }
184
185 /// Get the ERC20 addresses by for each
186 /// The method will call the function for each address
187 access(all)
188 fun forEachERC20Address(_ f: fun (EVM.EVMAddress): Bool) {
189 self.regsiteredErc20s.forEachKey(fun (key: String): Bool {
190 return f(EVM.addressFromString(key))
191 })
192 }
193
194 /// Get the ERC20 addresses(String) by for each
195 access(all)
196 fun forEachERC20AddressString(_ f: fun (String): Bool) {
197 self.regsiteredErc20s.forEachKey(f)
198 }
199
200 /// Borrow the Bridged Token's TokenList Entry
201 access(all)
202 view fun borrowFungibleTokenEntry(_ evmContractAddressHex: String): &{TokenList.FTEntryInterface}? {
203 if let item = self.regsiteredErc20s[evmContractAddressHex] {
204 let ftRegistry = TokenList.borrowRegistry()
205 let ftType = item.buildType()
206 return ftRegistry.borrowFungibleTokenEntry(ftType)
207 }
208 return nil
209 }
210
211 /// Get the total number of registered ERC721 tokens
212 access(all)
213 view fun getERC721Amount(): Int {
214 return self.regsiteredErc721s.keys.length
215 }
216
217 /// Get the ERC721 addresses with pagination
218 /// The method will copy the addresses to the result array
219 access(all)
220 fun getERC721Addresses(_ page: Int, _ size: Int): [EVM.EVMAddress] {
221 return self.getERC721AddressesHex(page, size)
222 .map(fun (key: String): EVM.EVMAddress { return EVM.addressFromString(key) })
223 }
224
225 /// Get the ERC721 addresses with pagination
226 /// The method will copy the addresses to the result array
227 access(all)
228 fun getERC721AddressesHex(_ page: Int, _ size: Int): [String] {
229 pre {
230 page >= 0: "Invalid page"
231 size > 0: "Invalid size"
232 }
233 let max = self.getERC721Amount()
234 let start = page * size
235 if start > max {
236 return []
237 }
238 var end = start + size
239 if end > max {
240 end = max
241 }
242 return self.regsiteredErc721s.keys.slice(from: start, upTo: end)
243 }
244
245 /// Get the ERC721 addresses by for each
246 /// The method will call the function for each address
247 access(all)
248 fun forEachERC721Address(_ f: fun (EVM.EVMAddress): Bool) {
249 self.regsiteredErc721s.forEachKey(fun (key: String): Bool {
250 return f(EVM.addressFromString(key))
251 })
252 }
253
254 /// Get the ERC721 addresses(String) by for each
255 access(all)
256 fun forEachERC721AddressString(_ f: fun (String): Bool) {
257 self.regsiteredErc721s.forEachKey(f)
258 }
259
260 /// Borrow the Bridged Token's TokenList Entry
261 access(all)
262 view fun borrowNonFungibleTokenEntry(_ evmContractAddressHex: String): &NFTList.NFTCollectionEntry? {
263 if let item = self.regsiteredErc721s[evmContractAddressHex] {
264 let nftRegistry = NFTList.borrowRegistry()
265 let nftType = item.buildNFTType()
266 return nftRegistry.borrowNFTEntry(nftType)
267 }
268 return nil
269 }
270
271 /* --- Register functions --- */
272
273 /// Register and onboard a new EVM compatible token (ERC20 or ERC721) to the EVM Token List
274 ///
275 access(all)
276 fun registerEVMAsset(_ evmContractAddressHex: String, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}?) {
277 var contractAddr: Address? = nil
278 var contractName: String? = nil
279 var isNFT: Bool = false
280
281 let address = EVM.addressFromString(evmContractAddressHex)
282 let isRequires = FlowEVMBridge.evmAddressRequiresOnboarding(address)
283 if isRequires == false {
284 // Now just need to look up the type of the token and register it to the Token List
285 let assetType = FlowEVMBridgeConfig.getTypeAssociated(with: address)
286 ?? panic("Could not find the asset type")
287 contractAddr = FlowEVMBridgeUtils.getContractAddress(fromType: assetType)
288 contractName = FlowEVMBridgeUtils.getContractName(fromType: assetType)
289 isNFT = assetType.isSubtype(of: Type<@{NonFungibleToken.NFT}>())
290 } else if isRequires == true {
291 // Onboard the token to the bridge
292 assert(feeProvider != nil, message: "Fee provider is required for onboarding")
293 FlowEVMBridge.onboardByEVMAddress(address, feeProvider: feeProvider!)
294 // FIXME: Because the new deployed Cadence contracts can not be found in the same transaction
295 // So we just build the contract address and name here
296 let identifierSplit = FlowEVMBridge.getType().identifier.split(separator: ".")
297 contractAddr = Address.fromString("0x".concat(identifierSplit[1]))
298 let evmOnboardingValues = FlowEVMBridgeUtils.getEVMOnboardingValues(evmContractAddress: address)
299 contractName = evmOnboardingValues.cadenceContractName
300 isNFT = evmOnboardingValues.isERC721
301 } else {
302 // This address is not a valid EVM asset
303 panic("Invalid EVM Asset for the Address: 0x".concat(evmContractAddressHex))
304 }
305
306 // register the asset to the Token List
307 self._tryRegisterAsset(
308 address.toString(),
309 contractAddr ?? panic("Contract address is required to register"),
310 contractName ?? panic("Contract name is required to register"),
311 isNFT
312 )
313 }
314
315 /// Register and onboard a new Cadence Asset to the EVM Token List
316 access(all)
317 fun registerCadenceAsset(_ ftOrNftType: Type, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}?) {
318 let contractAddr = FlowEVMBridgeUtils.getContractAddress(fromType: ftOrNftType) ?? panic("Could not find the contract address")
319 let contractName = FlowEVMBridgeUtils.getContractName(fromType: ftOrNftType) ?? panic("Could not find the contract name")
320 let isNFT: Bool = ftOrNftType.isSubtype(of: Type<@{NonFungibleToken.NFT}>())
321 var evmAddress: EVM.EVMAddress? = nil
322
323 let isRequires = FlowEVMBridge.typeRequiresOnboarding(ftOrNftType)
324 if isRequires == false {
325 // Now just need to look up the type of the token and register it to the Token List
326 evmAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: ftOrNftType)
327 } else if isRequires == true {
328 // Onboard the token to the bridge
329 assert(feeProvider != nil, message: "Fee provider is required for onboarding")
330 FlowEVMBridge.onboardByType(ftOrNftType, feeProvider: feeProvider!)
331 // EVM Contract deployment can be found in the same transaction
332 evmAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: ftOrNftType)
333 } else {
334 panic("Invalid Cadence Asset Type for the Type: ".concat(ftOrNftType.identifier))
335 }
336
337 self._tryRegisterAsset(
338 evmAddress?.toString() ?? panic("EVM Address is required to register"),
339 contractAddr,
340 contractName,
341 isNFT
342 )
343 }
344
345 /* --- Internal functions --- */
346
347 /// Register the EVM compatible asset to the Token List
348 ///
349 access(self)
350 fun _tryRegisterAsset(_ evmContractAddressHex: String, _ address: Address, _ contractName: String, _ isNFT: Bool) {
351 var isRegistered = false
352 if isNFT {
353 if self.regsiteredErc721s[evmContractAddressHex] != nil {
354 return
355 }
356 NFTList.ensureNFTCollectionRegistered(address, contractName)
357 if NFTList.isNFTCollectionRegistered(address, contractName) {
358 isRegistered = true
359 self.regsiteredErc721s[evmContractAddressHex] = NFTViewUtils.NFTIdentity(address, contractName)
360 }
361 } else {
362 if self.regsiteredErc20s[evmContractAddressHex] != nil {
363 return
364 }
365 TokenList.ensureFungibleTokenRegistered(address, contractName)
366 if TokenList.isFungibleTokenRegistered(address, contractName) {
367 isRegistered = true
368 self.regsiteredErc20s[evmContractAddressHex] = FTViewUtils.FTIdentity(address, contractName)
369 }
370 }
371
372 if isRegistered {
373 emit EVMBridgedAssetRegistered(
374 "0x".concat(evmContractAddressHex),
375 isNFT,
376 address,
377 contractName
378 )
379 }
380 }
381 }
382
383 /* --- Public functions --- */
384
385 /// Borrow the public capability of Token List Registry
386 ///
387 access(all)
388 view fun borrowRegistry(): &Registry {
389 return getAccount(self.account.address)
390 .capabilities.get<&Registry>(self.registryPublicPath)
391 .borrow()
392 ?? panic("Could not borrow the Registry reference")
393 }
394
395 /// Whether the EVM Address is registered
396 ///
397 access(all)
398 view fun isEVMAddressRegistered(_ evmContractAddressHex: String): Bool {
399 let registry = self.borrowRegistry()
400 return registry.isEVMAddressRegistered(evmContractAddressHex)
401 }
402
403 /// Whether the EVM Address is valid to register
404 ///
405 access(all)
406 fun isValidToRegisterEVMAddress(_ evmContractAddressHex: String): Bool {
407 let addr = EVM.addressFromString(evmContractAddressHex)
408 let isRegistered = self.isEVMAddressRegistered(addr.toString())
409 if isRegistered {
410 return false
411 }
412 let isRequires = FlowEVMBridge.evmAddressRequiresOnboarding(addr)
413 return isRequires != nil
414 }
415
416 /// Whether the Cadence Type is valid to register
417 ///
418 access(all)
419 view fun isValidToRegisterCadenceType(_ ftOrNftType: Type): Bool {
420 let registry = self.borrowRegistry()
421 let isRequires = FlowEVMBridge.typeRequiresOnboarding(ftOrNftType)
422 if isRequires == nil {
423 return false
424 }
425 let evmAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: ftOrNftType)
426 return evmAddress == nil || !registry.isEVMAddressRegistered(evmAddress!.toString())
427 }
428
429 /// Ensure the Cadence Asset is registered
430 ///
431 access(all)
432 fun ensureCadenceAssetRegistered(
433 _ address: Address,
434 _ contractName: String,
435 feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}?
436 ) {
437 var isNFT: Bool? = nil
438 var ftOrNftType = FTViewUtils.buildFTVaultType(address, contractName)
439 if ftOrNftType != nil {
440 if TokenList.isValidToRegister(address, contractName) {
441 isNFT = false
442 }
443 } else {
444 ftOrNftType = NFTViewUtils.buildNFTType(address, contractName)
445 if ftOrNftType != nil && NFTList.isValidToRegister(address, contractName) {
446 isNFT = true
447 }
448 }
449 if isNFT != nil && ftOrNftType != nil && ftOrNftType!.isRecovered == false {
450 if self.isValidToRegisterCadenceType(ftOrNftType!) {
451 let registry = self.borrowRegistry()
452 registry.registerCadenceAsset(ftOrNftType!, feeProvider: feeProvider)
453 }
454 }
455 }
456
457 /// Ensure the EVM Asset is registered
458 ///
459 access(all)
460 fun ensureEVMAssetRegistered(
461 _ evmContractAddressHex: String,
462 feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}?
463 ) {
464 let addrHex = EVM.addressFromString(evmContractAddressHex).toString()
465 if self.isValidToRegisterEVMAddress(addrHex) {
466 let registry = self.borrowRegistry()
467 registry.registerEVMAsset(addrHex, feeProvider: feeProvider)
468 }
469 }
470
471 /// The prefix for the paths
472 ///
473 access(all)
474 view fun getPathPrefix(): String {
475 return "EVMTokenList_".concat(self.account.address.toString()).concat("_")
476 }
477
478 /// Initialize the contract
479 init() {
480 // Identifiers
481 let identifier = NFTList.getPathPrefix()
482 self.registryStoragePath = StoragePath(identifier: identifier.concat("Registry"))!
483 self.registryPublicPath = PublicPath(identifier: identifier.concat("Registry"))!
484
485 // Create the Token List Registry
486 let registry <- create Registry()
487 self.account.storage.save(<- registry, to: self.registryStoragePath)
488 // link the public capability
489 let cap = self.account.capabilities
490 .storage.issue<&Registry>(self.registryStoragePath)
491 self.account.capabilities.publish(cap, at: self.registryPublicPath)
492
493 // Emit the initialized event
494 emit ContractInitialized()
495 }
496}
497