AvaCloud

How to transfer an L1 native token to C-Chain as an ERC-20 token?

Deploy L1 on AvaCloud and Enable Interoperability

In AvaCloud, choose "Interoperability" in left column. Activate it if not done so already.

Setup private key for deployment

$ cast wallet import my_wallet --interactive
 
$ cast wallet list
my_wallet (Local)
 
$ cast wallet address --account my_wallet
0xd1aA63B599E39ec6b076Ce5C052DF6609134210A

Clone the ICTT repo

$ git clone https://github.com/ava-labs/icm-services.git

Setup RPC URLs

$ export FUJI_RPC_URL=https://api.avax-test.network/ext/bc/C/rpc
$ export L1_RPC_URL=https://subnets.avax.network/avcdemo622/testnet/rpc

Deploy Wrapped Native Token Contract

$ export NATIVE_TOKEN_SYMBOL=TESTTOKEN
 
$ mkdir wtoken-foundry
$ cd wtoken-foundry
$ forge init
$ rm script/Counter.s.sol
$ rm src/Counter.sol
$ rm test/Counter.t.sol
$ curl https://raw.githubusercontent.com/ava-labs/wrapped-assets/refs/heads/main/WAVAX.sol -o src/WTOKEN.sol
$ sed -i "" "s/AVAX/$NATIVE_TOKEN_SYMBOL/g" src/WTOKEN.sol
 
# Note: Your wallet needs to have native gas tokens on the L1 to pay for the deployment of contracts.
$ cast balance --ether $(cast wallet address --account my_wallet) --rpc-url $L1_RPC_URL
999.639462169992879358
 
$ forge create \
--rpc-url $L1_RPC_URL \
--account my_wallet \
--broadcast \
src/WTOKEN.sol:W$NATIVE_TOKEN_SYMBOL
 
# Deployer: 0xd1aA63B599E39ec6b076Ce5C052DF6609134210A
# Deployed to: 0xe75B45bac19434526d6b787a4ae95300Fe1047A1
# Transaction hash: 0x68cf587b04b3e33f16d2761e6d009f86b6f60a52d43a24079836d28bbef25efb
 
# Grab address from "Deployed to:" output of last command
export WRAPPED_TOKEN_ADDRESS=0xe75B45bac19434526d6b787a4ae95300Fe1047A1
 
# Move back to project home directory
cd ..

Deploy TokenHome Contract on L1

$ cd icm-services
 
$ forge create \
--rpc-url $L1_RPC_URL \
--account my_wallet \
--broadcast \
icm-contracts/avalanche/ictt/TokenHome/NativeTokenHomeUpgradeable.sol:NativeTokenHomeUpgradeable \
--constructor-args 0
 
# Deployer: 0xd1aA63B599E39ec6b076Ce5C052DF6609134210A
# Deployed to: 0x36E9F643478F47a2306466b6A318de8577942d46
# Transaction hash: 0x7a0620788c8fb2367358d4c8d4d688056d2578e4f657e114a97f2b4f1951d503
 
# Get addresss from "Deployed to:" output line in above command
$ export TOKEN_HOME_ADDRESS=0x36E9F643478F47a2306466b6A318de8577942d46

Initialize TokenHome Contract

# This is the address that is allowed to perform upgrades in the future, likely the user's address
$ export TELEPORTER_MANAGER=$(cast wallet address --account my_wallet)
$ echo $TELEPORTER_MANAGER
0xd1aA63B599E39ec6b076Ce5C052DF6609134210A
 
# Get from AvaCloud Portal: Interoperability: AWM Relayers: Registry Contract Address
$ export L1_TELEPORTER_REGISTRY_ADDRESS=0xE329B5Ff445E4976821FdCa99D6897EC43891A6c
 
$ cast send \
--rpc-url $L1_RPC_URL \
--account my_wallet \
$TOKEN_HOME_ADDRESS \
'initialize(address,address,uint256,address)' \
$L1_TELEPORTER_REGISTRY_ADDRESS $TELEPORTER_MANAGER 1 $WRAPPED_TOKEN_ADDRESS
 
status               1 (success)
transactionHash      0x6b198907b82413b6e0b19d1e2a332c9599f7a75ffbb39744722cbda73969e192

Verify Wrapped Native Token ERC-20 Contract Address Is Properly Set In TokenHome Contract

# This value matches the ERC-20_ADDRESS shown below:
$ cast call --rpc-url $L1_RPC_URL $TOKEN_HOME_ADDRESS "getTokenAddress()(address)"
0xe75B45bac19434526d6b787a4ae95300Fe1047A1
 
