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

1$ cast wallet import my_wallet --interactive
2
3$ cast wallet list
4my_wallet (Local)
5
6$ cast wallet address --account my_wallet
70xd1aA63B599E39ec6b076Ce5C052DF6609134210A

Clone the ICTT repo

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

Setup RPC URLs

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

Deploy Wrapped Native Token Contract

1$ export NATIVE_TOKEN_SYMBOL=TESTTOKEN
2
3$ mkdir wtoken-foundry
4$ cd wtoken-foundry
5$ forge init
6$ rm script/Counter.s.sol
7$ rm src/Counter.sol
8$ rm test/Counter.t.sol
9$ curl https://raw.githubusercontent.com/ava-labs/wrapped-assets/refs/heads/main/WAVAX.sol -o src/WTOKEN.sol
10$ sed -i "" "s/AVAX/$NATIVE_TOKEN_SYMBOL/g" src/WTOKEN.sol
11
12# Note: Your wallet needs to have native gas tokens on the L1 to pay for the deployment of contracts.
13$ cast balance --ether $(cast wallet address --account my_wallet) --rpc-url $L1_RPC_URL
14999.639462169992879358
15
16$ forge create \
17--rpc-url $L1_RPC_URL \
18--account my_wallet \
19--broadcast \
20src/WTOKEN.sol:W$NATIVE_TOKEN_SYMBOL
21
22# Deployer: 0xd1aA63B599E39ec6b076Ce5C052DF6609134210A
23# Deployed to: 0xe75B45bac19434526d6b787a4ae95300Fe1047A1
24# Transaction hash: 0x68cf587b04b3e33f16d2761e6d009f86b6f60a52d43a24079836d28bbef25efb
25
26# Grab address from "Deployed to:" output of last command
27export WRAPPED_TOKEN_ADDRESS=0xe75B45bac19434526d6b787a4ae95300Fe1047A1
28
29# Move back to project home directory
30cd ..

Deploy TokenHome Contract on L1

1$ cd icm-services
2
3$ forge create \
4--rpc-url $L1_RPC_URL \
5--account my_wallet \
6--broadcast \
7icm-contracts/avalanche/ictt/TokenHome/NativeTokenHomeUpgradeable.sol:NativeTokenHomeUpgradeable \
8--constructor-args 0
9
10# Deployer: 0xd1aA63B599E39ec6b076Ce5C052DF6609134210A
11# Deployed to: 0x36E9F643478F47a2306466b6A318de8577942d46
12# Transaction hash: 0x7a0620788c8fb2367358d4c8d4d688056d2578e4f657e114a97f2b4f1951d503
13
14# Get addresss from "Deployed to:" output line in above command
15$ export TOKEN_HOME_ADDRESS=0x36E9F643478F47a2306466b6A318de8577942d46

Initialize TokenHome Contract

1# This is the address that is allowed to perform upgrades in the future, likely the user's address
2$ export TELEPORTER_MANAGER=$(cast wallet address --account my_wallet)
3$ echo $TELEPORTER_MANAGER
40xd1aA63B599E39ec6b076Ce5C052DF6609134210A
5
6# Get from AvaCloud Portal: Interoperability: AWM Relayers: Registry Contract Address
7$ export L1_TELEPORTER_REGISTRY_ADDRESS=0xE329B5Ff445E4976821FdCa99D6897EC43891A6c
8
9$ cast send \
10--rpc-url $L1_RPC_URL \
11--account my_wallet \
12$TOKEN_HOME_ADDRESS \
13'initialize(address,address,uint256,address)' \
14$L1_TELEPORTER_REGISTRY_ADDRESS $TELEPORTER_MANAGER 1 $WRAPPED_TOKEN_ADDRESS
15
16status 1 (success)
17transactionHash 0x6b198907b82413b6e0b19d1e2a332c9599f7a75ffbb39744722cbda73969e192

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

