Skip to content

Commit 09603f1

Browse files
committed
it builds
1 parent 756a7b0 commit 09603f1

File tree

2 files changed

+134
-94
lines changed

2 files changed

+134
-94
lines changed

remappings.txt

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ forge-std/=lib/forge-std/src/
55
@erc721a-upgradeable/=lib/ERC721A-Upgradeable/contracts/
66
@limitbreak/creator-token-standards/=lib/creator-token-standards/src/
77
@limitbreak/permit-c/=lib/PermitC/src/
8+
@chainlink/=lib/chainlink.git/contracts/src/v0.8/

src/module/token/crosschain/chainlink.sol

+133-94
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.20;
3+
4+
import {Module} from "../../../Module.sol";
5+
6+
import {Role} from "../../../Role.sol";
7+
8+
import {IERC20} from "../../../interface/IERC20.sol";
9+
import {IInstallationCallback} from "../../../interface/IInstallationCallback.sol";
10+
import {OwnableRoles} from "@solady/auth/OwnableRoles.sol";
11+
12+
import {IRouterClient} from "@chainlink/ccip/interfaces/IRouterClient.sol";
13+
import {Client} from "@chainlink/ccip/libraries/Client.sol";
114

215
library ChainlinkCrossChainStorage {
316

417
/// @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));
18+
bytes32 public constant CHAINLINKCROSSCHAIN_STORAGE_POSITION = keccak256(
19+
abi.encode(uint256(keccak256("token.minting.chainlinkcrosschain.erc721")) - 1)
20+
) & ~bytes32(uint256(0xff));
721

822
struct Data {
923
address router;
10-
address s_linkToken;
24+
address linkToken;
1125
}
1226

1327
function data() internal pure returns (Data storage data_) {
@@ -19,134 +33,159 @@ library ChainlinkCrossChainStorage {
1933

2034
}
2135

36+
contract ChainlinkCrossChain is Module {
37+
38+
error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees);
39+
40+
/*//////////////////////////////////////////////////////////////
41+
MODULE CONFIG
42+
//////////////////////////////////////////////////////////////*/
2243

23-
contract ChainlinkCrossChain is CCIPReceiver, OwnerIsCreator {
44+
/// @notice Returns all implemented callback and fallback functions.
45+
function getModuleConfig() external pure override returns (ModuleConfig memory config) {
46+
config.fallbackFunctions = new FallbackFunction[](5);
2447

25-
address immutable s_linkToken;
26-
address immutable router;
48+
config.fallbackFunctions[0] = FallbackFunction({selector: this.getRouter.selector, permissionBits: 0});
49+
config.fallbackFunctions[1] = FallbackFunction({selector: this.getLinkToken.selector, permissionBits: 0});
50+
config.fallbackFunctions[2] =
51+
FallbackFunction({selector: this.setRouter.selector, permissionBits: Role._MANAGER_ROLE});
52+
config.fallbackFunctions[3] =
53+
FallbackFunction({selector: this.setLinkToken.selector, permissionBits: Role._MANAGER_ROLE});
54+
config.fallbackFunctions[4] =
55+
FallbackFunction({selector: this.sendCrossChainTransaction.selector, permissionBits: 0});
2756

28-
constructor(address _router, address _link) {
29-
s_linkToken = _link;
30-
router = _router;
57+
config.registerInstallationCallback = true;
3158
}
3259

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));
60+
/*//////////////////////////////////////////////////////////////
61+
INSTALL / UNINSTALL FUNCTIONS
62+
//////////////////////////////////////////////////////////////*/
4263

43-
Client.EVM2AnyMessage memory evm2AnyMessage =
44-
_buildCCIPMessage(_receiver, _text, _token, _amount, _feeTokenAddress, ccipMessageExtraArgs);
64+
/// @dev Called by a Core into an Module during the installation of the Module.
65+
function onInstall(bytes calldata data) external {
66+
(address router, address linkToken) = abi.decode(data, (address, address));
67+
_chainlinkCrossChainStorage().router = router;
68+
_chainlinkCrossChainStorage().linkToken = linkToken;
69+
}
4570

46-
// Initialize a router client instance to interact with cross-chain router
47-
IRouterClient router = IRouterClient(_chainLinkCrossChainStorage().router);
71+
/// @dev Called by a Core into an Module during the uninstallation of the Module.
72+
function onUninstall(bytes calldata data) external {}
4873

49-
// Get the fee required to send the CCIP message
50-
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
74+
/// @dev Returns bytes encoded install params, to be sent to `onInstall` function
75+
function encodeBytesOnInstall(address router, address linkToken) external pure returns (bytes memory) {
76+
return abi.encode(router, linkToken);
77+
}
5178

52-
if (fees > s_linkToken.balanceOf(address(this))) {
53-
revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
54-
}
79+
/// @dev Returns bytes encoded uninstall params, to be sent to `onUninstall` function
80+
function encodeBytesOnUninstall() external pure returns (bytes memory) {
81+
return "";
82+
}
5583

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);
84+
/*//////////////////////////////////////////////////////////////
85+
FALLBACK FUNCTIONS
86+
//////////////////////////////////////////////////////////////*/
5887

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);
88+
function getRouter() external view returns (address) {
89+
return _chainlinkCrossChainStorage().router;
90+
}
91+
92+
function getLinkToken() external view returns (address) {
93+
return _chainlinkCrossChainStorage().linkToken;
94+
}
6195

