Cross Chain Transfer
Cross chain transfer is one of the most commonly used cases when it comes to cross chain verification. aelf already supports cross chain transfer functionality in its contract. This section will explain how to transfer tokens across chains. It assumes a side chain is already deployed and has been indexed by the main chain.
The transfer process will always use the same contract methods and follow these two steps:
- Initiate the transfer
- Receive the tokens
Prepare
There are a few preparation steps required before a cross chain transfer, which need to be done only once for each chain. If these steps are already completed, you can skip this part.
Let's say you want to transfer token FOO from chain A to chain B. Before you start, make sure you understand how cross chain transaction verification works. Any input containing MerklePath in the following steps means that cross chain verification is needed. Refer to the cross chain verification documentation for more details.
-
Validate
Token Contractaddress on chain ASend transaction
tx_1to theGenesis Contractwith the methodValidateSystemContractAddress. You need to providesystem_contract_hash_nameand the address of theToken Contract.tx_1should be successfully packed in the block.rpc ValidateSystemContractAddress(ValidateSystemContractAddressInput) returns (google.protobuf.Empty){}
message ValidateSystemContractAddressInput {
aelf.Hash system_contract_hash_name = 1;
aelf.Address address = 2;
} -
Register the token contract address of chain A on chain B
Create a proposal for the
RegisterCrossChainTokenContractAddressfor the default parliament organization on chain B. Refer to the Parliament contract documentation for more details. Apart from cross chain verification context, you also need to provide the origin data oftx_1and theToken Contractaddress on chain A.rpc RegisterCrossChainTokenContractAddress (RegisterCrossChainTokenContractAddressInput) returns (google.protobuf.Empty) {}
message RegisterCrossChainTokenContractAddressInput {
int32 from_chain_id = 1;
int64 parent_chain_height = 2;
bytes transaction_bytes = 3;
aelf.MerklePath merkle_path = 4;
aelf.Address token_contract_address = 5;
} -
Validate
TokenInfoof FOO on chain ASend transaction
tx_2to theToken Contractwith the methodValidateTokenInfoExistson chain A. You need to provideTokenInfoof FOO.tx_2should be successfully packed in the block.rpc ValidateTokenInfoExists(ValidateTokenInfoExistsInput) returns (google.protobuf.Empty){}
message ValidateTokenInfoExistsInput {
string symbol = 1;
string token_name = 2;
int64 total_supply = 3;
int32 decimals = 4;
aelf.Address issuer = 5;
bool is_burnable = 6;
int32 issue_chain_id = 7;
} -
Create token FOO on chain B
Send transaction
tx_3to theToken Contractwith the methodCrossChainCreateTokenon chain B. You need to provide the origin data oftx_2and the cross chain verification context oftx_2.rpc CrossChainCreateToken(CrossChainCreateTokenInput) returns (google.protobuf.Empty) {}
message CrossChainCreateTokenInput {
int32 from_chain_id = 1;
int64 parent_chain_height = 2;
bytes transaction_bytes = 3;
aelf.MerklePath merkle_path = 4;
}
Initiate the Transfer
On the token contract of the source chain, the CrossChainTransfer method is used to trigger the transfer:
rpc CrossChainTransfer (CrossChainTransferInput) returns (google.protobuf.Empty) { }
message CrossChainTransferInput {
aelf.Address to = 1;
string symbol = 2;
sint64 amount = 3;
string memo = 4;
int32 to_chain_id = 5;
int32 issue_chain_id = 6;
}
The fields of the input:
to: the target address to receive the tokensymbol: the symbol of the token to be transferredamount: the amount of the token to be transferredmemo: a memo field for this transferto_chain_id: the destination chain ID where the tokens will be receivedissue_chain_id: the chain ID where the token was issued
Receive on the Destination Chain
On the destination chain where the tokens need to be received, the CrossChainReceiveToken method is used to trigger the reception:
rpc CrossChainReceiveToken (CrossChainReceiveTokenInput) returns (google.protobuf.Empty) { }
message CrossChainReceiveTokenInput {
int32 from_chain_id = 1;
int64 parent_chain_height = 2;
bytes transfer_transaction_bytes = 3;
aelf.MerklePath merkle_path = 4;
}
rpc GetBoundParentChainHeightAndMerklePathByHeight (aelf.Int64Value) returns (CrossChainMerkleProofContext) {
option (aelf.is_view) = true;
}
message CrossChainMerkleProofContext {
int64 bound_parent_chain_height = 1;
aelf.MerklePath merkle_path_from_parent_chain = 2;
}
The fields of the input:
-
from_chain_id: the source chain ID from which the cross chain transfer was launched -
parent_chain_height: the height of the block on the main chain that contains theCrossChainTransfertransaction (for main chain to side chain transfer). For side chain to side chain or side chain to main chain transfer, it is the result ofGetBoundParentChainHeightAndMerklePathByHeight(input is the height of theCrossChainTransfer), accessible in thebound_parent_chain_heightfield. -
transfer_transaction_bytes: the serialized form of theCrossChainTransfertransaction. -
merkle_path: obtained from the source chain. The construction of merkle path data differs among cases:- Main chain to side chain transfer: merkle path from the main chain’s web API
GetMerklePathByTransactionIdAsync(withCrossChainTransfertransaction ID as input). - Side chain to side chain or side chain to main chain transfer:
- merkle path from the source chain’s web API
GetMerklePathByTransactionIdAsync(withCrossChainTransfertransaction ID as input). - output of
GetBoundParentChainHeightAndMerklePathByHeightmethod in theCrossChain Contract(withCrossChainTransfertransaction’s block height as input). Path nodes are in themerkle_path_from_parent_chainfield of theCrossChainMerkleProofContextobject. - Concatenate the above two merkle paths.
- merkle path from the source chain’s web API
- Main chain to side chain transfer: merkle path from the main chain’s web API