Hello World with Token Transfer

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.
1
contract MsgExampleBasicTransfer is MessageApp {
2
using SafeERC20 for IERC20;
3
4
event MessageWithTransferReceived(
5
address sender,
6
address token,
7
uint256 amount,
8
uint64 srcChainId,
9
bytes note
10
);
11
event MessageWithTransferRefunded(
12
address sender,
13
address token,
14
uint256 amount,
15
bytes note
16
);
17
18
// acccount, token -> balance
19
mapping(address => mapping(address => uint256)) public balances;
20
21
constructor(address _messageBus) MessageApp(_messageBus) {}
22
23
// called by user on source chain to send token with note to destination chain
24
function sendTokenWithNote(
25
address _dstContract,
26
address _token,
27
uint256 _amount,
28
uint64 _dstChainId,
29
uint64 _nonce,
30
uint32 _maxSlippage,
31
bytes calldata _note,
32
MsgDataTypes.BridgeSendType _bridgeSendType
33
) external payable {
34
IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
35
bytes memory message = abi.encode(msg.sender, _note);
36
sendMessageWithTransfer(
37
_dstContract,
38
_token,
39
_amount,
40
_dstChainId,
41
_nonce,
42
_maxSlippage,
43
message,
44
_bridgeSendType,
45
msg.value
46
);
47
}
48
49
// called by MessageBus on the destination chain to receive message with token
50
// transfer, record and emit info.
51
// the associated token transfer is guaranteed to have already been received
52
function executeMessageWithTransfer(
53
address, // srcContract
54
address _token,
55
uint256 _amount,
56
uint64 _srcChainId,
57
bytes memory _message,
58
address // executor
59
) external payable override onlyMessageBus returns (ExecutionStatus) {
60
(address sender, bytes memory note) = abi.decode(
61
(_message),
62
(address, bytes)
63
);
64
balances[sender][_token] += _amount;
65
emit MessageWithTransferReceived(
66
sender,
67
_token,
68
_amount,
69
_srcChainId,
70
note
71
);
72
return ExecutionStatus.Success;
73
}
74
75
// called by MessageBus on the source chain to handle message with
76
// failed associated token transfer.
77
// the failed token transfer is guaranteed to have already been refunded
78
function executeMessageWithTransferRefund(
79
address _token,
80
uint256 _amount,
81
bytes calldata _message,
82
address // executor
83
) external payable override onlyMessageBus returns (ExecutionStatus) {
84
(address sender, bytes memory note) = abi.decode(
85
(_message),
86
(address, bytes)
87
);
88
IERC20(_token).safeTransfer(sender, _amount);
89
emit MessageWithTransferRefunded(sender, _token, _amount, note);
90
return ExecutionStatus.Success;
91
}
92
93
// called by user on destination chain to withdraw tokens
94
function withdraw(address _token, uint256 _amount) external {
95
balances[msg.sender][_token] -= _amount;
96
IERC20(_token).safeTransfer(msg.sender, _amount);
97
}
98
}