62-
// Send the message through the router and store the returned message ID
63-
messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage);
96+
function setRouter(address router) external {
97+
_chainlinkCrossChainStorage().router = router;
98+
}
6499

65-
// Emit an event with message details
66-
emit MessageSent(
67-
messageId, _destinationChainSelector, _receiver, _text, _token, _amount, address(s_linkToken), fees
68-
);
100+
function setLinkToken(address linkToken) external {
101+
_chainlinkCrossChainStorage().linkToken = linkToken;
102+
}
69103

70-
// Return the message ID
71-
return messageId;
104+
function sendCrossChainTransaction(
105+
uint64 _destinationChain,
106+
address _recipient,
107+
bytes calldata _data,
108+
address _token,
109+
uint256 _amount,
110+
address _callAddress,
111+
bytes memory _extraArgs
112+
) external {
113+
(address _feeTokenAddress, bytes memory ccipMessageExtraArgs) = abi.decode(_extraArgs, (address, bytes));
114+
115+
if (_feeTokenAddress == address(0)) {
116+
_sendMessagePayNative(_destinationChain, _recipient, _data, _token, _amount, ccipMessageExtraArgs);
117+
} else {
118+
_sendMessagePayToken(
119+
_destinationChain, _recipient, _data, _token, _amount, _feeTokenAddress, ccipMessageExtraArgs
120+
);
121+
}
72122
}
73123

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,
124+
/*//////////////////////////////////////////////////////////////
125+
INTERNAL FUNCTIONS
126+
//////////////////////////////////////////////////////////////*/
127+
128+
function _sendMessagePayToken(
129+
uint64 _destinationChain,
130+
address _recipient,
131+
bytes calldata _data,
87132
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);
133+
uint256 _amount,
134+
address _feeTokenAddress,
135+
bytes memory _extraArgs
136+
) internal {
137+
Client.EVM2AnyMessage memory evm2AnyMessage =
138+
_buildCCIPMessage(_recipient, _data, _token, _amount, _feeTokenAddress, _extraArgs);
139+
IRouterClient router = IRouterClient(_chainlinkCrossChainStorage().router);
140+
uint256 fees = router.getFee(_destinationChain, evm2AnyMessage);
141+
IERC20 linkToken = IERC20(_chainlinkCrossChainStorage().linkToken);
105142

106-
if (fees > address(this).balance) {
107-
revert NotEnoughBalance(address(this).balance, fees);
143+
if (fees > linkToken.balanceOf(address(this))) {
144+
revert NotEnoughBalance(linkToken.balanceOf(address(this)), fees);
108145
}
109146

110-
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
147+
IERC20(linkToken).approve(address(router), fees);
111148
IERC20(_token).approve(address(router), _amount);
149+
router.ccipSend(_destinationChain, evm2AnyMessage);
150+
}
112151

113-
// Send the message through the router and store the returned message ID
114-
messageId = router.ccipSend{value: fees}(_destinationChainSelector, evm2AnyMessage);
152+
function _sendMessagePayNative(
153+
uint64 _destinationChain,
154+
address _recipient,
155+
bytes calldata _data,
156+
address _token,
157+
uint256 _amount,
158+
bytes memory _extraArgs
159+
) internal {
160+
Client.EVM2AnyMessage memory evm2AnyMessage =
161+
_buildCCIPMessage(_recipient, _data, _token, _amount, address(0), _extraArgs);
162+
IRouterClient router = IRouterClient(_chainlinkCrossChainStorage().router);
163+
uint256 fees = router.getFee(_destinationChain, evm2AnyMessage);
115164

116-
// Emit an event with message details
117-
emit MessageSent(messageId, _destinationChainSelector, _receiver, _text, _token, _amount, address(0), fees);
165+
if (fees > address(this).balance) {
166+
revert NotEnoughBalance(address(this).balance, fees);
167+
}
118168

119-
// Return the message ID
120-
return messageId;
169+
IERC20(_token).approve(address(router), _amount);
170+
router.ccipSend{value: fees}(_destinationChain, evm2AnyMessage);
121171
}
122172

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.
132173
function _buildCCIPMessage(
133-
address _recipient
174+
address _recipient,
134175
bytes calldata _data,
135176
address _token,
136177
uint256 _amount,
137178
address _feeTokenAddress,
138-
bytes calldata _extraArgs
179+
bytes memory _extraArgs
139180
) private pure returns (Client.EVM2AnyMessage memory) {
140-
// Set the token amounts
141181
Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1);
142182
tokenAmounts[0] = Client.EVMTokenAmount({token: _token, amount: _amount});
143-
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
183+
144184
return Client.EVM2AnyMessage({
145-
receiver: abi.encode(_recipient), // ABI-encoded receiver address
185+
receiver: abi.encode(_recipient),
146186
data: _data,
147-
tokenAmounts: tokenAmounts, // The amount and type of token being transferred
187+
tokenAmounts: tokenAmounts,
148188
extraArgs: _extraArgs,
149-
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
150189
feeToken: _feeTokenAddress
151190
});
152191
}

0 commit comments

Comments
 (0)