$ echo $WRAPPED_TOKEN_ADDRESS
0xe75B45bac19434526d6b787a4ae95300Fe1047A1

Deploy TokenRemote Contract on C-Chain

$ forge create \
--rpc-url $FUJI_RPC_URL \
--account my_wallet \
--broadcast \
icm-contracts/avalanche/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol:ERC20TokenRemoteUpgradeable \
--constructor-args 0
 
# Deployer: 0xd1aA63B599E39ec6b076Ce5C052DF6609134210A
# Deployed to: 0xEB9A66f680aEB6c9957D378dbD0c7323aFfB008B
# Transaction hash: 0x7980f0c74751df2cf80203f36569d9f1aa7cd34f710c8b5fb295ea801e40953e
 
# Get addresss from "Deployed to:" output line in above command
$ export TOKEN_REMOTE_ADDRESS=0xEB9A66f680aEB6c9957D378dbD0c7323aFfB008B

Initialize TokenRemote Contract

$ export TOKEN_SYMBOL=TESTTOKEN
$ export TOKEN_NAME=TESTTOKEN
 
# Get teleporter registry address here: https://build.avax.network/docs/cross-chain/teleporter/addresses
$ export FUJI_TELEPORTER_REGISTRY_ADDRESS=0xF86Cb19Ad8405AEFa7d09C778215D2Cb6eBfB228
 
# Get L1 "Blockchain ID (Hex)" from AvaCloud Portal: Details: Network Details: Blockchain ID (Hex)
$ export TOKEN_HOME_BLOCKCHAIN_ID=0xa0d897e580cef0346681be651d6a3e733de9b82e71eb79b06a3b3df8769da5fa
 
$ cast send \
--rpc-url $FUJI_RPC_URL \
--account my_wallet \
$TOKEN_REMOTE_ADDRESS \
'initialize((address,address,uint256,bytes32,address,uint8),string,string,uint8)' \
"($FUJI_TELEPORTER_REGISTRY_ADDRESS,$TELEPORTER_MANAGER,1,$TOKEN_HOME_BLOCKCHAIN_ID,$TOKEN_HOME_ADDRESS,18)" \
$TOKEN_SYMBOL $TOKEN_NAME 18
 
status               1 (success)
transactionHash      0xea12a714746d03aeda316103a5fb16bb48e210a80842be2657e35483ec7f95cf

Register the remote contract with the home contract

$ cast send \
--rpc-url $FUJI_RPC_URL \
--account my_wallet \
$TOKEN_REMOTE_ADDRESS \
'registerWithHome((address, uint256) feeInfo)' \
"($TOKEN_HOME_ADDRESS, 0)"
 
status               1 (success)
transactionHash      0xc77dce4bae7e16768c7e17f2e48824f28774240f6ca5b86f701ffdc6922249a3

Verify register transaction was successful

# Get "Blockchain ID (Hex)" from https://explorer-test.avax.network/c-chain/details
$ export TOKEN_REMOTE_BLOCKCHAIN_ID=0x7fc93d85c6d62c5b2ac0b519c87010ea5294012d1e407030d6acd0021cac10d5
 
# First value "true" means the remote token transferrer is registered
$ cast call --account my_address \
--rpc-url $L1_RPC_URL \
$TOKEN_HOME_ADDRESS \
'getRemoteTokenTransferrerSettings(bytes32,address)(bool,uint256,uint256,bool)' \
$TOKEN_REMOTE_BLOCKCHAIN_ID $TOKEN_REMOTE_ADDRESS
 
true   # registered
0      # collateralNeeded
1      # tokenMultiplier
false  # multiplyOnRemote

Send 0.1 TESTTOKEN (native token) from L1 to C-Chain (ERC-20)

$ cast wallet import test_user --interactive
 
$ cast wallet list
my_wallet (Local)
test_user (Local)
 
$ export USER_ADDRESS=$(cast wallet address --account test_user)
$ echo $USER_ADDRESS
0xd2BbD2C9da58EC0B329fB85c802aeE45FbCd523b
 
# Note: The test user needs to have native gas tokens on L1 to make this transaction.
$ cast balance --ether $USER_ADDRESS --rpc-url $L1_RPC_URL
99.893940749999757630
 
