Smart Contract

EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a

A.1e4aa0b87d10b141.EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a

Deployed

1w ago
Feb 16, 2026, 08:14:21 PM UTC

Dependents

0 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2import MetadataViews from 0x1d7e57aa55817448
3import ViewResolver from 0x1d7e57aa55817448
4import FungibleToken from 0xf233dcee88fe0abe
5import FlowToken from 0x1654653399040a61
6
7import EVM from 0xe467b9dd11fa00df
8
9import ICrossVM from 0x1e4aa0b87d10b141
10import ICrossVMAsset from 0x1e4aa0b87d10b141
11import IEVMBridgeNFTMinter from 0x1e4aa0b87d10b141
12import FlowEVMBridgeNFTEscrow from 0x1e4aa0b87d10b141
13import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141
14import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141
15import FlowEVMBridge from 0x1e4aa0b87d10b141
16import CrossVMNFT from 0x1e4aa0b87d10b141
17import FlowEVMBridgeResolver from 0x1e4aa0b87d10b141
18
19/// This contract is a template used by FlowEVMBridge to define EVM-native NFTs bridged from Flow EVM to Flow.
20/// Upon deployment of this contract, the contract name is derived as a function of the asset type (here an ERC721 aka
21/// an NFT) and the contract's EVM address. The derived contract name is then joined with this contract's code,
22/// prepared as chunks in FlowEVMBridgeTemplates before being deployed to the Flow EVM Bridge account.
23///
24/// On bridging, the ERC721 is transferred to the bridge's CadenceOwnedAccount EVM address and a new NFT is minted from
25/// this contract to the bridging caller. On return to Flow EVM, the reverse process is followed - the token is locked
26/// in NFT escrow and the ERC721 is transferred to the defined recipient. In this way, the Cadence token acts as a
27/// representation of both the EVM NFT and thus ownership rights to it upon bridging back to Flow EVM.
28///
29/// To bridge between VMs, a caller can either use the interface exposed on CadenceOwnedAccount or use FlowEVMBridge
30/// public contract methods.
31///
32access(all) contract EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a : ICrossVM, ICrossVMAsset, IEVMBridgeNFTMinter, NonFungibleToken {
33
34    /// Pointer to the Factory deployed Solidity contract address defining the bridged asset
35    access(all) let evmNFTContractAddress: EVM.EVMAddress
36    /// Name of the NFT collection defined in the corresponding ERC721 contract
37    access(all) let name: String
38    /// Symbol of the NFT collection defined in the corresponding ERC721 contract
39    access(all) let symbol: String
40    /// URI of the contract, if available as a var in case the bridge enables cross-VM Metadata syncing in the future
41    access(all) var contractURI: String?
42    /// Retain a Collection to reference when resolving Collection Metadata
43    access(self) let collection: @Collection
44    /// Mapping of token URIs indexed on their ERC721 ID. This would not normally be retained within a Cadence NFT
45    /// contract, but since NFT metadata may be updated in EVM, it's retained here so that the bridge can update
46    /// it against the source ERC721 contract which is treated as the NFT's source of truth.
47    access(all) let tokenURIs: {UInt256: String}
48
49    /// The NFT resource representing the bridged ERC721 token
50    ///
51    access(all) resource NFT : ICrossVMAsset.AssetInfo, CrossVMNFT.EVMNFT {
52        /// The Cadence ID of the NFT
53        access(all) let id: UInt64
54        /// The ERC721 ID of the NFT
55        access(all) let evmID: UInt256
56        /// Additional onchain metadata
57        access(all) let metadata: {String: AnyStruct}
58
59        init(
60            evmID: UInt256,
61            metadata: {String: AnyStruct}
62        ) {
63            self.id = self.uuid
64            self.evmID = evmID
65            self.metadata = metadata
66        }
67
68        /// Returns the metadata view types supported by this NFT
69        access(all) view fun getViews(): [Type] {
70            return [
71                Type<MetadataViews.Display>(),
72                Type<MetadataViews.Serial>(),
73                Type<MetadataViews.NFTCollectionData>(),
74                Type<MetadataViews.NFTCollectionDisplay>(),
75                Type<MetadataViews.EVMBridgedMetadata>()
76            ]
77        }
78
79        access(all) view fun getName(): String {
80            return EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.name
81        }
82
83        access(all) view fun getSymbol(): String {
84            return EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.symbol
85        }
86
87        access(all) view fun tokenURI(): String {
88            return EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.tokenURIs[self.evmID] ?? ""
89        }
90
91        /// Resolves a metadata view for this NFT
92        access(all) fun resolveView(_ view: Type): AnyStruct? {
93            switch view {
94                case Type<MetadataViews.Display>():
95                    let contractRef = EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.borrowThisContract()
96                    return FlowEVMBridgeResolver.resolveBridgedView(bridgedContract: contractRef, view: Type<MetadataViews.Display>())
97                case Type<MetadataViews.Serial>():
98                    return MetadataViews.Serial(
99                        self.id
100                    )
101                case Type<MetadataViews.NFTCollectionData>():
102                    return EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.resolveContractView(
103                        resourceType: self.getType(),
104                        viewType: Type<MetadataViews.NFTCollectionData>()
105                    )
106                case Type<MetadataViews.NFTCollectionDisplay>():
107                    return EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.resolveContractView(
108                        resourceType: self.getType(),
109                        viewType: Type<MetadataViews.NFTCollectionDisplay>()
110                    )
111                case Type<MetadataViews.EVMBridgedMetadata>():
112                    return MetadataViews.EVMBridgedMetadata(
113                        name: self.getName(),
114                        symbol: self.getSymbol(),
115                        uri: MetadataViews.URI(baseURI: nil, value: self.tokenURI())
116                    )
117            }
118            return nil
119        }
120
121        /// public function that anyone can call to create a new empty collection
122        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
123            return <- EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.createEmptyCollection(nftType: self.getType())
124        }
125
126        /* --- CrossVMNFT conformance --- */
127        //
128        /// Returns the EVM contract address of the NFT
129        access(all) view fun getEVMContractAddress(): EVM.EVMAddress {
130            return EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.getEVMContractAddress()
131        }
132    }
133
134    /// This resource holds associated NFTs, and serves queries about stored NFTs
135    access(all) resource Collection : CrossVMNFT.EVMNFTCollection {
136        /// dictionary of NFT conforming tokens indexed on their ID
137        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
138        /// Mapping of EVM IDs to Flow NFT IDs
139        access(contract) let evmIDToFlowID: {UInt256: UInt64}
140
141        access(all) var storagePath: StoragePath
142        access(all) var publicPath: PublicPath
143
144        init () {
145            self.ownedNFTs <- {}
146            self.evmIDToFlowID = {}
147            let collectionData = EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.resolveContractView(
148                    resourceType: Type<@EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.NFT>(),
149                    viewType: Type<MetadataViews.NFTCollectionData>()
150                ) as! MetadataViews.NFTCollectionData?
151                ?? panic("Could not resolve the collection data view for the NFT collection")
152            self.storagePath = collectionData.storagePath
153            self.publicPath = collectionData.publicPath
154        }
155
156        access(all) view fun getName(): String {
157            return EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.name
158        }
159
160        access(all) view fun getSymbol(): String {
161            return EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.symbol
162        }
163
164        /// Returns a list of NFT types that this receiver accepts
165        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
166            return { Type<@EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.NFT>(): true }
167        }
168
169        /// Returns whether or not the given type is accepted by the collection
170        /// A collection that can accept any type should just return true by default
171        access(all) view fun isSupportedNFTType(type: Type): Bool {
172           return type == Type<@EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.NFT>()
173        }
174
175        /// Removes an NFT from the collection and moves it to the caller
176        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
177            let token <- self.ownedNFTs.remove(key: withdrawID)
178                ?? panic("Could not withdraw an NFT with the provided ID from the collection")
179
180            return <-token
181        }
182
183        /// Withdraws an NFT from the collection by its EVM ID
184        access(NonFungibleToken.Withdraw) fun withdrawByEVMID(_ id: UInt256): @{NonFungibleToken.NFT} {
185            return <- self.withdraw(withdrawID: 
186                self.getCadenceID(from: id) ?? panic("Could not withdraw an NFT with the provided EVM ID from the collection")
187            )
188        }
189
190        /// Ttakes a NFT and adds it to the collections dictionary and adds the ID to the evmIDToFlowID mapping
191        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
192            let token <- token as! @EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.NFT
193
194            // add the new token to the dictionary which removes the old one
195            self.evmIDToFlowID[token.evmID] = token.id
196            let oldToken <- self.ownedNFTs[token.id] <- token
197
198            destroy oldToken
199        }
200
201        /// Returns an array of the IDs that are in the collection
202        access(all) view fun getIDs(): [UInt64] {
203            return self.ownedNFTs.keys
204        }
205
206        /// Returns an array of the EVM IDs that are in the collection
207        access(all) view fun getEVMIDs(): [UInt256] {
208            return self.evmIDToFlowID.keys
209        }
210
211        /// Returns the Cadence NFT.id for the given EVM NFT ID if it exists in the collection
212        access(all) view fun getCadenceID(from evmID: UInt256): UInt64? {
213            if self.evmIDToFlowID[evmID] != nil {
214                return self.evmIDToFlowID[evmID]
215            } else if evmID < UInt256(UInt64.max) && self.borrowNFT(UInt64(evmID)) != nil {
216                return UInt64(evmID)
217            } else {
218                return nil
219            }
220        }
221
222        /// Returns the EVM NFT ID associated with the Cadence NFT ID. The goal is to retrieve the ERC721 ID value.
223        /// As far as the bridge is concerned, an ERC721 defined by the bridge is the NFT's ID at the time of bridging
224        /// or the value of the NFT.evmID if it implements the CrossVMNFT.EVMNFT interface when bridged.
225        /// Following this pattern, if locked, the NFT is checked for EVMNFT conformance returning .evmID if so,
226        /// otherwise the NFT's ID is returned as a UInt256 since that's how the bridge would handle minting in the
227        /// corresponding ERC721 contract.
228        ///
229        access(all) view fun getEVMID(from cadenceID: UInt64): UInt256? {
230            if let nft = self.borrowNFT(cadenceID) {
231                if let evmNFT = CrossVMNFT.getEVMID(from: nft) {
232                    return evmNFT
233                }
234                return UInt256(nft.id)
235            }
236            return nil
237        }
238
239        /// Returns the contractURI for the NFT collection as defined in the source ERC721 contract. If none was
240        /// defined at the time of bridging, an empty string is returned.
241        access(all) view fun contractURI(): String? {
242            return EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.contractURI
243        }
244
245        /// Gets the amount of NFTs stored in the collection
246        access(all) view fun getLength(): Int {
247            return self.ownedNFTs.keys.length
248        }
249
250        /// Retrieves a reference to the NFT stored in the collection by its ID
251        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
252            return &self.ownedNFTs[id]
253        }
254
255        /// Borrow the view resolver for the specified NFT ID
256        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
257            return &self.ownedNFTs[id] as &{ViewResolver.Resolver}? ?? nil
258        }
259
260        /// Creates an empty collection
261        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection}  {
262            return <-EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.createEmptyCollection(nftType: Type<@EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.NFT>())
263        }
264    }
265
266    /// createEmptyCollection creates an empty Collection for the specified NFT type
267    /// and returns it to the caller so that they can own NFTs
268    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
269        return <- create Collection()
270    }
271
272    /**********************
273            Getters
274    ***********************/
275
276    /// Returns the name of the asset
277    ///
278    access(all) view fun getName(): String {
279        return self.name
280    }
281
282    /// Returns the symbol of the asset
283    ///
284    access(all) view fun getSymbol(): String {
285        return self.symbol
286    }
287
288    /// Returns the EVM contract address of the NFT this contract represents
289    ///
290    access(all) view fun getEVMContractAddress(): EVM.EVMAddress {
291        return self.evmNFTContractAddress
292    }
293
294    /// Function that returns all the Metadata Views implemented by a Non Fungible Token
295    ///
296    /// @return An array of Types defining the implemented views. This value will be used by
297    ///         developers to know which parameter to pass to the resolveView() method.
298    ///
299    access(all) view fun getContractViews(resourceType: Type?): [Type] {
300        return [
301            Type<MetadataViews.NFTCollectionData>(),
302            Type<MetadataViews.NFTCollectionDisplay>(),
303            Type<MetadataViews.EVMBridgedMetadata>()
304        ]
305    }
306
307    /// Function that resolves a metadata view for this contract.
308    ///
309    /// @param view: The Type of the desired view.
310    /// @return A structure representing the requested view.
311    ///
312    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
313        switch viewType {
314            case Type<MetadataViews.NFTCollectionData>():
315                let identifier = "EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2aCollection"
316                let collectionData = MetadataViews.NFTCollectionData(
317                    storagePath: StoragePath(identifier: identifier)!,
318                    publicPath: PublicPath(identifier: identifier)!,
319                    publicCollection: Type<&EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.Collection>(),
320                    publicLinkedType: Type<&EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.Collection>(),
321                    createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
322                        return <-EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.createEmptyCollection(nftType: Type<@EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.NFT>())
323                    })
324                )
325                return collectionData
326            case Type<MetadataViews.NFTCollectionDisplay>():
327                let selfRef = self.borrowThisContract()
328                return FlowEVMBridgeResolver.resolveBridgedView(bridgedContract: selfRef, view: Type<MetadataViews.NFTCollectionDisplay>())
329            case Type<MetadataViews.EVMBridgedMetadata>():
330                return MetadataViews.EVMBridgedMetadata(
331                    name: self.name,
332                    symbol: self.symbol,
333                    uri: self.contractURI != nil ? MetadataViews.URI(baseURI: nil, value: self.contractURI!) : MetadataViews.URI(baseURI: nil, value: "")
334                )
335        }
336        return nil
337    }
338
339    /**********************
340        Internal Methods
341    ***********************/
342
343    /// Allows the bridge to mint NFTs from bridge-defined NFT contracts
344    ///
345    access(account)
346    fun mintNFT(id: UInt256, tokenURI: String): @NFT {
347        pre {
348            self.tokenURIs[id] == nil: "A token with the given ERC721 ID already exists"
349        }
350        self.tokenURIs[id] = tokenURI
351        return <-create NFT(
352            evmID: id,
353            metadata: {
354                "Bridged Block": getCurrentBlock().height,
355                "Bridged Timestamp": getCurrentBlock().timestamp
356            }
357        )
358    }
359
360    /// Allows the bridge to update the URI of bridged NFTs. This assumes that the EVM-defining project may contain
361    /// logic (onchain or offchain) which updates NFT metadata in the source ERC721 contract. On bridging, the URI can
362    /// then be updated in this contract to reflect the source ERC721 contract's metadata.
363    ///
364    access(account)
365    fun updateTokenURI(evmID: UInt256, newURI: String) {
366        pre {
367            self.tokenURIs[evmID] != nil: "No token with the given ERC721 ID exists"
368        }
369        if self.tokenURIs[evmID] != newURI {
370            self.tokenURIs[evmID] = newURI
371        }
372    }
373
374    /// Returns a reference to this contract as an ICrossVMAsset contract
375    ///
376    access(self)
377    fun borrowThisContract(): &{ICrossVMAsset} {
378        let contractAddress = self.account.address
379        return getAccount(contractAddress).contracts.borrow<&{ICrossVMAsset}>(name: "EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a")!
380    }
381
382    init(name: String, symbol: String, evmContractAddress: EVM.EVMAddress, contractURI: String?) {
383        self.evmNFTContractAddress = evmContractAddress
384        self.name = name
385        self.symbol = symbol
386        self.contractURI = contractURI
387        self.tokenURIs = {}
388        self.collection <- create Collection()
389
390        FlowEVMBridgeConfig.associateType(Type<@EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.NFT>(), with: self.evmNFTContractAddress)
391        FlowEVMBridgeNFTEscrow.initializeEscrow(
392            forType: Type<@EVMVMBridgedNFT_4fe8d151845b98c45ce2dc20501e4818db1fbd2a.NFT>(),
393            name: name,
394            symbol: symbol,
395            erc721Address: self.evmNFTContractAddress
396        )
397    }
398}
399