1# This value matches the ERC-20_ADDRESS shown below:
2$ cast call --rpc-url $L1_RPC_URL $TOKEN_HOME_ADDRESS "getTokenAddress()(address)"
30xe75B45bac19434526d6b787a4ae95300Fe1047A1
4
5$ echo $WRAPPED_TOKEN_ADDRESS
60xe75B45bac19434526d6b787a4ae95300Fe1047A1

Deploy TokenRemote Contract on C-Chain

1$ forge create \
2--rpc-url $FUJI_RPC_URL \
3--account my_wallet \
4--broadcast \
5icm-contracts/avalanche/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol:ERC20TokenRemoteUpgradeable \
6--constructor-args 0
7
8# Deployer: 0xd1aA63B599E39ec6b076Ce5C052DF6609134210A
9# Deployed to: 0xEB9A66f680aEB6c9957D378dbD0c7323aFfB008B
10# Transaction hash: 0x7980f0c74751df2cf80203f36569d9f1aa7cd34f710c8b5fb295ea801e40953e
11
12# Get addresss from "Deployed to:" output line in above command
13$ export TOKEN_REMOTE_ADDRESS=0xEB9A66f680aEB6c9957D378dbD0c7323aFfB008B

Initialize TokenRemote Contract

1$ export TOKEN_SYMBOL=TESTTOKEN
2$ export TOKEN_NAME=TESTTOKEN
3
4# Get teleporter registry address here: https://build.avax.network/docs/cross-chain/teleporter/addresses
5$ export FUJI_TELEPORTER_REGISTRY_ADDRESS=0xF86Cb19Ad8405AEFa7d09C778215D2Cb6eBfB228
6
7# Get L1 "Blockchain ID (Hex)" from AvaCloud Portal: Details: Network Details: Blockchain ID (Hex)
8$ export TOKEN_HOME_BLOCKCHAIN_ID=0xa0d897e580cef0346681be651d6a3e733de9b82e71eb79b06a3b3df8769da5fa
9
10$ cast send \
11--rpc-url $FUJI_RPC_URL \
12--account my_wallet \
13$TOKEN_REMOTE_ADDRESS \
14'initialize((address,address,uint256,bytes32,address,uint8),string,string,uint8)' \
15"($FUJI_TELEPORTER_REGISTRY_ADDRESS,$TELEPORTER_MANAGER,1,$TOKEN_HOME_BLOCKCHAIN_ID,$TOKEN_HOME_ADDRESS,18)" \
16$TOKEN_SYMBOL $TOKEN_NAME 18
17
18status 1 (success)
19transactionHash 0xea12a714746d03aeda316103a5fb16bb48e210a80842be2657e35483ec7f95cf

Register the remote contract with the home contract

1$ cast send \
2--rpc-url $FUJI_RPC_URL \
3--account my_wallet \
4$TOKEN_REMOTE_ADDRESS \
5'registerWithHome((address, uint256) feeInfo)' \
6"($TOKEN_HOME_ADDRESS, 0)"
7
8status 1 (success)
9transactionHash 0xc77dce4bae7e16768c7e17f2e48824f28774240f6ca5b86f701ffdc6922249a3

Verify register transaction was successful

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

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