$ cast send \
--rpc-url $L1_RPC_URL \
--account test_user \
--value $(cast to-wei 0.1) \
$TOKEN_HOME_ADDRESS \
'send((bytes32,address,address,address,uint256,uint256,uint256,address))' \
"($TOKEN_REMOTE_BLOCKCHAIN_ID,$TOKEN_REMOTE_ADDRESS,$USER_ADDRESS,0x0000000000000000000000000000000000000000,0,0,10000000,0x0000000000000000000000000000000000000000)"
 
# status               1 (success)
# transactionHash      0x222aa225c96d159c839d0827311609d1e6fc6ba21a5951d90a2898340ddc2c9e
 
# Teleporter Explorer: https://explorer-test.avax.network/teleporter/61a492ebb21aa22bc380861f88df3df4739e557d22d4e417b30b3d4cba7a94cd
# Source tx: https://explorer-test.avax.network/avcdemo622/tx/0x222aa225c96d159c839d0827311609d1e6fc6ba21a5951d90a2898340ddc2c9e
# Destination tx: https://explorer-test.avax.network/c-chain/tx/0xbecdfd92047a695bb00dfe65fde50ebf579ac29c00100b4f58fef4147b3d12c0
 
# On remote end, the TokenRemote contract IS the ERC-20:
# https://explorer-test.avax.network/c-chain/token/0xEB9A66f680aEB6c9957D378dbD0c7323aFfB008B

Send 0.02 TESTHUB (ERC-20) from C-Chain to L1 (native token)

# Note: The test user needs to have funds on C-Chain to make this transaction.
$ cast balance --ether $USER_ADDRESS --rpc-url $FUJI_RPC_URL
1.998369346997467686
 
$ cast send \
--rpc-url $FUJI_RPC_URL \
--account test_user \
$TOKEN_REMOTE_ADDRESS \
'approve(address,uint256)' \
$TOKEN_REMOTE_ADDRESS \
$(cast to-wei 0.02)
 
# status               1 (success)
# transactionHash      0x79a2eb780e285a6cf376019b14998ef1abc2c6bfcfcfa1382c767be250c0e01a
 
$ cast send \
--rpc-url $FUJI_RPC_URL \
--account test_user \
$TOKEN_REMOTE_ADDRESS \
'send((bytes32,address,address,address,uint256,uint256,uint256,address),uint256)' \
"($TOKEN_HOME_BLOCKCHAIN_ID,$TOKEN_HOME_ADDRESS,$USER_ADDRESS,0x0000000000000000000000000000000000000000,0,0,10000000,0x0000000000000000000000000000000000000000)" \
$(cast to-wei 0.02)
 
# status               1 (success)
# transactionHash      0x7770fd801f2caf39c07f64941946b84e9d2fdbb1148e25e72560dc3cf4210dae
 
# Teleporter Explorer: https://explorer-test.avax.network/teleporter/f7724e43bd884a2a18c1eca59ce06074e92c699041d7311242ebddce014714a7
# Source tx: https://explorer-test.avax.network/c-chain/tx/0x7770fd801f2caf39c07f64941946b84e9d2fdbb1148e25e72560dc3cf4210dae
# Destination tx: https://explorer-test.avax.network/avcdemo622/tx/0xa680b0b73fb2300a779474305a18066d761ebab766f67eb0a60681af74024591

Verify return of native tokens to user address (internal transaction)

$ export DEVELOPER_RPC_URL=<PUT_YOUR_VALUE_HERE>
$ export DESTINATION_TX_HASH=0xa680b0b73fb2300a779474305a18066d761ebab766f67eb0a60681af74024591
 
$ curl --silent -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"debug_traceTransaction","params":["'$DESTINATION_TX_HASH'", {"tracer": "callTracer"}],"id":1}' $DEVELOPER_RPC_URL | jq
 
# Look in "calls" section for a record that has "input": "0x" (TOKEN_HOME is sending native token to user address)
          {
            "from": "0x36e9f643478f47a2306466b6a318de8577942d46",
            "gas": "0x94a48e",
            "gasUsed": "0x0",
            "to": "0xd2bbd2c9da58ec0b329fb85c802aee45fbcd523b",
            "input": "0x",
            "value": "0x470de4df820000",
            "type": "CALL"
          }
 
$ cast to-fixed-point 18 0x470de4df820000
0.020000000000000000

If you need more help, explore our other articles or reach out to our support team via chat or email [email protected]. All examples provided are for demonstration purposes only.

Learn More About AvaCloud | Download Case Studies | Schedule an AvaCloud Demo