Here is a basic hello-world example that sends and receives cross-chain messages with associated token transfers:
Users call sendTokenWithNote on the source chain to send some tokens with an arbitrary bytes note to the destination chain
The receiver side implements executeMessageWithTransfer, which records received token balances for each sender, and emits events with transfer info including the note.
.
contract MsgExampleBasicTransfer is MessageApp {
using SafeERC20 for IERC20;
event MessageWithTransferReceived(
address sender,
address token,
uint256 amount,
uint64 srcChainId,
bytes note
);
event MessageWithTransferRefunded(
address sender,
address token,
uint256 amount,
bytes note
);
// acccount, token -> balance
mapping(address => mapping(address => uint256)) public balances;
constructor(address _messageBus) MessageApp(_messageBus) {}
// called by user on source chain to send token with note to destination chain
function sendTokenWithNote(
address _dstContract,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce,
uint32 _maxSlippage,
bytes calldata _note,
MsgDataTypes.BridgeSendType _bridgeSendType
) external payable {
IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
bytes memory message = abi.encode(msg.sender, _note);
sendMessageWithTransfer(
_dstContract,
_token,
_amount,
_dstChainId,
_nonce,
_maxSlippage,
message,
_bridgeSendType,
msg.value
);
}
// called by MessageBus on the destination chain to receive message with token
// transfer, record and emit info.
// the associated token transfer is guaranteed to have already been received
function executeMessageWithTransfer(
address, // srcContract
address _token,
uint256 _amount,
uint64 _srcChainId,
bytes memory _message,
address // executor
) external payable override onlyMessageBus returns (ExecutionStatus) {
(address sender, bytes memory note) = abi.decode(
(_message),
(address, bytes)
);
balances[sender][_token] += _amount;
emit MessageWithTransferReceived(
sender,
_token,
_amount,
_srcChainId,
note
);
return ExecutionStatus.Success;
}
// called by MessageBus on the source chain to handle message with
// failed associated token transfer.
// the failed token transfer is guaranteed to have already been refunded
function executeMessageWithTransferRefund(
address _token,
uint256 _amount,
bytes calldata _message,
address // executor
) external payable override onlyMessageBus returns (ExecutionStatus) {
(address sender, bytes memory note) = abi.decode(
(_message),
(address, bytes)
);
IERC20(_token).safeTransfer(sender, _amount);
emit MessageWithTransferRefunded(sender, _token, _amount, note);
return ExecutionStatus.Success;
}
// called by user on destination chain to withdraw tokens
function withdraw(address _token, uint256 _amount) external {
balances[msg.sender][_token] -= _amount;
IERC20(_token).safeTransfer(msg.sender, _amount);
}
}