Flow provides the Cross-VM Bridge which enables the movement of
fungible and non-fungible tokens between Flow-Cadence & Flow-EVM. The Cross-VM Bridge is a contract-based protocol enabling the
automated and atomic bridging of tokens from Cadence into EVM with their corresponding ERC-20 and ERC-721 token types.
In the opposite direction, it supports bridging of arbitrary ERC-20 and ERC-721 tokens from EVM to Cadence as their
corresponding FT or NFT token types.
By default, when a user onboards a new token to the bridge,
the bridge will deploy a standard token contract in the other VM that only the core bridge
protocol contracts retain limited control over. This bridge-deployed contract handles basic
minting and metadata operations that are required for usage in the needed environment.
If a developer wants to define and connect the NFT contracts on both sides of the bridge,
they can have each contract point to each other to indicate that they are associated and then
register that association with the bridge so the token moves between VMs as either definition.
The Cross-VM Bridge internalizes the capabilities to deploy new token contracts in either VM state as needed, resolving
access to, and maintaining links between associated contracts. It additionally automates account and contract calls to
enforce source VM asset burn or lock, and target VM token mint or unlock.
Developers wishing to use the Cross-VM Bridge will be required to use a Cadence transaction. Cross-VM bridging
functionality is not currently available natively in Flow EVM. By extension, this means that the EVM account bridging
from EVM to Cadence must be a CadenceOwnedAccount (COA) as this is the only EVM account
type that can be controlled from the Cadence runtime.
This FLIP-233 outlines the architecture and implementation of the VM bridge.
An additional FLIP-318 describes how developers can create custom associations
between NFTs they define and control in each VM.
This document will focus on how to use the Cross-VM Bridge and considerations for fungible and non-fungible token
projects deploying to either Cadence or EVM.
And below are the bridge escrow's EVM addresses. These addresses are COAs and are stored stored in the same Flow account
as you'll find the Cadence contracts (see above).
All bridging activity in either direction is orchestrated via Cadence on COA EVM accounts. This means that all bridging
activity must be initiated via a Cadence transaction, not an EVM transaction regardless of the directionality of the
bridge request. For more information on the interplay between Cadence and EVM, see How Flow EVM
Works.
The Flow EVM bridge allows both fungible and non-fungible tokens to move atomically between Cadence and EVM. In the
context of EVM, fungible tokens are defined as ERC20 tokens, and non-fungible tokens as ERC721 tokens. In Cadence,
fungible tokens are defined by contracts implementing
the FungibleToken interface
and non-fungible tokens implement
the NonFungibleToken interface.
You can find full guides for creating these projects here.
Like all operations on Flow, there are native fees associated with both computation and storage. To prevent spam and
sustain the bridge account's storage consumption, fees are charged for both onboarding assets and bridging assets. In
the case where storage consumption is expected, fees are charged based on the storage consumed at the current network
storage rate.
For the purpose of this guide, we are assuming that the developer has already deployed
a token smart contract to their preferred VM (Flow-Cadence or Flow-EVM) and wants
to bridge it to the other (target) VM.
In order for the developer's token to be usable in the target VM, there must be a contract
that defines the asset and how it behaves in the target VM that also enables the bridge to
fulfill the asset from Cadence to EVM and vice versa. This contract is separate from the
contract in the native VM, but they are "associated" with each other by the mechanisms of
the Flow VM bridge.
To create this association, the asset must be "onboarded" to the bridge
before bridging operations can be fulfilled. This can happen in two ways:
Any user registers the native token contract with the bridge and the bridge deploys
a basic templated version of the contract in the target VM. This basic contract is automatically
associated with the native contract and is used for bridging. The developer has no direct control
over this bridge-deployed contract because it is controlled by the bridge.
With this option (available for only for NFTs) developers can deploy their own contract to the
target VM and declare a custom association between it and the native contract. This allows
them to have more control over both contracts, enabling them to include more sophisticated
features and mechanisms in their bridged token contracts such as ERC-721C, unique metadata
views, and more that aren't included in the default bridged template versions.
Before continuing with onboarding your token, you should review
the Prep Your Assets for Bridging section of this document.
This describes some steps you should follow to make sure that your native asset and/or
bridged asset are properly set up for you to register them with the bridge.
Moving from a Cadence-native asset to EVM, automatic onboarding can occur on the fly,
deploying a template contract in the same transaction as
the asset is bridged to EVM if the transaction so specifies.
Moving from EVM to Cadence, however, requires that onboarding occur in a separate transaction due to the fact that a
Cadence contract is initialized at the end of a transaction and isn't available in the runtime until after the
transaction has executed.
Below are transactions relevant to automatically onboarding assets native to either VM:
Automatically Onboard a Cadence-native asset:
onboard_by_type.cdc
onboard_by_type.cdc
_56
import "FungibleToken"
_56
import "FlowToken"
_56
_56
import "ScopedFTProviders"
_56
_56
import "EVM"
_56
_56
import "FlowEVMBridge"
_56
import "FlowEVMBridgeConfig"
_56
_56
/// This transaction onboards the asset type to the bridge, configuring the bridge to move assets between environments
_56
/// NOTE: This must be done before bridging a Cadence-native asset to EVM
_56
///
_56
/// @param type: The Cadence type of the bridgeable asset to onboard to the bridge
_56
///
_56
transaction(type: Type) {
_56
_56
let scopedProvider: @ScopedFTProviders.ScopedFTProvider
With Custom Associations,
developers can deploy NFT contracts in both VMs and associate them with each other,
allowing them to retain control of the contracts in both VMs as well as implement custom
use-case specific functionality.
In order to do this, each contract must implement a special interface
that tells the bridge what the associated contract is in the other VM.
The fact that both point to each other validates the intended association,
preventing spoofing. If the contracts do not point to each other this way,
they will not be able to be registered as a custom association.
Review the Preparing Custom Associations section
to learn how to set up each of your contracts for a custom association.
Below is the transaction for onboarding NFTs for a custom association.
Remember that both the Cadence and the Solidity contract need to be deployed
and include the special interface conformances to point to each other before registration!
Onboard an NFT Custom Association:
register_cross_vm_nft.cdc
onboard_by_type.cdc
_90
import "FungibleToken"
_90
import "NonFungibleToken"
_90
import "CrossVMMetadataViews"
_90
import "EVM"
_90
_90
import "ScopedFTProviders"
_90
import "FlowEVMBridgeCustomAssociationTypes"
_90
import "FlowEVMBridgeCustomAssociations"
_90
import "FlowEVMBridge"
_90
import "FlowEVMBridgeConfig"
_90
_90
/// This transaction will register an NFT type as a custom cross-VM NFT. The Cadence contract must implement the
_90
/// CrossVMMetadata.EVMPointer view and the corresponding ERC721 must implement ICrossVM interface such that the Type
_90
/// points to the EVM contract and vice versa. If the NFT is EVM-native, a
_90
/// FlowEVMBridgeCustomAssociations.NFTFulfillmentMinter Capability must be provided, allowing the bridge to fulfill
_90
/// requests moving the ERC721 from EVM into Cadence.
_90
///
_90
/// See FLIP-318 for more information on implementing custom cross-VM NFTs: https://github.com/onflow/flips/issues/318
_90
///
_90
/// @param nftTypeIdentifer: The type identifier of the NFT being registered as a custom cross-VM implementation
_90
/// @param fulfillmentMinterPath: The StoragePath where the NFTFulfillmentMinter is stored
let fulfillmentMinterCap: Capability<auth(FlowEVMBridgeCustomAssociationTypes.FulfillFromEVM) &{FlowEVMBridgeCustomAssociationTypes.NFTFulfillmentMinter}>?
_90
let scopedProvider: @ScopedFTProviders.ScopedFTProvider
Once an asset has been onboarded, either by automatic or custom association, it can be bridged in either
direction, referred to by its Cadence type. For Cadence-native assets, this is simply its native type. For EVM-native
assets, this is in most cases a templated Cadence contract deployed to the bridge account, the name of which is derived
from the EVM contract address. For instance, an ERC721 contract at address 0x1234 would be onboarded to the bridge as
EVMVMBridgedNFT_0x1234, making its type identifier A.<BRIDGE_ADDRESS>.EVMVMBridgedNFT_0x1234.NFT.
To get the type identifier for a given NFT, you can use the following code:
_10
// Where `nft` is either a @{NonFungibleToken.NFT} or &{NonFungibleToken.NFT}
_10
nft.getType().identifier
You may also retrieve the type associated with a given EVM contract address using the following script:
get_associated_type.cdc
get_associated_type.cdc
_16
import "EVM"
_16
_16
import "FlowEVMBridgeConfig"
_16
_16
/// Returns the Cadence Type associated with the given EVM address (as its hex String)
_16
///
_16
/// @param evmAddressHex: The hex-encoded address of the EVM contract as a String
_16
///
_16
/// @return The Cadence Type associated with the EVM address or nil if the address is not onboarded. `nil` may also be
_16
/// returned if the address is not a valid EVM address.
Any Cadence NFTs bridging to EVM are escrowed in the bridge account and either minted in a bridge-deployed ERC721
contract or transferred from escrow to the calling COA in EVM. On the return trip, NFTs are escrowed in EVM - owned by
the bridge's COA - and either unlocked from escrow if locked or minted from a bridge-owned NFT contract.
Below are transactions relevant to bridging NFTs:
bridge_nft_to_evm.cdc
bridge_nft_to_evm.cdc
_122
import "FungibleToken"
_122
import "NonFungibleToken"
_122
import "ViewResolver"
_122
import "MetadataViews"
_122
import "FlowToken"
_122
_122
import "ScopedFTProviders"
_122
_122
import "EVM"
_122
_122
import "FlowEVMBridge"
_122
import "FlowEVMBridgeConfig"
_122
import "FlowEVMBridgeUtils"
_122
_122
/// Bridges an NFT from the signer's collection in Cadence to the signer's COA in FlowEVM
_122
///
_122
/// NOTE: This transaction also onboards the NFT to the bridge if necessary which may incur additional fees
_122
/// than bridging an asset that has already been onboarded.
_122
///
_122
/// @param nftIdentifier: The Cadence type identifier of the NFT to bridge - e.g. nft.getType().identifier
_122
/// @param id: The Cadence NFT.id of the NFT to bridge to EVM
_122
///
_122
transaction(nftIdentifier: String, id: UInt64) {
_122
_122
let nft: @{NonFungibleToken.NFT}
_122
let coa: auth(EVM.Bridge) &EVM.CadenceOwnedAccount
_122
let requiresOnboarding: Bool
_122
let scopedProvider: @ScopedFTProviders.ScopedFTProvider
Any Cadence fungible tokens bridging to EVM are escrowed in the bridge account only if they are Cadence-native. If the
bridge defines the tokens, they are burned. On the return trip the pattern is similar, with the bridge burning
bridge-defined tokens or escrowing them if they are EVM-native. In all cases, if the bridge has authority to mint on one
side, it must escrow on the other as the native VM contract is owned by an external party.
With fungible tokens in particular, there may be some cases where the Cadence contract is not deployed to the bridge
account, but the bridge still follows a mint/burn pattern in Cadence. These cases are handled via
TokenHandler
implementations. Also know that moving $FLOW to EVM is built into the EVMAddress object so any requests bridging $FLOW
to EVM will simply leverage this interface; however, moving $FLOW from EVM to Cadence must be done through the COA
resource.
Below are transactions relevant to bridging fungible tokens:
bridge_tokens_to_evm.cdc
bridge_tokens_to_evm.cdc
_120
import "FungibleToken"
_120
import "ViewResolver"
_120
import "FungibleTokenMetadataViews"
_120
import "FlowToken"
_120
_120
import "ScopedFTProviders"
_120
_120
import "EVM"
_120
_120
import "FlowEVMBridge"
_120
import "FlowEVMBridgeConfig"
_120
import "FlowEVMBridgeUtils"
_120
_120
/// Bridges a Vault from the signer's storage to the signer's COA in EVM.Account.
_120
///
_120
/// NOTE: This transaction also onboards the Vault to the bridge if necessary which may incur additional fees
_120
/// than bridging an asset that has already been onboarded.
_120
///
_120
/// @param vaultIdentifier: The Cadence type identifier of the FungibleToken Vault to bridge
_120
/// - e.g. vault.getType().identifier
_120
/// @param amount: The amount of tokens to bridge from EVM
To maximize utility to the ecosystem, this bridge is permissionless and open to any fungible or non-fungible token as
defined by the respective Cadence standards and limited to ERC20 and ERC721 Solidity standards. Ultimately, a project
does not have to do anything for users to be able to bridge their assets between VMs. However, there are some
considerations developers may take to enhance the representation of their assets in non-native VMs. These largely relate
to asset metadata and ensuring that bridging does not compromise critical user assumptions about asset ownership.
Proposed in @onflow/flow-nft/pull/203, the EVMBridgedMetadata view
presents a mechanism to both represent metadata from bridged EVM assets as well as enable Cadence-native projects to
specify the representation of their assets in EVM. Implementing this view is not required for assets to be bridged, but
the bridge does default to it when available as a way to provide projects greater control over their EVM asset
definitions within the scope of ERC20 and ERC721 standards.
The interface for this view is as follows:
_20
access(all) struct URI: MetadataViews.File {
_20
/// The base URI prefix, if any. Not needed for all URIs, but helpful
_20
/// for some use cases For example, updating a whole NFT collection's
_20
/// image host easily
_20
access(all) let baseURI: String?
_20
/// The URI string value
_20
/// NOTE: this is set on init as a concatenation of the baseURI and the
_20
/// value if baseURI != nil
_20
access(self) let value: String
_20
_20
access(all) view fun uri(): String
_20
_20
}
_20
_20
access(all) struct EVMBridgedMetadata {
_20
access(all) let name: String
_20
access(all) let symbol: String
_20
_20
access(all) let uri: {MetadataViews.File}
_20
}
This uri value could be a pointer to some offchain metadata if you expect your metadata to be static. Or you could
couple the uri() method with the utility contract below to serialize the onchain metadata on the fly. Alternatively,
you may choose to host a metadata proxy which serves the requested token URI content.
The key consideration with respect to metadata is the distinct metadata storage patterns between ecosystem. It's
critical for NFT utility that the metadata be bridged in addition to the representation of the NFTs ownership. However,
it's commonplace for Cadence NFTs to store metadata onchain while EVM NFTs often store an onchain pointer to metadata
stored offchain. In order for Cadence NFTs to be properly represented in EVM platforms, the metadata must be bridged in
a format expected by those platforms and be done in a manner that also preserves the atomicity of bridge requests. The
path forward on this was decided to be a commitment of serialized Cadence NFT metadata into formats popular in the EVM
ecosystem.
For assets that do not implement EVMBridgedMetadata, the bridge will attempt to serialize the metadata of the asset as
a JSON data URL string. This is done via the SerializeMetadata
contract which
serializes metadata values into a JSON blob compatible with the OpenSea metadata standard. The serialized metadata is
then committed as the ERC721 tokenURI upon bridging Cadence-native NFTs to EVM. Since Cadence NFTs can easily update
onchain metadata either by field or by the ownership of sub-NFTs, this serialization pattern enables token URI updates
on subsequent bridge requests.
If you are a developer who wants to deploy and manage NFT contracts in both VMs
and have tokens from each be exchangable for each other,
you'll have to add some code to your contracts so they point to each other,
indicating that they each represent the same token in their respective VMs.
For the purposes of these instructions, an NFT is native to a VM if that VM
is the main source of truth for the contracts and where they are originally minted.
This feature is not available for Fungible Tokens at the moment, but may be in the future.
warning
Note that the bridge only supports a single custom association declaration. This
means that once you register an association between your Cadence NFT & EVM
contract, the association cannot be updated. If you wish to retain some upgradeability
to your registered implementations, it's recommended that you both retain keys on
your Cadence NFT contract account **and ** implement an upgradeable Solidity pattern
when deploying your ERC721, then register the association between your Cadence NFT
Type & ERC721 proxy (not the implementation address).
All Cadence NFT contracts implement Metadata Views
that return metadata about their NFTs in standard ways
via the {Contract}.resolveContractView() and {NFT}.resolveView() methods.
The following new view (CrossVMMetadataViews.EVMPointer) must be resolved at the contract level (ViewResolver.resolveContractView()) for a given Type
and at the NFT level (ViewResolver.Resolver.resolveView())
_11
/// View resolved at contract & resource level pointing to the associated EVM implementation
_11
access(all) struct EVMPointer {
_11
/// The associated Cadence Type
_11
access(all) let cadenceType: Type
_11
/// The defining Cadence contract address
_11
access(all) let cadenceContractAddress: Address
_11
/// The associated EVM contract address
_11
access(all) let evmContractAddress: EVM.EVMAddress
_11
/// Whether the asset is Cadence- or EVM-native
_11
access(all) let isCadenceNative: Bool
_11
}
This view allows a Cadence contract to specify which Solidity contract it is associated with.
You can see an example of how this view is implemented in
the ExampleNFT contract
in the Flow Non-Fungible Token repo.
If your EVM contract expects metadata to be passed from Cadence at the time of
bridging, you must implement the CrossVMMetadataViews.EVMBytesMetadata
view. You'll find this useful for Cadence-native NFTs with dynamic metadata.
This view will be resolved by the bridge and passed to your EVM contract
when the fulfillToEVM method is called.
How you handle the bridged bytes in your ERC721 implementation will be a matter
of overriding the _beforeFulfillment and/or _afterFulfillment hooks included in the
CrossVMBridgeERC721Fulfillment base contract.
Flow EVM-Native NFTs
If the NFT being onboarded to the bridge is native to Flow-EVM, then the associated contract's
minter resource must implement the FlowEVMBridgeCustomAssociationTypes.NFTFulfillmentMinter interface:
_29
/// Resource interface used by EVM-native NFT collections allowing for the fulfillment of NFTs from EVM into Cadence
A Capability with the FulfillFromEVM entitlement is required at the time of registration so the bridge
can fulfill NFTs bridged from EVM for the first time.
If you are registering a custom association for an NFT that is native to Cadence, meaning that your project distributes NFTs to users on the Cadence side,
then your ERC721 contract will need to implement the CrossVMBridgeERC721Fulfillment contract. This is
a required conformance that does three primary things:
Implements the mint/escrow pattern expected by the VM bridge
Allows for the passing of arbitrary abi-encodable metadata from the Cadence NFT at the time of bridging
Exposes two optional hooks enabling you to update the fulfilled token's URI with the provided metadata at the time of bridging
Here is the Solidity contract to implement:
_100
abstract contract CrossVMBridgeERC721Fulfillment is ICrossVMBridgeERC721Fulfillment, CrossVMBridgeCallable, ERC721 {
_100
_100
/**
_100
* Initializes the bridge EVM address such that only the bridge COA can call privileged methods
// No-op by default, meant to be overridden by implementations for things like processing
_100
// and setting metadata
_100
}
_100
}
Note the _beforeFulfillment() and _afterFulfillment() hooks are virtual, allowing implementations
to optionally override the methods and handle the provided metadata passed from your NFT if
EVMBytesMetadata is resolved at the time of bridging. Also, notice that the fulfillToEVM method
is onlyVMBridge, allowing on the VM bridge to call the method either minting the NFT if it does not
exist or transferring the NFT from escrow in a manner consistent with the bridge's mint/escrow pattern.
It's also recognized that the logic of some use cases may actually be compromised by the act of bridging, particularly
in such a unique partitioned runtime environment. Such cases might include those that do not maintain ownership
assumptions implicit to ecosystem standards.
For instance, an ERC721 implementation may reclaim a user's assets after a month of inactivity. In such a case, bridging
that ERC721 to Cadence would decouple the representation of ownership of the bridged NFT from the actual ownership in
the defining ERC721 contract after the token had been reclaimed - there would be no NFT in escrow for the bridge to
transfer on fulfillment of the NFT back to EVM. In such cases, projects may choose to opt-out of bridging, but
importantly must do so before the asset has been onboarded to the bridge.
For Solidity contracts, opting out is as simple as extending the BridgePermissions.sol abstract
contract which
defaults allowsBridging() to false. The bridge explicitly checks for the implementation of IBridgePermissions and
the value of allowsBridging() to validate that the contract has not opted out of bridging.
Similarly, Cadence contracts can implement the IBridgePermissions.cdc contract
interface.
This contract has a single method allowsBridging() with a default implementation returning false. Again, the bridge
explicitly checks for the implementation of IBridgePermissions and the value of allowsBridging() to validate that
the contract has not opted out of bridging. Should you later choose to enable bridging, you can simply override the
default implementation and return true.
In both cases, allowsBridging() gates onboarding to the bridge. Once onboarded - a permissionless operation anyone
can execute - the value of allowsBridging() is irrelevant and assets can move between VMs permissionlessly.