Skip to content

Commit 32391a4

Browse files
committed
initial implementation of chainlink crosschain module
1 parent 15052bc commit 32391a4

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed
+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
2+
library ChainlinkCrossChainStorage {
3+
4+
/// @custom:storage-location erc7201:token.minting.chainlinkcrosschain
5+
bytes32 public constant CHAINLINKCROSSCHAIN_STORAGE_POSITION =
6+
keccak256(abi.encode(uint256(keccak256("token.minting.chainlinkcrosschain.erc721")) - 1)) & ~bytes32(uint256(0xff));
7+
8+
struct Data {
9+
address router;
10+
address s_linkToken;
11+
}
12+
13+
function data() internal pure returns (Data storage data_) {
14+
bytes32 position = CHAINLINKCROSSCHAIN_STORAGE_POSITION;
15+
assembly {
16+
data_.slot := position
17+
}
18+
}
19+
20+
}
21+
22+
23+
contract ChainlinkCrossChain is CCIPReceiver, OwnerIsCreator {
24+
25+
address immutable s_linkToken;
26+
address immutable router;
27+
28+
constructor(address _router, address _link) {
29+
s_linkToken = _link;
30+
router = _router;
31+
}
32+
33+
function bridgeWithToken(
34+
address _destinationChain,
35+
address _recipient,
36+
bytes memory _data,
37+
address _token,
38+
uint256 _amount,
39+
bytes memory _extraArgs,
40+
) external {
41+
(uint256 _feeTokenAddress, ccipMessageExtraArgs) = abi.decode(_extraArgs, (uint256, bytes));
42+
43+
Client.EVM2AnyMessage memory evm2AnyMessage =
44+
_buildCCIPMessage(_receiver, _text, _token, _amount, _feeTokenAddress, ccipMessageExtraArgs);
45+
46+
// Initialize a router client instance to interact with cross-chain router
47+
IRouterClient router = IRouterClient(_chainLinkCrossChainStorage().router);
48+
49+
// Get the fee required to send the CCIP message
50+
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
51+
52+
if (fees > s_linkToken.balanceOf(address(this))) {
53+
revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
54+
}
55+
56+
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
57+
s_linkToken.approve(address(router), fees);
58+
59+
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
60+
IERC20(_token).approve(address(router), _amount);
61+
62+
// Send the message through the router and store the returned message ID
63+
messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage);
64+
65+
// Emit an event with message details
66+
emit MessageSent(
67+
messageId, _destinationChainSelector, _receiver, _text, _token, _amount, address(s_linkToken), fees
68+
);
69+
70+
// Return the message ID
71+
return messageId;
72+
}
73+
74+
/// @notice Sends data and transfer tokens to receiver on the destination chain.
75+
/// @notice Pay for fees in native gas.
76+
/// @dev Assumes your contract has sufficient native gas like ETH on Ethereum or POL on Polygon.
77+
/// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
78+
/// @param _receiver The address of the recipient on the destination blockchain.
79+
/// @param _text The string data to be sent.
80+
/// @param _token token address.
81+
/// @param _amount token amount.
82+
/// @return messageId The ID of the CCIP message that was sent.
83+
function sendMessagePayNative(
84+
uint64 _destinationChainSelector,
85+
address _receiver,
86+
string calldata _text,
87+
address _token,
88+
uint256 _amount
89+
)
90+
external
91+
onlyOwner
92+
onlyAllowlistedDestinationChain(_destinationChainSelector)
93+
validateReceiver(_receiver)
94+
returns (bytes32 messageId)
95+
{
96+
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
97+
// address(0) means fees are paid in native gas
98+
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(_receiver, _text, _token, _amount, address(0));
99+
100+
// Initialize a router client instance to interact with cross-chain router
101+
IRouterClient router = IRouterClient(this.getRouter());
102+
103+
// Get the fee required to send the CCIP message
104+
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
105+
106+
if (fees > address(this).balance) {
107+
revert NotEnoughBalance(address(this).balance, fees);
108+
}
109+
110+
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
111+
IERC20(_token).approve(address(router), _amount);
112+
113+
// Send the message through the router and store the returned message ID
114+
messageId = router.ccipSend{value: fees}(_destinationChainSelector, evm2AnyMessage);
115+
116+
// Emit an event with message details
117+
emit MessageSent(messageId, _destinationChainSelector, _receiver, _text, _token, _amount, address(0), fees);
118+
119+
// Return the message ID
120+
return messageId;
121+
}
122+
123+
124+
/// @notice Construct a CCIP message.
125+
/// @dev This function will create an EVM2AnyMessage struct with all the necessary information for programmable tokens transfer.
126+
/// @param _receiver The address of the receiver.
127+
/// @param _text The string data to be sent.
128+
/// @param _token The token to be transferred.
129+
/// @param _amount The amount of the token to be transferred.
130+
/// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas.
131+
/// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message.
132+
function _buildCCIPMessage(
133+
address _recipient
134+
bytes calldata _data,
135+
address _token,
136+
uint256 _amount,
137+
address _feeTokenAddress,
138+
bytes calldata _extraArgs
139+
) private pure returns (Client.EVM2AnyMessage memory) {
140+
// Set the token amounts
141+
Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1);
142+
tokenAmounts[0] = Client.EVMTokenAmount({token: _token, amount: _amount});
143+
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
144+
return Client.EVM2AnyMessage({
145+
receiver: abi.encode(_recipient), // ABI-encoded receiver address
146+
data: _data,
147+
tokenAmounts: tokenAmounts, // The amount and type of token being transferred
148+
extraArgs: _extraArgs,
149+
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
150+
feeToken: _feeTokenAddress
151+
});
152+
}
153+
154+
function _chainlinkCrossChainStorage() internal pure returns (ChainlinkCrossChainStorage.Data storage) {
155+
return ChainlinkCrossChainStorage.data();
156+
}
157+
158+
}

0 commit comments

Comments
 (0)