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)

Clone the ICTT repo

1$ git clone https://github.com/ava-labs/icm-contracts.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/qr0407m1ts/testnet/rpc

Deploy Wrapped Native Token Contract

Make sure your wallet has native tokens available on the L1 before doing this step.

1export NATIVE_TOKEN_SYMBOL=TESTHUB
2
3mkdir wtoken-foundry
4cd wtoken-foundry
5forge init
6rm script/Counter.s.sol
7rm src/Counter.sol
8rm test/Counter.t.sol
9curl https://raw.githubusercontent.com/ava-labs/wrapped-assets/refs/heads/main/WAVAX.sol -o src/WTOKEN.sol
10sed -i "" "s/AVAX/$NATIVE_TOKEN_SYMBOL/g" src/WTOKEN.sol
11
12forge create src/WTOKEN.sol:W$NATIVE_TOKEN_SYMBOL --rpc-url $L1_RPC_URL --account my_wallet --broadcast
13
14# Deployer: 0xd1aA63B599E39ec6b076Ce5C052DF6609134210A
15# Deployed to: 0x4C4b94adc6DD05F447c5b51A7FdB69E90dAbFe27
16# Transaction hash: 0xe298ff6bf142a1ecab2bb6ff61e4eebff90a3dcd247763390297fc43d4afdf39
17
18# Grab address from "Deployed to:" output of last command
19export WRAPPED_TOKEN_ADDRESS=0x4C4b94adc6DD05F447c5b51A7FdB69E90dAbFe27
20
21# Move back to project home directory
22cd ..

Deploy TokenHome Contract on L1

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

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: Interoperability: AWS Relayers: Registry Contract Address
7$ export L1_TELEPORTER_REGISTRY_ADDRESS=0x8488454c36BBB27856DC9F9F2B601FD4cAd26CB1
8
9$ cast send \
10--account my_wallet \
11--rpc-url $L1_RPC_URL \
12$TOKEN_HOME_ADDRESS 'initialize(address,address,uint256,address)' \
13$L1_TELEPORTER_REGISTRY_ADDRESS $TELEPORTER_MANAGER 1 $WRAPPED_TOKEN_ADDRESS
14
15status 1 (success)
16transactionHash 0x28f0fe55c452262954bf02461c371451da9591058e0a57a4026852347883536f

Verify Wrapped Native Token ERC20 Contract Address Is Properly Set In TokenHome Contract

1# This hex value with extra-zero padding matches the ERC20_ADDRESS shown below:
2$ cast call --rpc-url $L1_RPC_URL $TOKEN_HOME_ADDRESS "getTokenAddress()"
30x0000000000000000000000004c4b94adc6dd05f447c5b51a7fdb69e90dabfe27
4
5$ echo $WRAPPED_TOKEN_ADDRESS
60x4C4b94adc6DD05F447c5b51A7FdB69E90dAbFe27

Deploy TokenRemote Contract on C-Chain

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

Initialize TokenRemote Contract

1$ export TOKEN_SYMBOL=TESTHUB
2$ export TOKEN_NAME=TESTHUB
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: Details: Network Details: Blockchain ID (Hex)
8$ export TOKEN_HOME_BLOCKCHAIN_ID=0x00f91a24f9e592ceff7a8f9902264f6ec4ce7ae8fccf25469ce476242f4976d7
9
10$ cast send \
11--account my_wallet \
12--rpc-url $FUJI_RPC_URL \
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 0x342ecce14ffef6ab6eddcca226db669095c16d4b086230afc3bd21928741f7b2

Register the remote contract with the home contract

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

Verify register transaction was successful if this output is non-zero

1# Get "Blockchain ID (Hex)" from https://subnets-test.avax.network/c-chain
2$ export TOKEN_REMOTE_BLOCKCHAIN_ID=0x7fc93d85c6d62c5b2ac0b519c87010ea5294012d1e407030d6acd0021cac10d5
3
4$ cast call --account my_address \
5--rpc-url $L1_RPC_URL \
6$TOKEN_HOME_ADDRESS \
7'getRemoteTokenTransferrerSettings(bytes32,address)' \
8$TOKEN_REMOTE_BLOCKCHAIN_ID \
9$TOKEN_REMOTE_ADDRESS
10
110x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000

Send 0.1 TESTHUB (native token) from L1 to C-Chain (ERC20)

The following assumes USER_ADDRESS already been funded with native tokens.

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$ cast send \
12--account test_user \
13--rpc-url $L1_RPC_URL \
14--value $(cast to-wei 0.1) \
15$TOKEN_HOME_ADDRESS \
16'send((bytes32,address,address,address,uint256,uint256,uint256,address))' \
17"($TOKEN_REMOTE_BLOCKCHAIN_ID,$TOKEN_REMOTE_ADDRESS,$USER_ADDRESS,0x0000000000000000000000000000000000000000,0,0,10000000,0x0000000000000000000000000000000000000000)"
18
19# status 1 (success)
20# transactionHash 0x71ea2405c05bc84730593a9ca27169154961ecd2a3f17dc496451ca8659387b7
21
22# https://subnets-test.avax.network/qr0407m1ts/tx/0x71ea2405c05bc84730593a9ca27169154961ecd2a3f17dc496451ca8659387b7
23# https://subnets-test.avax.network/c-chain/tx/0x8f4e48f9ce885bf97049db77e75393884a0ecd7072c422f60112ff2c08db586e
24
25# On remote end, the TestRemote contract IS the ERC20:
26# https://testnet.snowtrace.io/token/0x9984F0B5CD6684408023E9db7c651f70A7d8893d?chainid=43113

Send 0.02 TESTHUB (ERC20) 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.000000000000000000
4
5$ cast send \
6--account test_user \
7--rpc-url $FUJI_RPC_URL \
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 0x5c78c869b72251dcf4d97941b0c5ad02a7c3b3521821da4797b398e511679ed6
15
16$ cast send \
17--account test_user \
18--rpc-url $FUJI_RPC_URL \
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 0x2105438725cd0584d3eb8a81dfd6d3946f968b2507f17ca25760366e42291fe8
26
27# https://subnets-test.avax.network/c-chain/tx/0x2105438725cd0584d3eb8a81dfd6d3946f968b2507f17ca25760366e42291fe8
28# https://subnets-test.avax.network/qr0407m1ts/tx/0xb4dd5c8105097f57c3b8e0d7f5b74ff48cf6674f454c0e11f10387c58c1b382d?tab=details

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

1$ export DEVELOPER_RPC_URL=<PUT_YOUR_VALUE_HERE>
2
3$ curl --silent -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"debug_traceTransaction","params":["0xb4dd5c8105097f57c3b8e0d7f5b74ff48cf6674f454c0e11f10387c58c1b382d", {"tracer": "callTracer"}],"id":1}' $DEVELOPER_RPC_URL | jq
4
5# Look in "calls" section for a record that has "input": "0x" (TOKEN_HOME is sending native token to user address)
6 {
7 "from": "0x20c5541eb19573f4c9044969de818706f70adbc7",
8 "gas": "0x948d02",
9 "gasUsed": "0x0",
10 "to": "0xd2bbd2c9da58ec0b329fb85c802aee45fbcd523b",
11 "input": "0x",
12 "value": "0x470de4df820000",
13 "type": "CALL"
14 }
15
16$ cast to-fixed-point 18 0x470de4df820000
170.020000000000000000

For any additional questions, please view our other knowledge base articles or contact a support team member via the chat button. Examples are for illustrative purposes only.

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