Files
proxmox/docs/CCIPWETH9Bridge_flattened.sol
defiQUG cb47cce074 Complete markdown files cleanup and organization
- Organized 252 files across project
- Root directory: 187 → 2 files (98.9% reduction)
- Moved configuration guides to docs/04-configuration/
- Moved troubleshooting guides to docs/09-troubleshooting/
- Moved quick start guides to docs/01-getting-started/
- Moved reports to reports/ directory
- Archived temporary files
- Generated comprehensive reports and documentation
- Created maintenance scripts and guides

All files organized according to established standards.
2026-01-06 01:46:25 -08:00

397 lines
13 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
// contracts/ccip/IRouterClient.sol
/**
* @title Chainlink CCIP Router Client Interface
* @notice Interface for Chainlink CCIP Router Client
* @dev This interface is based on Chainlink CCIP Router Client specification
*/
interface IRouterClient {
/// @notice Represents the router's fee token
enum TokenAmountType {
Fiat,
Native
}
/// @notice Represents a token amount and its type
struct TokenAmount {
address token;
uint256 amount;
TokenAmountType amountType;
}
/// @notice Represents a CCIP message
struct EVM2AnyMessage {
bytes receiver;
bytes data;
TokenAmount[] tokenAmounts;
address feeToken;
bytes extraArgs;
}
/// @notice Represents a CCIP message with source chain information
struct Any2EVMMessage {
bytes32 messageId;
uint64 sourceChainSelector;
bytes sender;
bytes data;
TokenAmount[] tokenAmounts;
}
/// @notice Emitted when a message is sent
event MessageSent(
bytes32 indexed messageId,
uint64 indexed destinationChainSelector,
address indexed sender,
bytes receiver,
bytes data,
TokenAmount[] tokenAmounts,
address feeToken,
bytes extraArgs
);
/// @notice Emitted when a message is received
event MessageReceived(
bytes32 indexed messageId,
uint64 indexed sourceChainSelector,
address indexed sender,
bytes data,
TokenAmount[] tokenAmounts
);
/// @notice Sends a message to a destination chain
/// @param destinationChainSelector The chain selector of the destination chain
/// @param message The message to send
/// @return messageId The ID of the sent message
/// @return fees The fees required for the message
/// @dev If feeToken is zero address, fees are paid in native token (ETH) via msg.value
function ccipSend(
uint64 destinationChainSelector,
EVM2AnyMessage memory message
) external payable returns (bytes32 messageId, uint256 fees);
/// @notice Gets the fee for sending a message
/// @param destinationChainSelector The chain selector of the destination chain
/// @param message The message to send
/// @return fee The fee required for the message
function getFee(
uint64 destinationChainSelector,
EVM2AnyMessage memory message
) external view returns (uint256 fee);
/// @notice Gets the supported tokens for a destination chain
/// @param destinationChainSelector The chain selector of the destination chain
/// @return tokens The list of supported tokens
function getSupportedTokens(
uint64 destinationChainSelector
) external view returns (address[] memory tokens);
}
// contracts/ccip/CCIPWETH9Bridge.sol
// Minimal IERC20 interface for ERC20 tokens (WETH9 and LINK)
interface IERC20 {
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function transfer(address to, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
/**
* @title CCIP WETH9 Bridge
* @notice Cross-chain WETH9 transfer bridge using Chainlink CCIP
* @dev Enables users to send WETH9 tokens across chains via CCIP
*/
contract CCIPWETH9Bridge {
IRouterClient public immutable ccipRouter;
address public immutable weth9; // WETH9 contract address
address public feeToken; // LINK token address
address public admin;
// Destination chain configurations
struct DestinationChain {
uint64 chainSelector;
address receiverBridge; // Address of corresponding bridge on destination chain
bool enabled;
}
mapping(uint64 => DestinationChain) public destinations;
uint64[] public destinationChains;
// Track cross-chain transfers for replay protection
mapping(bytes32 => bool) public processedTransfers;
mapping(address => uint256) public nonces;
event CrossChainTransferInitiated(
bytes32 indexed messageId,
address indexed sender,
uint64 indexed destinationChainSelector,
address recipient,
uint256 amount,
uint256 nonce
);
event CrossChainTransferCompleted(
bytes32 indexed messageId,
uint64 indexed sourceChainSelector,
address indexed recipient,
uint256 amount
);
event DestinationAdded(uint64 chainSelector, address receiverBridge);
event DestinationRemoved(uint64 chainSelector);
event DestinationUpdated(uint64 chainSelector, address receiverBridge);
modifier onlyAdmin() {
require(msg.sender == admin, "CCIPWETH9Bridge: only admin");
_;
}
modifier onlyRouter() {
require(msg.sender == address(ccipRouter), "CCIPWETH9Bridge: only router");
_;
}
constructor(address _ccipRouter, address _weth9, address _feeToken) {
require(_ccipRouter != address(0), "CCIPWETH9Bridge: zero router");
require(_weth9 != address(0), "CCIPWETH9Bridge: zero WETH9");
require(_feeToken != address(0), "CCIPWETH9Bridge: zero fee token");
ccipRouter = IRouterClient(_ccipRouter);
weth9 = _weth9;
feeToken = _feeToken;
admin = msg.sender;
}
/**
* @notice Send WETH9 tokens to another chain via CCIP
* @param destinationChainSelector The chain selector of the destination chain
* @param recipient The recipient address on the destination chain
* @param amount The amount of WETH9 to send
* @return messageId The CCIP message ID
*/
function sendCrossChain(
uint64 destinationChainSelector,
address recipient,
uint256 amount
) external returns (bytes32 messageId) {
require(amount > 0, "CCIPWETH9Bridge: invalid amount");
require(recipient != address(0), "CCIPWETH9Bridge: zero recipient");
DestinationChain memory dest = destinations[destinationChainSelector];
require(dest.enabled, "CCIPWETH9Bridge: destination not enabled");
// Transfer WETH9 from user
require(IERC20(weth9).transferFrom(msg.sender, address(this), amount), "CCIPWETH9Bridge: transfer failed");
// Increment nonce for replay protection
nonces[msg.sender]++;
uint256 currentNonce = nonces[msg.sender];
// Encode transfer data (recipient, amount, sender, nonce)
bytes memory data = abi.encode(
recipient,
amount,
msg.sender,
currentNonce
);
// Prepare CCIP message with WETH9 tokens
IRouterClient.EVM2AnyMessage memory message = IRouterClient.EVM2AnyMessage({
receiver: abi.encode(dest.receiverBridge),
data: data,
tokenAmounts: new IRouterClient.TokenAmount[](1),
feeToken: feeToken,
extraArgs: ""
});
// Set token amount (WETH9)
message.tokenAmounts[0] = IRouterClient.TokenAmount({
token: weth9,
amount: amount,
amountType: IRouterClient.TokenAmountType.Fiat
});
// Calculate fee
uint256 fee = ccipRouter.getFee(destinationChainSelector, message);
// Approve and pay fee
if (fee > 0) {
require(IERC20(feeToken).transferFrom(msg.sender, address(this), fee), "CCIPWETH9Bridge: fee transfer failed");
require(IERC20(feeToken).approve(address(ccipRouter), fee), "CCIPWETH9Bridge: fee approval failed");
}
// Send via CCIP
(messageId, ) = ccipRouter.ccipSend(destinationChainSelector, message);
emit CrossChainTransferInitiated(
messageId,
msg.sender,
destinationChainSelector,
recipient,
amount,
currentNonce
);
return messageId;
}
/**
* @notice Receive WETH9 tokens from another chain via CCIP
* @param message The CCIP message
*/
function ccipReceive(
IRouterClient.Any2EVMMessage calldata message
) external onlyRouter {
// Replay protection: check if message already processed
require(!processedTransfers[message.messageId], "CCIPWETH9Bridge: transfer already processed");
// Mark as processed
processedTransfers[message.messageId] = true;
// Validate token amounts
require(message.tokenAmounts.length > 0, "CCIPWETH9Bridge: no tokens");
require(message.tokenAmounts[0].token == weth9, "CCIPWETH9Bridge: invalid token");
uint256 amount = message.tokenAmounts[0].amount;
require(amount > 0, "CCIPWETH9Bridge: invalid amount");
// Decode transfer data (recipient, amount, sender, nonce)
(address recipient, , , ) = abi.decode(
message.data,
(address, uint256, address, uint256)
);
require(recipient != address(0), "CCIPWETH9Bridge: zero recipient");
// Transfer WETH9 to recipient
require(IERC20(weth9).transfer(recipient, amount), "CCIPWETH9Bridge: transfer failed");
emit CrossChainTransferCompleted(
message.messageId,
message.sourceChainSelector,
recipient,
amount
);
}
/**
* @notice Calculate fee for cross-chain transfer
* @param destinationChainSelector The chain selector of the destination chain
* @param amount The amount of WETH9 to send
* @return fee The fee required for the transfer
*/
function calculateFee(
uint64 destinationChainSelector,
uint256 amount
) external view returns (uint256 fee) {
DestinationChain memory dest = destinations[destinationChainSelector];
require(dest.enabled, "CCIPWETH9Bridge: destination not enabled");
bytes memory data = abi.encode(address(0), amount, address(0), 0);
IRouterClient.EVM2AnyMessage memory message = IRouterClient.EVM2AnyMessage({
receiver: abi.encode(dest.receiverBridge),
data: data,
tokenAmounts: new IRouterClient.TokenAmount[](1),
feeToken: feeToken,
extraArgs: ""
});
message.tokenAmounts[0] = IRouterClient.TokenAmount({
token: weth9,
amount: amount,
amountType: IRouterClient.TokenAmountType.Fiat
});
return ccipRouter.getFee(destinationChainSelector, message);
}
/**
* @notice Add destination chain
*/
function addDestination(
uint64 chainSelector,
address receiverBridge
) external onlyAdmin {
require(receiverBridge != address(0), "CCIPWETH9Bridge: zero address");
require(!destinations[chainSelector].enabled, "CCIPWETH9Bridge: destination already exists");
destinations[chainSelector] = DestinationChain({
chainSelector: chainSelector,
receiverBridge: receiverBridge,
enabled: true
});
destinationChains.push(chainSelector);
emit DestinationAdded(chainSelector, receiverBridge);
}
/**
* @notice Remove destination chain
*/
function removeDestination(uint64 chainSelector) external onlyAdmin {
require(destinations[chainSelector].enabled, "CCIPWETH9Bridge: destination not found");
destinations[chainSelector].enabled = false;
// Remove from array
for (uint256 i = 0; i < destinationChains.length; i++) {
if (destinationChains[i] == chainSelector) {
destinationChains[i] = destinationChains[destinationChains.length - 1];
destinationChains.pop();
break;
}
}
emit DestinationRemoved(chainSelector);
}
/**
* @notice Update destination receiver bridge
*/
function updateDestination(
uint64 chainSelector,
address receiverBridge
) external onlyAdmin {
require(destinations[chainSelector].enabled, "CCIPWETH9Bridge: destination not found");
require(receiverBridge != address(0), "CCIPWETH9Bridge: zero address");
destinations[chainSelector].receiverBridge = receiverBridge;
emit DestinationUpdated(chainSelector, receiverBridge);
}
/**
* @notice Update fee token
*/
function updateFeeToken(address newFeeToken) external onlyAdmin {
require(newFeeToken != address(0), "CCIPWETH9Bridge: zero address");
feeToken = newFeeToken;
}
/**
* @notice Change admin
*/
function changeAdmin(address newAdmin) external onlyAdmin {
require(newAdmin != address(0), "CCIPWETH9Bridge: zero address");
admin = newAdmin;
}
/**
* @notice Get destination chains
*/
function getDestinationChains() external view returns (uint64[] memory) {
return destinationChains;
}
/**
* @notice Get user nonce
*/
function getUserNonce(address user) external view returns (uint256) {
return nonces[user];
}
}