Cadence Owned Accounts (COAs) are EVM accounts owned by a Cadence resource 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 will focus on how to interact with COAs with Cadence.
In this guide we will walk through some basic examples creating and interacting with a COA in Cadence. Your specific usage of the COA resource will depend on your own application's requirements (e.g. 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 begin by importing 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):
// This assumes you are working in the in the Flow CLI, FCL, or another tool that supports this syntax
// The contract address should be configured in your project's `flow.json` file
import "EVM"
// ...
However, if you wish to use manual address imports instead, you can use the following format:
// 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, saving it to the user's storage and publishing a public capability to its reference:
import "EVM"
// Note that this is a simplified example & will not handle cases where the COA already exists
transaction() {
prepare(signer: auth(SaveValue) &Account) {
let storagePath = /storage/evm
let publicPath = /public/evm
// Create account & save to storage
let coa: @EVM.CadenceOwnedAccount <- EVM.createCadenceOwnedAccount()
signer.storage.save(<-coa, to: storagePath)
// Publish a public capability to the COA
let cap = signer.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(storagePath)
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 can also be used to query the balance, code, nonce, etc. of an account.
For our example, we could query the address of the COA we just created with the following script:
import "EVM"
access(all)
fun main(address: Address): EVM.EVMAddress {
// Get the desired Flow account holding the COA in storage
let account: = getAuthAccount<auth(Storage) &Account>(address)
// Borrow a reference to the COA from the storage location we saved it to
let coa = account.storage.borrow<&EVM.CadenceOwnedAccount>(
from: /storage/evm
) ?? panic("Could not borrow reference to the COA")
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:
import "EVM"
access(all)
fun main(address: Address): EVM.Balance {
// Get the desired Flow account holding the COA in storage
let account: = getAuthAccount<auth(Storage) &Account>(address)
// Borrow a reference to the COA from the storage location we saved it to
let coa = account.storage.borrow<&EVM.CadenceOwnedAccount>(
from: /storage/evm
) ?? panic("Could not borrow reference to the COA")
Tokens can be seamlessly transferred between the Flow EVM and Cadence environment using the deposit and withdraw functions provided by the COA resource. 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, representing 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:
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.
You can also deposit tokens directly into other types of EVM accounts using the EVM.EVMAddress.deposit function. See the EVM contract documentation for more information.
The withdraw function takes a EVM.Balance struct as an argument, representing 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:
// Deposit the withdrawn tokens into the receiving vault
receiver.deposit(from: <-self.sentVault)
}
}
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 call a contract at a given EVM address with a hardcoded data payload, gas limit, and value using the signer's COA:
import "EVM"
transaction() {
let coa: auth(EVM.Call) &EVM.CadenceOwnedAccount
prepare(signer: auth(Storage) &Account) {
// Borrow an entitled reference to the COA from the storage location we saved it to
To deploy a contract to the EVM, you can use the deploy function provided by the COA resource. 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 using the signer's COA:
import "EVM"
transaction(code: String) {
let coa: auth(EVM.Deploy) &EVM.CadenceOwnedAccount
prepare(signer: auth(Storage) &Account) {
// Borrow an entitled reference to the COA from the storage location we saved it to