Batch Transfer
Here is an example app that sends tokens from one sender at the source chain to multiple receivers at the destination chain through a single cross-chain token transfer.
- 1.The sender calls
batchTransfer
at the source chain, which internally calls app framework'ssendMessageWithTransfer
to send tokens and a message specifying a list of <receivers, amounts> to the app contract at the destination chain. - 2.The receiver side implements the
executeMessageWithTransfer
interface to handle the batch transfer message, and distribute received tokens according to the message content. It also internally callssendMessage
to send a receipt back to the source chain app contract. - 3.The sender side implements the
executeMessage
interface to handle the receipt message.
1
contract BatchTransfer is MessageApp {
2
using SafeERC20 for IERC20;
3
4
struct TransferRequest {
5
uint64 nonce;
6
address[] accounts;
7
uint256[] amounts;
8
address sender;
9
}
10
11
enum TransferStatus {
12
Null,
13
Success,
14
Fail
15
}
16
17
struct TransferReceipt {
18
uint64 nonce;
19
TransferStatus status;
20
}
21
22
constructor(address _messageBus) MessageApp(_messageBus) {}
23
24
// ============== functions and states on source chain ==============
25
26
uint64 nonce;
27
28
struct BatchTransferStatus {
29
bytes32 h; // hash(receiver, dstChainId)
30
TransferStatus status;
31
}
32
// nonce -> BatchTransferStatus
33
mapping(uint64 => BatchTransferStatus) public status;
34
35
modifier onlyEOA() {
36
require(msg.sender == tx.origin, "Not EOA");
37
_;
38
}
39
40
// called by sender on source chain to send tokens to a list of
41
// <_accounts, _amounts> on the destination chain
42
function batchTransfer(
43
address _dstContract, // BatchTransfer contract address at the dst chain
44
address _token,
45
uint256 _amount,
46
uint64 _dstChainId,
47
uint32 _maxSlippage,
48
MsgDataTypes.BridgeSendType _bridgeSendType,
49
address[] calldata _accounts,
50
uint256[] calldata _amounts
51
) external payable onlyEOA {
52
uint256 totalAmt;
53
for (uint256 i = 0; i < _amounts.length; i++) {
54
totalAmt += _amounts[i];
55
}
56
uint256 minRecv = _amount - (_amount * _maxSlippage) / 1e6;
57
require(minRecv > totalAmt, "invalid maxSlippage");
58
nonce += 1;
59
status[nonce] = BatchTransferStatus({
60
h: keccak256(abi.encodePacked(_dstContract, _dstChainId)),
61
status: TransferStatus.Null
62
});
63
IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
64
bytes memory message = abi.encode(
65
TransferRequest({
66
nonce: nonce,
67
accounts: _accounts,
68
amounts: _amounts,
69
sender: msg.sender
70
})
71
);
72
// send token and message to the destination chain
73
sendMessageWithTransfer(
74
_dstContract,
75
_token,
76
_amount,
77
_dstChainId,
78
nonce,
79
_maxSlippage,
80
message,
81
_bridgeSendType,
82
msg.value
83
);
84
}
85
86
// called by MessageBus on the source chain to handle token transfer failures
87
// (e.g., due to bad slippage).
88
// the associated token transfer is guaranteed to have already been refunded
89
function executeMessageWithTransferRefund(
90
address _token,
91
uint256 _amount,
92
bytes calldata _message,
93
address // executor
94
) external payable override onlyMessageBus returns (ExecutionStatus) {
95
TransferRequest memory transfer = abi.decode(
96
(_message),
97
(TransferRequest)
98
);
99
IERC20(_token).safeTransfer(transfer.sender, _amount);
100
return ExecutionStatus.Success;
101
}
102
103
// called by MessageBus on the source chain to receive receipts
104
function executeMessage(
105
address _sender,
106
uint64 _srcChainId,
107
bytes memory _message,
108
address // executor
109
) external payable override onlyMessageBus returns (ExecutionStatus) {
110
TransferReceipt memory receipt = abi.decode(
111
(_message),
112
(TransferReceipt)
113
);
114
require(
115
status[receipt.nonce].h ==
116
keccak256(abi.encodePacked(_sender, _srcChainId)),
117
"invalid message"
118
);
119
status[receipt.nonce].status = receipt.status;
120
return ExecutionStatus.Success;
121
}
122
123
// ============== functions on destination chain ==============
124
125
// called by MessageBus on destination chain to handle batchTransfer message by
126
// distributing tokens to receivers and sending receipt.
127
// the lump sum token transfer associated with the message is guaranteed to have
128
// already been received.
129
function executeMessageWithTransfer(
130
address _srcContract,
131
address _token,
132
uint256 _amount,
133
uint64 _srcChainId,
134
bytes memory _message,
135
address // executor
136
) external payable override onlyMessageBus returns (ExecutionStatus) {
137
TransferRequest memory transfer = abi.decode(
138
(_message),
139
(TransferRequest)
140
);
141
uint256 totalAmt;
142
for (uint256 i = 0; i < transfer.accounts.length; i++) {
143
IERC20(_token).safeTransfer(
144
transfer.accounts[i],
145
transfer.amounts[i]
146
);
147
totalAmt += transfer.amounts[i];
148
}
149
uint256 remainder = _amount - totalAmt;
150
if (_amount > totalAmt) {
151
// transfer the remainder of the money to the sender as a fee for
152
// executing this transfer
153
IERC20(_token).safeTransfer(transfer.sender, remainder);
154
}
155
bytes memory message = abi.encode(
156
TransferReceipt({
157
nonce: transfer.nonce,
158
status: TransferStatus.Success
159
})
160
);
161
// send receipt back to the source chain contract
162
sendMessage(_srcContract, _srcChainId, message, msg.value);
163
return ExecutionStatus.Success;
164
}
165
166
// called by MessageBus if handleMessageWithTransfer above got reverted
167
function executeMessageWithTransferFallback(
168
address _srcContract,
169
address _token,
170
uint256 _amount,
171
uint64 _srcChainId,
172
bytes memory _message,
173
address // executor
174
) external payable override onlyMessageBus returns (ExecutionStatus) {
175
TransferRequest memory transfer = abi.decode(
176
(_message),
177
(TransferRequest)
178
);
179
IERC20(_token).safeTransfer(transfer.sender, _amount);
180
bytes memory message = abi.encode(
181
TransferReceipt({
182
nonce: transfer.nonce,
183
status: TransferStatus.Fail
184
})
185
);
186
// send receipt back to the source chain contract
187
sendMessage(_srcContract, _srcChainId, message, msg.value);
188
return ExecutionStatus.Success;
189
}
190
}
Last modified 1yr ago