LLM Notice: This documentation site supports content negotiation for AI agents. Request any page with Accept: text/markdown or Accept: text/plain header to receive Markdown instead of HTML. Alternatively, append ?format=md to any URL. All markdown files are available at /md/ prefix paths. For all content in one file, visit /llms-full.txt
Cadence Owned Accounts (COAs) are EVM accounts that a Cadence resouce owns, and are used to interact with Flow EVM from Cadence.
COAs expose two interfaces for interaction: one on the Cadence side and one on the EVM side. In this guide, we focuses on how to interact with COAs with Cadence.
In this guide, we will walk through some basic examples that create and and interact with a COA in Cadence. Your specific usage of the COA resource will depend on your own application's requirements (for example, the COA resource may not live directly in /storage/evm as in these examples, but may instead be a part of a more complex resource structure).
The CadenceOwnedAccount resource is a part of the EVM system contract, so to use any of these functions, you will need to import the EVM contract into your Cadence code.
To import the EVM contract into your Cadence code using the simple import syntax, you can use the following format (learn more about configuring contracts in flow.jsonhere):
_10
// This assumes you are working in the in the Flow CLI, FCL, or another tool that supports this syntax
_10
// The contract address should be configured in your project's `flow.json` file
_10
import "EVM"
_10
// ...
However, if you wish to use manual address imports instead, you can use the following format:
_10
// Must use the correct address based on the network you are interacting with
To create a COA, we can use the createCadenceOwnedAccount function from the EVM contract. This function takes no
arguments and returns a new CadenceOwnedAccount resource which represents this newly-created EVM account.
For example, we can create this COA in a transaction, save it to the user's storage, and publish a public capability to its reference:
create_coa.cdc
_17
import "EVM"
_17
_17
// Note that this is a simplified example & will not handle cases where the COA already exists
It is possible to create a new Cadence account and COA within the same transaction. Another account will need to sign and pay for this transaction, but any account will do. A common process is to set up a backend service to handle this function.
info
During the singular transaction in which an account is created, the AuthAccount object for the newly-created account is present. As a result, the creating account can access and modify the new account's storage only during this transaction.
First, you'll need to use the CLI to generate keys for the new account. Then, run the following transaction to create the Cadence Account and COA at one time.
warning
This is a very minimal example. You may wish to set up vaults and perform other actions during account creation.
To get the EVM address of a COA, you can use the address function from the EVM contract. This function returns the EVM address of the COA as an EVM.Address struct. This struct is used to represent addresses within Flow EVM and you cna also use it to query the balance, code, nonce, and so on of an account.
For our example, we could query the address of the COA we just created with the following script:
get_coa_address.cdc
_16
import "EVM"
_16
_16
access(all)
_16
fun main(address: Address): EVM.EVMAddress {
_16
// Get the desired Flow account holding the COA in storage
_16
let account = getAuthAccount<auth(Storage) &Account>(address)
_16
_16
// Borrow a reference to the COA from the storage location we saved it to
_16
let coa = account.storage.borrow<&EVM.CadenceOwnedAccount>(
_16
from: /storage/evm
_16
) ?? panic("Could not borrow reference to the signer's CadenceOwnedAccount (COA). "
_16
.concat("Ensure the signer account has a COA stored in the canonical /storage/evm path"))
_16
_16
// Return the EVM address of the COA
_16
return coa.address()
_16
}
If you'd prefer the hex representation of the address, you instead return with the EVMAddress.toString() function:
_10
return coa.address().toString()
The above will return the EVM address as a string; however, Cadence does not prefix hex strings with 0x.
Like any other Flow EVM or Cadence account, COAs possess a balance of FLOW tokens. To get the current balance of our COA, we can use the COA's balance function. It will return a EVM.Balance struct for the account - these are used to represent balances within Flow EVM.
This script will query the current balance of our newly created COA:
get_coa_balance.cdc
_16
import "EVM"
_16
_16
access(all)
_16
fun main(address: Address): EVM.Balance {
_16
// Get the desired Flow account holding the COA in storage
_16
let account = getAuthAccount<auth(Storage) &Account>(address)
_16
_16
// Borrow a reference to the COA from the storage location we saved it to
_16
let coa = account.storage.borrow<&EVM.CadenceOwnedAccount>(
_16
from: /storage/evm
_16
) ?? panic("Could not borrow reference to the signer's CadenceOwnedAccount (COA). "
_16
.concat("Ensure the signer account has a COA stored in the canonical /storage/evm path"))
_16
_16
// Get the current balance of this COA
_16
return coa.balance()
_16
}
You can also easily get the UFix64 FLOW balance of any EVM address with this script:
get_coa_balance_as_ufix64.cdc
_10
import "EVM"
_10
_10
access(all)
_10
fun main(addressHex: String): UFix64 {
_10
let addr = EVM.addressFromString(addressHex)
_10
return addr.balance().inFLOW()
_10
}
The above script is helpful if you already know the COA address and can provide the hex representation directly.
You can seamlessly transfer tokens between the Flow EVM and Cadence environment with the deposit and withdraw functions that the COA resource provides. Anybody with a valid reference to a COA may deposit Flow tokens into a it, however only someone with the Owner or Withdraw entitlements can withdraw tokens.
The deposit function takes a FlowToken.Vault resource as an argument, which represents the tokens to deposit. It will transfer the tokens from the vault into the COA's balance.
This transaction will withdraw Flow tokens from a user's Cadence vault and deposit them into their COA:
deposit_to_coa.cdc
_27
import "EVM"
_27
import "FungibleToken"
_27
import "FlowToken"
_27
_27
transaction(amount: UFix64) {
_27
let coa: &EVM.CadenceOwnedAccount
_27
let sentVault: @FlowToken.Vault
_27
_27
prepare(signer: auth(BorrowValue) &Account) {
_27
// Borrow the public capability to the COA from the desired account
_27
// This script could be modified to deposit into any account with a `EVM.CadenceOwnedAccount` capability
This is a basic example which only transfers tokens between a single user's COA & Flow account. You can easily modify it to transfer these tokens between any arbitrary accounts.
You can also deposit tokens directly into other types of EVM accounts with the EVM.EVMAddress.deposit function. See the EVM contract documentation for more information.
The withdraw function takes a EVM.Balance struct as an argument, which represents the amount of Flow tokens to withdraw, and returns a FlowToken.Vault resource with the withdrawn tokens.
We can run the following transaction to withdraw Flow tokens from a user's COA and deposit them into their Flow vault:
withdraw_from_coa.cdc
_32
import "EVM"
_32
import "FungibleToken"
_32
import "FlowToken"
_32
_32
transaction(amount: UFix64) {
_32
let sentVault: @FlowToken.Vault
_32
let receiver: &{FungibleToken.Receiver}
_32
_32
prepare(signer: auth(BorrowValue) &Account) {
_32
// Borrow a reference to the COA from the storage location we saved it to with the `EVM.Withdraw` entitlement
_32
let coa = signer.storage.borrow<auth(EVM.Withdraw) &EVM.CadenceOwnedAccount>(
_32
from: /storage/evm
_32
) ?? panic("Could not borrow reference to the signer's CadenceOwnedAccount (COA). "
_32
.concat("Ensure the signer account has a COA stored in the canonical /storage/evm path"))
_32
_32
// We must create a `EVM.Balance` struct to represent the amount of Flow tokens to withdraw
_32
let withdrawBalance = EVM.Balance(attoflow: 0)
_32
withdrawBalance.setFLOW(flow: amount)
_32
_32
// Withdraw the balance from the COA, we will use this later to deposit into the receiving account
// Deposit the withdrawn tokens into the receiving vault
_32
self.receiver.deposit(from: <-self.sentVault)
_32
}
_32
}
info
This is a basic example which only transfers tokens between a single user's COA & Flow account. It can be easily
modified to transfer these tokens between any arbitrary accounts.
To interact with smart contracts on the EVM, you can use the call function provided by the COA resource. This function
takes the EVM address of the contract you want to call, the data you want to send, the gas limit, and the value you want
to send. It will return a EVM.Result struct with the result of the call - you will need to handle this result in your
Cadence code.
This transaction will use the signer's COA to call a contract method with the defined signature and args at a given EVM
address, executing with the provided gas limit and value:
call.cdc
_47
import "EVM"
_47
_47
/// Calls the function with the provided signature and args at the target contract address using
_47
/// the defined gas limit and transmitting the provided value.
) ?? panic("Could not borrow reference to the signer's CadenceOwnedAccount (COA). "
_47
.concat("Ensure the signer account has a COA stored in the canonical /storage/evm path"))
_47
}
_47
_47
execute {
_47
// Deserialize the EVM address from the hex string
_47
let contractAddress = EVM.addressFromString(evmContractHex)
_47
// Construct the calldata from the signature and arguments
_47
let calldata = EVM.encodeABIWithSignature(
_47
signature,
_47
args
_47
)
_47
// Define the value as EVM.Balance struct
_47
let value = EVM.Balance(attoflow: flowValue)
_47
// Call the contract at the given EVM address with the given data, gas limit, and value
_47
// These values could be configured through the transaction arguments or other means
_47
// however, for simplicity, we will hardcode them here
_47
let result: EVM.Result = self.coa.call(
_47
to: contractAddress,
_47
data: calldata,
_47
gasLimit: gasLimit,
_47
value: value
_47
)
_47
_47
// Revert the transaction if the call was not successful
_47
// Note: a failing EVM call will not automatically revert the Cadence transaction
_47
// and it is up to the developer to use this result however it suits their application
_47
assert(
_47
result.status == EVM.Status.successful,
_47
message: "EVM call to ".concat(evmContractHex)
_47
.concat(" and signature ").concat(signature)
_47
.concat(" failed with error code ").concat(result.errorCode.toString())
_47
.concat(": ").concat(result.errorMessage)
_47
)
_47
}
_47
}
info
Notice that the calldata is encoded in the scope of the transaction. While developers can encode the calldata
outside the scope of the transaction and pass the encoded data as an argument, doing so compromises the
human-readability of Cadence transactions.
It's encouraged to either define transactions for each COA call and encoded the hardcoded EVM signature and arguments,
or to pass in the human-readable arguments and signature and encode the calldata within the transaction. This ensures a
more interpretable and therefore transparent transaction.
Similar to when you trasnfer ETH and other native value in other EVMs, you'll want to call to the target EVM address with empty calldata and provide the transfer value.
transfer_evm_flow.cdc
_42
import "EVM"
_42
_42
/// Transfers FLOW to another EVM address from the signer's COA
_42
///
_42
/// @param to: the serialized EVM address of the recipient
As covered in the Batched EVM transactions walkthrough, you can script multiple EVM calls in a single Cadence transaction. Compared to the single ERC721 transfer, to bulk send multiple tokens isn't much more code and allows for greater utility out of a single transaction. Below is an example of a bulk ERC721 token transfer.
erc721_bulk_transfer.cdc
_48
import "EVM"
_48
_48
/// Bulk transfers ERC721 tokens from the signer's COA to the named recipient. All tokens must be from
_48
/// the same collection and sent to the same recipient.
_48
///
_48
/// @param erc721AddressHex: the serialized EVM address of the ERC721 contract
_48
/// @param to: the serialized EVM address of the recipient
_48
/// @param ids: an array of IDs to send from the signer's COA to the recipient
_48
transaction(erc721AddressHex: String, to: String, ids: [UInt256]) {
_48
let coa: auth(EVM.Call) &EVM.CadenceOwnedAccount
_48
_48
prepare(signer: auth(BorrowValue) &Account) {
_48
// Borrow an entitled reference to the COA from the canonical storage location
To deploy a contract to the EVM, you can use the deploy function that the COA resource provides. This function takes the contract code, gas limit, and value you want to send. It will return the EVM address of the newly deployed contract.
This transaction will deploy a contract with the given code with the signer's COA:
deploy_evm_contract.cdc
_22
import "EVM"
_22
_22
transaction(bytecode: String) {
_22
let coa: auth(EVM.Deploy) &EVM.CadenceOwnedAccount
_22
_22
prepare(signer: auth(BorrowValue) &Account) {
_22
// Borrow an entitled reference to the COA from the storage location we saved it to