When transferring tokens using Rango cross-chain API, you could pass a random message from the source chain to the destination and call your contract on the destination. In order to do so, you need to pass your contracts on source & destination chains plus an arbitrary hex message. Here is a brief guide on what you need to do in terms of SDK usage and the smart contract side.
1. SDK Usage
You should specify sourceContract, destinationContract and imMessage arguments in both quote and swap methods if you want to pass a message from the source contract to the destination.
constswapResponse=awaitrangoClient.swap({ from: {"blockchain":"FANTOM","symbol":"FTM","address":null }, to: {"blockchain":"BSC","symbol":"BNB","address":null }, amount:"100000000000000000000", fromAddress: fromAddress, toAddress: fromAddress, disableEstimate:false, referrerAddress:null, referrerFee:null, slippage:'1.0', messagingProtocols: ['cbridge'], sourceContract:"<source contract address>", destinationContract:"<destination contract address>", imMessage: "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000007E8A8b130272430008eCa062419ACD8B423d339D"
})if (!!swapResponse && !swapResponse.error && swapResponse.resultType === "OK" && swapResponse.tx?.type === TransactionType.EVM) {
constevmTx=swapResponse.tx asEvmTransactionconst {value,txData} = evmTxconsole.log({value, txData})// pass value and txData to your own contract}
You could also limit messagingProtocols used to a custom list like ['cbridge']. (Please note that as the message is relayed alongside with token in a single transaction if you limit messaging protocols to cbridge, we use the same bridge for transferring tokens.
2. Smart Contract
Both dApp contracts on the source and destination chains should implement IRangoMessageReceiver interface. Rango will call handleRangoMessage function in case of SUCCESS, REFUND_IN_SOURCE or REFUND_IN_DESTINATION.
And here is a sample dApp contract for the demo purpose. (The demo is very simple and you should consider adding security considerations yourself)
CrosschainSampleApp.sol
// SPDX-License-Identifier: UNLICENSEDpragmasolidity 0.8.13;import"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";import"@openzeppelin/contracts/token/ERC20/IERC20.sol";interface IRangoMessageReceiver {enumProcessStatus { SUCCESS, REFUND_IN_SOURCE, REFUND_IN_DESTINATION }functionhandleRangoMessage(address_token,uint_amount,ProcessStatus_status,bytesmemory_message ) external;}contractCrosschainPurchaseAppisIRangoMessageReceiver {addresspayableconstant NULL_ADDRESS =payable(0x0000000000000000000000000000000000000000);structAppMessage { uint assetId; address buyer; }enumPurchaseType { BOUGHT, SOLD_OUT }eventNFTPurchaseStatus(uint assetId, address buyer, PurchaseType purchaseType);addresspayable rangoContract;constructor(address payable _rangoContract) { rangoContract = _rangoContract; }receive() externalpayable { }// Source chainfunctionbuyNFTCrosschain(bytescalldata rangoData) externalpayable {// 1. Do your own logic here// 2. send the money via Rango (bool success,bytesmemory retData) = rangoContract.call{value: msg.value}(rangoData);require(success,_getRevertMsg(retData)); }// Destination chainfunctionhandleRangoMessage(address_token,uint_amount,ProcessStatus_status,bytesmemory_message ) external { AppMessage memory m = abi.decode((_message), (AppMessage));if (m.assetId <10) {emitNFTPurchaseStatus(m.assetId, m.buyer, PurchaseType.SOLD_OUT);// Give the money back to userif (_token == NULL_ADDRESS) { (bool sent, ) = m.buyer.call{value: _amount}("");require(sent,"failed to send native"); } else { SafeERC20.safeTransfer(IERC20(_token), m.buyer, _amount); } } else {emitNFTPurchaseStatus(m.assetId, m.buyer, PurchaseType.BOUGHT);// give the purchased asset to user } }functionrefund(address_tokenAddress,uint256_amount) external { IERC20 ercToken =IERC20(_tokenAddress);uint balance = ercToken.balanceOf(address(this));require(balance >= _amount,'Insufficient balance'); SafeERC20.safeTransfer(IERC20(_tokenAddress), msg.sender, _amount); }functionrefundNative(uint256_amount) external {uint balance =address(this).balance;require(balance >= _amount,'Insufficient balance'); (bool sent, ) = msg.sender.call{value: _amount}("");require(sent,"failed to send native"); }function_getRevertMsg(bytesmemory_returnData) internalpurereturns (stringmemory) {// If the _res length is less than 68, then the transaction failed silently (without a revert message)if (_returnData.length <68) return'Transaction reverted silently';assembly {// Slice the sighash. _returnData :=add(_returnData,0x04) }return abi.decode(_returnData, (string)); // All that remains is the revert string }}
You need to deploy this contract on every chains needed and ask us to white list them in Rango Contract.
As you can see in the code above, here is how dApp calls Rango in the source chain:
// Source chainfunctionbuyNFTCrosschain(bytescalldata rangoData) externalpayable {// 1. Do your own logic here// 2. send the money via Rango (bool success,bytesmemory retData) = rangoContract.call{value: msg.value}(rangoData);require(success,_getRevertMsg(retData));}
And here is how dApp could handle Rango message:
functionhandleRangoMessage(address_token,uint_amount,ProcessStatus_status,bytesmemory_message) external { AppMessage memory m = abi.decode((_message), (AppMessage));// your logic here}