1$ cast wallet import test_user --interactive
2
3$ cast wallet list
4my_wallet (Local)
5test_user (Local)
6
7$ export USER_ADDRESS=$(cast wallet address --account test_user)
8$ echo $USER_ADDRESS
90xd2BbD2C9da58EC0B329fB85c802aeE45FbCd523b
10
11# Note: The test user needs to have native gas tokens on L1 to make this transaction.
12$ cast balance --ether $USER_ADDRESS --rpc-url $L1_RPC_URL
1399.893940749999757630
14
15$ cast send \
16--rpc-url $L1_RPC_URL \
17--account test_user \
18--value $(cast to-wei 0.1) \
19$TOKEN_HOME_ADDRESS \
20'send((bytes32,address,address,address,uint256,uint256,uint256,address))' \
21"($TOKEN_REMOTE_BLOCKCHAIN_ID,$TOKEN_REMOTE_ADDRESS,$USER_ADDRESS,0x0000000000000000000000000000000000000000,0,0,10000000,0x0000000000000000000000000000000000000000)"
22
23# status 1 (success)
24# transactionHash 0x222aa225c96d159c839d0827311609d1e6fc6ba21a5951d90a2898340ddc2c9e
25
26# Teleporter Explorer: https://explorer-test.avax.network/teleporter/61a492ebb21aa22bc380861f88df3df4739e557d22d4e417b30b3d4cba7a94cd
27# Source tx: https://explorer-test.avax.network/avcdemo622/tx/0x222aa225c96d159c839d0827311609d1e6fc6ba21a5951d90a2898340ddc2c9e
28# Destination tx: https://explorer-test.avax.network/c-chain/tx/0xbecdfd92047a695bb00dfe65fde50ebf579ac29c00100b4f58fef4147b3d12c0
29
30# On remote end, the TokenRemote contract IS the ERC-20:
31# https://explorer-test.avax.network/c-chain/token/0xEB9A66f680aEB6c9957D378dbD0c7323aFfB008B

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

1# Note: The test user needs to have funds on C-Chain to make this transaction.
2$ cast balance --ether $USER_ADDRESS --rpc-url $FUJI_RPC_URL
31.998369346997467686
4
5$ cast send \
6--rpc-url $FUJI_RPC_URL \
7--account test_user \
8$TOKEN_REMOTE_ADDRESS \
9'approve(address,uint256)' \
10$TOKEN_REMOTE_ADDRESS \
11$(cast to-wei 0.02)
12
13# status 1 (success)
14# transactionHash 0x79a2eb780e285a6cf376019b14998ef1abc2c6bfcfcfa1382c767be250c0e01a
15
16$ cast send \
17--rpc-url $FUJI_RPC_URL \
18--account test_user \
19$TOKEN_REMOTE_ADDRESS \
20'send((bytes32,address,address,address,uint256,uint256,uint256,address),uint256)' \
21"($TOKEN_HOME_BLOCKCHAIN_ID,$TOKEN_HOME_ADDRESS,$USER_ADDRESS,0x0000000000000000000000000000000000000000,0,0,10000000,0x0000000000000000000000000000000000000000)" \
22$(cast to-wei 0.02)
23
24# status 1 (success)
25# transactionHash 0x7770fd801f2caf39c07f64941946b84e9d2fdbb1148e25e72560dc3cf4210dae
26
27# Teleporter Explorer: https://explorer-test.avax.network/teleporter/f7724e43bd884a2a18c1eca59ce06074e92c699041d7311242ebddce014714a7
28# Source tx: https://explorer-test.avax.network/c-chain/tx/0x7770fd801f2caf39c07f64941946b84e9d2fdbb1148e25e72560dc3cf4210dae
29# Destination tx: https://explorer-test.avax.network/avcdemo622/tx/0xa680b0b73fb2300a779474305a18066d761ebab766f67eb0a60681af74024591

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

1$ export DEVELOPER_RPC_URL=<PUT_YOUR_VALUE_HERE>
2$ export DESTINATION_TX_HASH=0xa680b0b73fb2300a779474305a18066d761ebab766f67eb0a60681af74024591
3
4$ 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
5
6# Look in "calls" section for a record that has "input": "0x" (TOKEN_HOME is sending native token to user address)
7 {
8 "from": "0x36e9f643478f47a2306466b6a318de8577942d46",
9 "gas": "0x94a48e",
10 "gasUsed": "0x0",
11 "to": "0xd2bbd2c9da58ec0b329fb85c802aee45fbcd523b",
12 "input": "0x",
13 "value": "0x470de4df820000",
14 "type": "CALL"
15 }
16
17$ cast to-fixed-point 18 0x470de4df820000
180.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