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 4 my_wallet (Local) 5 6 $ cast wallet address --account my_wallet 7 0xd1aA63B599E39ec6b076Ce5C052DF6609134210A
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 14 999.639462169992879358 15 16 $ forge create \ 17 --rpc-url $L1_RPC_URL \ 18 --account my_wallet \ 19 --broadcast \ 20 src/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 27 export WRAPPED_TOKEN_ADDRESS=0xe75B45bac19434526d6b787a4ae95300Fe1047A1 28 29 # Move back to project home directory 30 cd ..
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 \ 7 icm-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 4 0xd1aA63B599E39ec6b076Ce5C052DF6609134210A 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 16 status 1 (success) 17 transactionHash 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)" 3 0xe75B45bac19434526d6b787a4ae95300Fe1047A1 4 5 $ echo $WRAPPED_TOKEN_ADDRESS 6 0xe75B45bac19434526d6b787a4ae95300Fe1047A1
Deploy TokenRemote Contract on C-Chain
1 $ forge create \ 2 --rpc-url $FUJI_RPC_URL \ 3 --account my_wallet \ 4 --broadcast \ 5 icm-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 18 status 1 (success) 19 transactionHash 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 8 status 1 (success) 9 transactionHash 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 11 true # registered 12 0 # collateralNeeded 13 1 # tokenMultiplier 14 false # 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 4 my_wallet (Local) 5 test_user (Local) 6 7 $ export USER_ADDRESS=$(cast wallet address --account test_user) 8 $ echo $USER_ADDRESS 9 0xd2BbD2C9da58EC0B329fB85c802aeE45FbCd523b 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 13 99.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 3 1.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 18 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