Smart Contract

EVMTokenList

A.15a918087ab12d86.EVMTokenList

Valid From

86,819,490

Deployed

3d ago
Feb 24, 2026, 11:42:11 PM UTC

Dependents

6 imports
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