Smart Contract
EVMVMBridgedNFT_12c05208f23ac839f22d1ce854f15b3b082fbd72
A.1e4aa0b87d10b141.EVMVMBridgedNFT_12c05208f23ac839f22d1ce854f15b3b082fbd72
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_12c05208f23ac839f22d1ce854f15b3b082fbd72 : 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_12c05208f23ac839f22d1ce854f15b3b082fbd72.name
81 }
82
83 access(all) view fun getSymbol(): String {
84 return EVMVMBridgedNFT_12c05208f23ac839f22d1ce854f15b3b082fbd72.symbol
85 }
86
87 access(all) view fun tokenURI(): String {
88 return EVMVMBridgedNFT_12c05208f23ac839f22d1ce854f15b3b082fbd72.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_12c05208f23ac839f22d1ce854f15b3b082fbd72.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_12c05208f23ac839f22d1ce854f15b3b082fbd72.resolveContractView(
103 resourceType: self.getType(),
104 viewType: Type<MetadataViews.NFTCollectionData>()
105 )
106 case Type<MetadataViews.NFTCollectionDisplay>():
107 return EVMVMBridgedNFT_12c05208f23ac839f22d1ce854f15b3b082fbd72.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_12c05208f23ac839f22d1ce854f15b3b082fbd72.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_12c05208f23ac839f22d1ce854f15b3b082fbd72.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_12c05208f23ac839f22d1ce854f15b3b082fbd72.resolveContractView(
148 resourceType: Type<@EVMVMBridgedNFT_12c05208f23ac839f22d1ce854f15b3b082fbd72.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_12c05208f23ac839f22d1ce854f15b3b082fbd72.name
158 }
159
160 access(all) view fun getSymbol(): String {
161 return EVMVMBridgedNFT_12c05208f23ac839f22d1ce854f15b3b082fbd72.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_12c05208f23ac839f22d1ce854f15b3b082fbd72.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_12c05208f23ac839f22d1ce854f15b3b082fbd72.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_12c05208f23ac839f22d1ce854f15b3b082fbd72.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_12c05208f23ac839f22d1ce854f15b3b082fbd72.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_12c05208f23ac839f22d1ce854f15b3b082fbd72.createEmptyCollection(nftType: Type<@EVMVMBridgedNFT_12c05208f23ac839f22d1ce854f15b3b082fbd72.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_12c05208f23ac839f22d1ce854f15b3b082fbd72Collection"
316 let collectionData = MetadataViews.NFTCollectionData(
317 storagePath: StoragePath(identifier: identifier)!,
318 publicPath: PublicPath(identifier: identifier)!,
319 publicCollection: Type<&EVMVMBridgedNFT_12c05208f23ac839f22d1ce854f15b3b082fbd72.Collection>(),
320 publicLinkedType: Type<&EVMVMBridgedNFT_12c05208f23ac839f22d1ce854f15b3b082fbd72.Collection>(),
321 createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
322 return <-EVMVMBridgedNFT_12c05208f23ac839f22d1ce854f15b3b082fbd72.createEmptyCollection(nftType: Type<@EVMVMBridgedNFT_12c05208f23ac839f22d1ce854f15b3b082fbd72.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_12c05208f23ac839f22d1ce854f15b3b082fbd72")!
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_12c05208f23ac839f22d1ce854f15b3b082fbd72.NFT>(), with: self.evmNFTContractAddress)
391 FlowEVMBridgeNFTEscrow.initializeEscrow(
392 forType: Type<@EVMVMBridgedNFT_12c05208f23ac839f22d1ce854f15b3b082fbd72.NFT>(),
393 name: name,
394 symbol: symbol,
395 erc721Address: self.evmNFTContractAddress
396 )
397 }
398}
399