Source Code
Overview
ETH Balance
0 ETH
ETH Value
$0.00| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
Latest 1 internal transaction
Advanced mode:
| Parent Transaction Hash | Block | From | To | |||
|---|---|---|---|---|---|---|
| 21809498 | 3 days ago | Contract Creation | 0 ETH |
Cross-Chain Transactions
Loading...
Loading
Contract Name:
EnsoCCIPReceiver
Compiler Version
v0.8.28+commit.7893614a
Optimization Enabled:
Yes with 200 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.24;
import { IEnsoCCIPReceiver } from "../interfaces/IEnsoCCIPReceiver.sol";
import { IEnsoRouter, Token, TokenType } from "../interfaces/IEnsoRouter.sol";
import { ITypeAndVersion } from "../interfaces/ITypeAndVersion.sol";
import { CCIPMessageDecoder } from "../libraries/CCIPMessageDecoder.sol";
import { CCIPReceiver, Client } from "chainlink-ccip/applications/CCIPReceiver.sol";
import { Ownable, Ownable2Step } from "openzeppelin-contracts/access/Ownable2Step.sol";
import { IERC20, SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import { Pausable } from "openzeppelin-contracts/utils/Pausable.sol";
/// @title EnsoCCIPReceiver
/// @author Enso
/// @notice Destination-side CCIP receiver that enforces replay protection, validates the delivered
/// token shape (exactly one non-zero ERC-20), decodes a payload, and either forwards funds
/// to Enso Shortcuts via the Enso Router or performs defensive refund/quarantine without reverting.
/// @dev Key properties:
/// - Relies on Chainlink CCIP Router gating via {CCIPReceiver}.
/// - Maintains idempotency with a messageId → handled flag.
/// - Validates `destTokenAmounts` has exactly one ERC-20 with non-zero amount.
/// - Decodes `(receiver, shortcutData)` from the message payload (temp external helper).
/// - For environment issues (PAUSED), refunds to `receiver` for better UX.
/// - For malformed messages (no/too many tokens, zero amount, bad payload, zero address receiver), quarantines
/// funds in this contract.
/// - Executes Shortcuts using a self-call (`try this.execute(...)`) to catch and handle reverts.
contract EnsoCCIPReceiver is IEnsoCCIPReceiver, CCIPReceiver, Ownable2Step, Pausable, ITypeAndVersion {
using SafeERC20 for IERC20;
/// forge-lint: disable-next-item(screaming-snake-case-const)
string public constant override typeAndVersion = "EnsoCCIPReceiver 1.0.0";
/// @dev Immutable Enso Router used to dispatch tokens + call Shortcuts.
/// forge-lint: disable-next-item(screaming-snake-case-immutable)
IEnsoRouter private immutable i_ensoRouter;
/// @dev Replay protection: tracks CCIP message IDs that were executed/refunded/quarantined.
/// forge-lint: disable-next-item(mixed-case-variable)
mapping(bytes32 messageId => bool wasExecuted) private s_executedMessage;
/// @notice Initializes the receiver with the CCIP router and Enso Router.
/// @dev The owner is set via {Ownable} base (passed in to support 2-step ownership if desired).
/// @param _owner Address to set as initial owner.
/// @param _ccipRouter Address of the CCIP Router on the destination chain.
/// @param _ensoRouter Address of the Enso Router that will execute Shortcuts.
constructor(address _owner, address _ccipRouter, address _ensoRouter) Ownable(_owner) CCIPReceiver(_ccipRouter) {
i_ensoRouter = IEnsoRouter(_ensoRouter);
}
/// @notice CCIP router callback: validate, classify (refund/quarantine/execute), and avoid reverting.
/// @dev Flow:
/// 1) Replay check by `messageId` (idempotent no-op if already handled).
/// 2) Validate token shape (exactly one ERC-20, non-zero amount).
/// 3) Decode payload `(receiver, shortcutData)` using a temporary external helper.
/// 4) Environment checks: `paused()`.
/// 5) If non-OK → select refund policy:
/// - TO_RECEIVER for environment issues (PAUSED),
/// - TO_ESCROW for malformed token/payload (funds remain in this contract),
/// - NONE for ALREADY_EXECUTED (no-op).
/// 6) If OK → mark executed and `try this.execute(...)`; on revert, refund to `receiver`.
/// @param _message The CCIP Any2EVM message with metadata, payload, and delivered tokens.
function _ccipReceive(Client.Any2EVMMessage memory _message) internal override {
(
address token,
uint256 amount,
address receiver,
bytes memory shortcutData,
ErrorCode errorCode,
bytes memory errorData
) = _validateMessage(_message);
if (errorCode != ErrorCode.NO_ERROR) {
emit MessageValidationFailed(_message.messageId, errorCode, errorData);
RefundKind refundKind = _getRefundPolicy(errorCode);
if (refundKind == RefundKind.NONE) {
// ALREADY_EXECUTED → idempotent no-op (do not flip the flag again)
return;
}
if (refundKind == RefundKind.TO_RECEIVER) {
s_executedMessage[_message.messageId] = true;
IERC20(token).safeTransfer(receiver, amount);
return;
}
if (refundKind == RefundKind.TO_ESCROW) {
s_executedMessage[_message.messageId] = true;
// Quarantine-in-place: funds remain in this contract; ops can recover via `recoverTokens`.
emit MessageQuarantined(_message.messageId, errorCode, token, amount, receiver);
return;
}
// Should not happen; guarded to surface during development.
revert EnsoCCIPReceiver_UnsupportedRefundKind(refundKind);
}
// Happy path: mark handled and attempt Shortcuts execution.
s_executedMessage[_message.messageId] = true;
try this.execute(token, amount, shortcutData) {
emit ShortcutExecutionSuccessful(_message.messageId);
} catch (bytes memory err) {
emit ShortcutExecutionFailed(_message.messageId, err);
IERC20(token).safeTransfer(receiver, amount);
}
}
/// @inheritdoc IEnsoCCIPReceiver
function execute(address _token, uint256 _amount, bytes calldata _shortcutData) external {
if (msg.sender != address(this)) {
revert EnsoCCIPReceiver_OnlySelf();
}
Token memory tokenIn = Token({ tokenType: TokenType.ERC20, data: abi.encode(_token, _amount) });
IERC20(_token).forceApprove(address(i_ensoRouter), _amount);
i_ensoRouter.routeSingle(tokenIn, _shortcutData);
}
/// @inheritdoc IEnsoCCIPReceiver
function pause() external onlyOwner {
_pause();
}
/// @inheritdoc IEnsoCCIPReceiver
function recoverTokens(address _token, address _to, uint256 _amount) external onlyOwner {
IERC20(_token).safeTransfer(_to, _amount);
emit TokensRecovered(_token, _to, _amount);
}
/// @inheritdoc IEnsoCCIPReceiver
function unpause() external onlyOwner {
_unpause();
}
/// @inheritdoc IEnsoCCIPReceiver
function getEnsoRouter() external view returns (address) {
return address(i_ensoRouter);
}
/// @inheritdoc IEnsoCCIPReceiver
function wasMessageExecuted(bytes32 _messageId) external view returns (bool) {
return s_executedMessage[_messageId];
}
/// @dev Maps an ErrorCode to a refund policy. NONE means no action (e.g., ALREADY_EXECUTED).
/// @dev NO_ERROR is included for completeness; in practice this function is only called when errorCode != NO_ERROR
/// (see _ccipReceive).
function _getRefundPolicy(ErrorCode _errorCode) private pure returns (RefundKind) {
if (_errorCode == ErrorCode.NO_ERROR || _errorCode == ErrorCode.ALREADY_EXECUTED) {
return RefundKind.NONE;
}
if (_errorCode == ErrorCode.PAUSED) {
return RefundKind.TO_RECEIVER;
}
// Only refund directly to the receiver when the payload decodes successfully.
// If decoding fails (MALFORMED_MESSAGE_DATA), all fields (including `receiver`) must be treated as untrusted,
// since a malformed payload could spoof a plausible receiver address.
if (
_errorCode == ErrorCode.NO_TOKENS || _errorCode == ErrorCode.TOO_MANY_TOKENS
|| _errorCode == ErrorCode.NO_TOKEN_AMOUNT || _errorCode == ErrorCode.MALFORMED_MESSAGE_DATA
|| _errorCode == ErrorCode.ZERO_ADDRESS_RECEIVER
) {
return RefundKind.TO_ESCROW;
}
// Should not happen; guarded to surface during development.
revert EnsoCCIPReceiver_UnsupportedErrorCode(_errorCode);
}
/// @dev Validates message shape and environment; does not mutate state.
/// @return token The delivered ERC-20 token (must be non-zero if NO_ERROR).
/// @return amount The delivered token amount (must be > 0 if NO_ERROR).
/// @return receiver Decoded receiver from payload (valid if NO_ERROR/PAUSED).
/// @return shortcutData Decoded Enso Shortcuts calldata.
/// @return errorCode Classification of the validation result.
/// @return errorData Optional details (see `MessageValidationFailed` doc).
function _validateMessage(Client.Any2EVMMessage memory _message)
private
view
returns (
address token,
uint256 amount,
address receiver,
bytes memory shortcutData,
ErrorCode errorCode,
bytes memory errorData
)
{
// Replay protection
bytes32 messageId = _message.messageId;
if (s_executedMessage[messageId]) {
errorData = abi.encode(messageId);
return (token, amount, receiver, shortcutData, ErrorCode.ALREADY_EXECUTED, errorData);
}
// Token shape
Client.EVMTokenAmount[] memory destTokenAmounts = _message.destTokenAmounts;
if (destTokenAmounts.length == 0) {
return (token, amount, receiver, shortcutData, ErrorCode.NO_TOKENS, errorData);
}
if (destTokenAmounts.length > 1) {
// CCIP currently delivers at most ONE token per message. Multiple-token deliveries are not supported by the
// protocol today, so treat any length > 1 as invalid and quarantine/refuse.
return (token, amount, receiver, shortcutData, ErrorCode.TOO_MANY_TOKENS, errorData);
}
token = destTokenAmounts[0].token;
amount = destTokenAmounts[0].amount;
if (amount == 0) {
return (token, amount, receiver, shortcutData, ErrorCode.NO_TOKEN_AMOUNT, errorData);
}
// Decode payload
bool decodeSuccess;
(decodeSuccess, receiver, shortcutData) = CCIPMessageDecoder._tryDecodeMessageData(_message.data);
if (!decodeSuccess) {
return (token, amount, receiver, shortcutData, ErrorCode.MALFORMED_MESSAGE_DATA, errorData);
}
// Check receiver
if (receiver == address(0)) {
return (token, amount, receiver, shortcutData, ErrorCode.ZERO_ADDRESS_RECEIVER, errorData);
}
// Environment checks (refundable to receiver)
if (paused()) {
return (token, amount, receiver, shortcutData, ErrorCode.PAUSED, errorData);
}
return (token, amount, receiver, shortcutData, ErrorCode.NO_ERROR, errorData);
}
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
/// @title IEnsoCCIPReceiver
/// @author Enso
/// @notice Interface for a CCIP destination receiver that enforces replay protection,
/// validates delivered token shape (exactly one non-zero ERC-20), decodes a payload,
/// and either forwards funds into Enso Shortcuts or performs defensive refunds/quarantine.
/// @dev Exposes only the external/public surface of the implementing receiver:
/// - Self-call execution entry used by try/catch in `_ccipReceive`
/// - Pause/unpause and token recovery
/// - Views for router/version/replay state
interface IEnsoCCIPReceiver {
/// @notice High-level validation/flow outcomes produced by `_validateMessage`.
/// @dev Meanings:
/// - NO_ERROR: message is well-formed; proceed to execution.
/// - ALREADY_EXECUTED: messageId was previously handled (idempotent no-op).
/// - NO_TOKENS / TOO_MANY_TOKENS / NO_TOKEN_AMOUNT: token shape invalid.
/// - MALFORMED_MESSAGE_DATA: payload (address,bytes) could not be decoded.
/// - ZERO_ADDRESS_RECEIVER: payload receiver is the zero address.
/// - PAUSED: contract is paused; environment block on execution.
enum ErrorCode {
NO_ERROR,
ALREADY_EXECUTED,
NO_TOKENS,
TOO_MANY_TOKENS,
NO_TOKEN_AMOUNT,
MALFORMED_MESSAGE_DATA,
ZERO_ADDRESS_RECEIVER,
PAUSED
}
/// @notice Refund policy selected by the receiver for a given ErrorCode.
/// @dev TO_RECEIVER is used for environment errors (e.g., PAUSED) after successful payload decode.
/// TO_ESCROW is used for malformed token/payload cases.
enum RefundKind {
NONE,
TO_RECEIVER,
TO_ESCROW
}
// -------------------------------------------------------------------------
// Events
// -------------------------------------------------------------------------
/// @notice Emitted when validation fails. See `errorCode` for the reason.
/// @dev errorData encodings:
/// - ALREADY_EXECUTED: (bytes32 messageId)
/// - Others: empty bytes unless specified by the implementation.
event MessageValidationFailed(bytes32 indexed messageId, ErrorCode indexed errorCode, bytes errorData);
/// @notice Funds were quarantined in the receiver instead of delivered to the payload receiver.
/// @dev CCIP currently delivers at most one ERC-20 per message, so we log the single token/amount
/// pair (best-effort, informational) rather than arrays.
/// @param messageId The CCIP message id.
/// @param code The validation error that triggered quarantine.
/// @param token ERC-20 token retained.
/// @param amount Token amount retained.
/// @param receiver Original payload receiver (informational; may be zero if not decoded).
event MessageQuarantined(
bytes32 indexed messageId, ErrorCode indexed code, address token, uint256 amount, address receiver
);
/// @notice Emitted when Enso Shortcuts execution succeeds for a CCIP message.
/// @param messageId CCIP message identifier.
event ShortcutExecutionSuccessful(bytes32 indexed messageId);
/// @notice Emitted when Enso Shortcuts execution reverts for a CCIP message.
/// @param messageId CCIP message identifier.
/// @param err ABI-encoded revert data from the failed call.
event ShortcutExecutionFailed(bytes32 indexed messageId, bytes err);
/// @notice Emitted when the owner recovers tokens from the receiver.
event TokensRecovered(address indexed token, address indexed to, uint256 amount);
// -------------------------------------------------------------------------
// Errors
// -------------------------------------------------------------------------
/// @notice Revert when an external caller targets the internal executor.
error EnsoCCIPReceiver_OnlySelf();
/// @notice Revert if an unexpected ErrorCode is encountered in refund policy logic.
error EnsoCCIPReceiver_UnsupportedErrorCode(ErrorCode errorCode);
/// @notice Revert if an unexpected RefundKind is encountered in refund policy logic.
error EnsoCCIPReceiver_UnsupportedRefundKind(RefundKind refundKind);
// -------------------------------------------------------------------------
// External Functions
// -------------------------------------------------------------------------
/// @notice Executes Enso Shortcuts with a single ERC-20 that was previously received via CCIP.
/// @dev MUST be callable only by the contract itself (self-call), typically from `_ccipReceive`
/// using `try this.execute(...)`. Implementations should guard with
/// `if (msg.sender != address(this)) revert EnsoCCIPReceiver_OnlySelf();`
/// @param token ERC-20 token to route.
/// @param amount Amount of `token` to route.
/// @param shortcutData ABI-encoded call data for the Enso Shortcuts entrypoint.
function execute(address token, uint256 amount, bytes calldata shortcutData) external;
/// @notice Pauses the CCIP receiver, disabling new incoming message execution until unpaused.
/// @dev Only callable by the contract owner.
function pause() external;
/// @notice Provides the ability for the owner to recover any ERC-20 tokens held by this contract
/// (for example, after quarantine or accidental sends).
/// @param token ERC20-token to recover.
/// @param to Destination address to send the tokens to.
/// @param amount The amount of tokens to send.
function recoverTokens(address token, address to, uint256 amount) external;
/// @notice Unpauses the CCIP receiver, re-enabling normal message processing.
/// @dev Only callable by the contract owner.
function unpause() external;
/// @notice Returns the Enso Router address used by this receiver.
/// @return router Address of the Enso Router.
function getEnsoRouter() external view returns (address router);
/// @notice Returns whether a CCIP message was already handled (executed/refunded/quarantined).
/// @param messageId CCIP message identifier.
/// @return executed True if the messageId is marked as executed/handled.
function wasMessageExecuted(bytes32 messageId) external view returns (bool executed);
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
enum TokenType {
Native,
ERC20,
ERC721,
ERC1155
}
struct Token {
TokenType tokenType;
bytes data;
}
interface IEnsoRouter {
function routeSingle(
Token calldata tokenIn,
bytes calldata data
)
external
payable
returns (bytes memory response);
function routeMulti(
Token[] calldata tokensIn,
bytes calldata data
)
external
payable
returns (bytes memory response);
function safeRouteSingle(
Token calldata tokenIn,
Token calldata tokenOut,
address receiver,
bytes calldata data
)
external
payable
returns (bytes memory response);
function safeRouteMulti(
Token[] calldata tokensIn,
Token[] calldata tokensOut,
address receiver,
bytes calldata data
)
external
payable
returns (bytes memory response);
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
/// @title ITypeAndVersion
/// @author Enso
/// @notice Provides a human-readable identifier for the contract type and its version.
interface ITypeAndVersion {
/// @return A string containing the contract type and semantic version.
function typeAndVersion() external pure returns (string memory);
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.24;
library CCIPMessageDecoder {
/// @dev Safe, non-reverting decoder for abi.encode(address,bytes) in MEMORY.
/// Returns (ok, receiver, shortcutData). On malformed input, ok=false.
/// Layout: HEAD (64 bytes) = [receiver|offset], TAIL at (base+off) = [len|bytes...].
function _tryDecodeMessageData(bytes memory _data)
internal
pure
returns (bool success, address receiver, bytes memory shortcutData)
{
// Need 2 head words (64) + 1 length word (32) = 96 bytes minimum
if (_data.length < 96) {
return (false, address(0), bytes(""));
}
// Pointer to first head word
uint256 base;
assembly { base := add(_data, 32) }
uint256 off;
assembly ("memory-safe") {
// Address is right-aligned in the word → keep low 20 bytes
receiver := and(mload(base), 0x000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
off := mload(add(base, 32))
}
// Word-aligned offset?
if ((off & 31) != 0) {
return (false, address(0), bytes(""));
}
uint256 baseLen = _data.length;
// Off must be at/after 2-word head and leave room for tail length word
// i.e. off >= 64 && off <= baseLen - 32 (avoid off+32 overflow)
if (off < 64 || off > baseLen - 32) {
return (false, address(0), bytes(""));
}
// Safe now to compute tail start (no overflow)
uint256 tailStart = off + 32;
// Read tail len
uint256 len;
assembly ("memory-safe") {
len := mload(add(base, off))
}
unchecked {
// Available bytes remaining after the tail length word
uint256 avail = baseLen - tailStart;
// Require len itself to fit in the available tail
if (len > avail) {
return (false, address(0), bytes(""));
}
// Ceil32(len) and ensure padded length matches exactly (reject superfluous bytes)
uint256 padded = (len + 31) & ~uint256(31);
if (padded != avail) {
return (false, address(0), bytes(""));
}
// Allocate and copy exactly `len` bytes (ignore padding)
shortcutData = new bytes(len);
if (len != 0) {
assembly ("memory-safe") {
let src := add(add(base, off), 32) // start of tail payload
let dst := add(shortcutData, 32) // start of new bytes payload
// Copy in 32-byte chunks up to padded boundary
for { let i := 0 } lt(i, padded) { i := add(i, 32) } {
mstore(add(dst, i), mload(add(src, i)))
}
}
}
}
return (true, receiver, shortcutData);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {IAny2EVMMessageReceiver} from "../interfaces/IAny2EVMMessageReceiver.sol";
import {Client} from "../libraries/Client.sol";
import {IERC165} from "@openzeppelin/[email protected]/utils/introspection/IERC165.sol";
/// @title CCIPReceiver - Base contract for CCIP applications that can receive messages.
abstract contract CCIPReceiver is IAny2EVMMessageReceiver, IERC165 {
address internal immutable i_ccipRouter;
constructor(
address router
) {
if (router == address(0)) revert InvalidRouter(address(0));
i_ccipRouter = router;
}
/// @notice IERC165 supports an interfaceId.
/// @param interfaceId The interfaceId to check.
/// @return true if the interfaceId is supported.
/// @dev Should indicate whether the contract implements IAny2EVMMessageReceiver.
/// e.g. return interfaceId == type(IAny2EVMMessageReceiver).interfaceId || interfaceId == type(IERC165).interfaceId
/// This allows CCIP to check if ccipReceive is available before calling it.
/// - If this returns false or reverts, only tokens are transferred to the receiver.
/// - If this returns true, tokens are transferred and ccipReceive is called atomically.
/// Additionally, if the receiver address does not have code associated with it at the time of
/// execution (EXTCODESIZE returns 0), only tokens will be transferred.
function supportsInterface(
bytes4 interfaceId
) public pure virtual override returns (bool) {
return interfaceId == type(IAny2EVMMessageReceiver).interfaceId || interfaceId == type(IERC165).interfaceId;
}
/// @inheritdoc IAny2EVMMessageReceiver
function ccipReceive(
Client.Any2EVMMessage calldata message
) external virtual override onlyRouter {
_ccipReceive(message);
}
/// @notice Override this function in your implementation.
/// @param message Any2EVMMessage.
function _ccipReceive(
Client.Any2EVMMessage memory message
) internal virtual;
/// @notice Return the current router
/// @return CCIP router address
function getRouter() public view virtual returns (address) {
return address(i_ccipRouter);
}
error InvalidRouter(address router);
/// @dev only calls from the set router are accepted.
modifier onlyRouter() {
if (msg.sender != getRouter()) revert InvalidRouter(msg.sender);
_;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.20;
import {Ownable} from "./Ownable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* This extension of the {Ownable} contract includes a two-step mechanism to transfer
* ownership, where the new owner must call {acceptOwnership} in order to replace the
* old one. This can help prevent common mistakes, such as transfers of ownership to
* incorrect accounts, or to contracts that are unable to interact with the
* permission system.
*
* The initial owner is specified at deployment time in the constructor for `Ownable`. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*
* Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
if (pendingOwner() != sender) {
revert OwnableUnauthorizedAccount(sender);
}
_transferOwnership(sender);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Client} from "../libraries/Client.sol";
/// @notice Application contracts that intend to receive messages from the router should implement this interface.
interface IAny2EVMMessageReceiver {
/// @notice Called by the Router to deliver a message. If this reverts, any token transfers also revert.
/// The message will move to a FAILED state and become available for manual execution.
/// @param message CCIP Message.
/// @dev Note ensure you check the msg.sender is the OffRampRouter.
function ccipReceive(
Client.Any2EVMMessage calldata message
) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// End consumer library.
library Client {
/// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
struct EVMTokenAmount {
address token; // token address on the local chain.
uint256 amount; // Amount of tokens.
}
struct Any2EVMMessage {
bytes32 messageId; // MessageId corresponding to ccipSend on source.
uint64 sourceChainSelector; // Source chain selector.
bytes sender; // abi.decode(sender) if coming from an EVM chain.
bytes data; // payload sent in original message.
EVMTokenAmount[] destTokenAmounts; // Tokens and their amounts in their destination chain representation.
}
// If extraArgs is empty bytes, the default is 200k gas limit.
struct EVM2AnyMessage {
bytes receiver; // abi.encode(receiver address) for dest EVM chains.
bytes data; // Data payload.
EVMTokenAmount[] tokenAmounts; // Token transfers.
address feeToken; // Address of feeToken. address(0) means you will send msg.value.
bytes extraArgs; // Populate this with _argsToBytes(EVMExtraArgsV2).
}
// Tag to indicate only a gas limit. Only usable for EVM as destination chain.
bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;
struct EVMExtraArgsV1 {
uint256 gasLimit;
}
function _argsToBytes(
EVMExtraArgsV1 memory extraArgs
) internal pure returns (bytes memory bts) {
return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);
}
// Tag to indicate a gas limit (or dest chain equivalent processing units) and Out Of Order Execution. This tag is
// available for multiple chain families. If there is no chain family specific tag, this is the default available
// for a chain.
// Note: not available for Solana VM based chains.
bytes4 public constant GENERIC_EXTRA_ARGS_V2_TAG = 0x181dcf10;
/// @param gasLimit: gas limit for the callback on the destination chain.
/// @param allowOutOfOrderExecution: if true, it indicates that the message can be executed in any order relative to
/// other messages from the same sender. This value's default varies by chain. On some chains, a particular value is
/// enforced, meaning if the expected value is not set, the message request will revert.
/// @dev Fully compatible with the previously existing EVMExtraArgsV2.
struct GenericExtraArgsV2 {
uint256 gasLimit;
bool allowOutOfOrderExecution;
}
// Extra args tag for chains that use the Sui VM.
bytes4 public constant SUI_EXTRA_ARGS_V1_TAG = 0x21ea4ca9;
// Extra args tag for chains that use the Solana VM.
bytes4 public constant SVM_EXTRA_ARGS_V1_TAG = 0x1f3b3aba;
struct SVMExtraArgsV1 {
uint32 computeUnits;
uint64 accountIsWritableBitmap;
bool allowOutOfOrderExecution;
bytes32 tokenReceiver;
// Additional accounts needed for execution of CCIP receiver. Must be empty if message.receiver is zero.
// Token transfer related accounts are specified in the token pool lookup table on SVM.
bytes32[] accounts;
}
/// @dev The maximum number of accounts that can be passed in SVMExtraArgs.
uint256 public constant SVM_EXTRA_ARGS_MAX_ACCOUNTS = 64;
/// @dev The expected static payload size of a token transfer when Borsh encoded and submitted to SVM.
/// TokenPool extra data and offchain data sizes are dynamic, and should be accounted for separately.
uint256 public constant SVM_TOKEN_TRANSFER_DATA_OVERHEAD = (4 + 32) // source_pool
+ 32 // token_address
+ 4 // gas_amount
+ 4 // extra_data overhead
+ 32 // amount
+ 32 // size of the token lookup table account
+ 32 // token-related accounts in the lookup table, over-estimated to 32, typically between 11 - 13
+ 32 // token account belonging to the token receiver, e.g ATA, not included in the token lookup table
+ 32 // per-chain token pool config, not included in the token lookup table
+ 32 // per-chain token billing config, not always included in the token lookup table
+ 32; // OffRamp pool signer PDA, not included in the token lookup table
/// @dev Number of overhead accounts needed for message execution on SVM.
/// @dev These are message.receiver, and the OffRamp Signer PDA specific to the receiver.
uint256 public constant SVM_MESSAGING_ACCOUNTS_OVERHEAD = 2;
/// @dev The size of each SVM account address in bytes.
uint256 public constant SVM_ACCOUNT_BYTE_SIZE = 32;
struct SuiExtraArgsV1 {
uint256 gasLimit;
bool allowOutOfOrderExecution;
bytes32 tokenReceiver;
bytes32[] receiverObjectIds;
}
/// @dev The expected static payload size of a token transfer when Borsh encoded and submitted to SUI.
/// TokenPool extra data and offchain data sizes are dynamic, and should be accounted for separately.
uint256 public constant SUI_TOKEN_TRANSFER_DATA_OVERHEAD = (4 + 32) // source_pool
+ 32 // token_address
+ 4 // gas_amount
+ 4 // extra_data overhead
+ 32 // amount
+ 32 // size of the token lookup table account
+ 32 // token-related accounts in the lookup table, over-estimated to 32, typically between 11 - 13
+ 32 // token account belonging to the token receiver, e.g ATA, not included in the token lookup table
+ 32 // per-chain token pool config, not included in the token lookup table
+ 32; // per-chain token billing config, not always included in the token lookup table
/// @dev Number of overhead accounts needed for message execution on SUI.
/// @dev This is the message.receiver.
uint256 public constant SUI_MESSAGING_ACCOUNTS_OVERHEAD = 1;
/// @dev The maximum number of receiver object ids that can be passed in SuiExtraArgs.
uint256 public constant SUI_EXTRA_ARGS_MAX_RECEIVER_OBJECT_IDS = 64;
/// @dev The size of each SUI account address in bytes.
uint256 public constant SUI_ACCOUNT_BYTE_SIZE = 32;
function _argsToBytes(
GenericExtraArgsV2 memory extraArgs
) internal pure returns (bytes memory bts) {
return abi.encodeWithSelector(GENERIC_EXTRA_ARGS_V2_TAG, extraArgs);
}
function _svmArgsToBytes(
SVMExtraArgsV1 memory extraArgs
) internal pure returns (bytes memory bts) {
return abi.encodeWithSelector(SVM_EXTRA_ARGS_V1_TAG, extraArgs);
}
function _suiArgsToBytes(
SuiExtraArgsV1 memory extraArgs
) internal pure returns (bytes memory bts) {
return abi.encodeWithSelector(SUI_EXTRA_ARGS_V1_TAG, extraArgs);
}
/// @notice The CCV struct is used to represent a cross-chain verifier.
struct CCV {
/// @param The ccvAddress is the address of the verifier contract on the source chain
address ccvAddress;
/// @param args The args are the arguments that the verifier contract expects. They are opaque to CCIP and are only
/// used in the CCV.
bytes args;
}
bytes4 public constant GENERIC_EXTRA_ARGS_V3_TAG = 0x302326cb;
struct EVMExtraArgsV3 {
CCV[] requiredCCV;
CCV[] optionalCCV;
uint8 optionalThreshold;
/// @notice The finality config, 0 means the default finality that the CCV considers final. Any non-zero value means
/// a block depth.
uint16 finalityConfig;
address executor;
bytes executorArgs;
bytes tokenArgs;
}
function _argsToBytes(
EVMExtraArgsV3 memory extraArgs
) internal pure returns (bytes memory bts) {
return abi.encodeWithSelector(GENERIC_EXTRA_ARGS_V3_TAG, extraArgs);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";{
"remappings": [
"@layerzerolabs/oapp-evm/=dependencies/devtools-0.0.1/packages/oapp-evm/",
"@layerzerolabs/lz-evm-protocol-v2/=dependencies/layerzero-v2-2.0.2/packages/layerzero-v2/evm/protocol/",
"@layerzerolabs/lz-evm-oapp-v2/=dependencies/layerzero-v2-2.0.2/packages/layerzero-v2/evm/oapp/",
"@uniswap/v4-core/=dependencies/v4-core-4.0.0/",
"@uniswap/v4-periphery/=dependencies/v4-periphery-4.0.0/",
"devtools/=dependencies/devtools-0.0.1/packages/toolbox-foundry/src/",
"ds-test/=dependencies/forge-std-1.9.7/dependencies/ds-test/src/",
"enso-weiroll/=dependencies/enso-weiroll-1.4.1/contracts/",
"erc4626-tests/=dependencies/@openzeppelin-contracts-5.2.0/lib/erc4626-tests/",
"forge-std/=dependencies/forge-std-1.9.7/src/",
"halmos-cheatcodes/=dependencies/@openzeppelin-contracts-5.2.0/lib/halmos-cheatcodes/src/",
"layerzero-v2/=dependencies/layerzero-v2-2.0.2/",
"openzeppelin-contracts/=dependencies/@openzeppelin-contracts-5.2.0/contracts/",
"safe-contracts/=dependencies/safe-tools-0.2.0/lib/safe-contracts/contracts/",
"safe-tools/=dependencies/safe-tools-0.2.0/src/",
"solady/=dependencies/solady-0.1.22/src/",
"solmate/=dependencies/solady-0.1.22/lib/solmate/src/",
"@ensdomains/=dependencies/v4-core-4.0.0/node_modules/@ensdomains/",
"forge-gas-snapshot/=dependencies/v4-periphery-4.0.0/lib/permit2/lib/forge-gas-snapshot/src/",
"hardhat/=dependencies/v4-core-4.0.0/node_modules/hardhat/",
"permit2/=dependencies/v4-periphery-4.0.0/lib/permit2/",
"v4-core/=dependencies/v4-core-4.0.0/src/",
"v4-periphery/=dependencies/v4-periphery-4.0.0/",
"account-abstraction-v7/=dependencies/account-abstraction-v7-0.7.0/contracts/",
"@openzeppelin-contracts-5.2.0/=dependencies/@openzeppelin-contracts-5.2.0/",
"account-abstraction-0.7.0/=dependencies/account-abstraction-v7-0.7.0/contracts/",
"devtools-0.0.1/=dependencies/devtools-0.0.1/",
"enso-weiroll-1.4.1/=dependencies/enso-weiroll-1.4.1/contracts/",
"forge-std-1.9.7/=dependencies/forge-std-1.9.7/src/",
"layerzero-v2-2.0.2/=dependencies/layerzero-v2-2.0.2/",
"safe-smart-account-1.5.0/=dependencies/safe-smart-account-1.5.0/contracts/",
"safe-tools-0.2.0/=dependencies/safe-tools-0.2.0/src/",
"solady-0.1.22/=dependencies/solady-0.1.22/src/",
"v4-core-4.0.0/=dependencies/v4-core-4.0.0/src/",
"v4-periphery-4.0.0/=dependencies/v4-periphery-4.0.0/src/",
"chainlink-ccip/=dependencies/chainlink-ccip-1.6.2/chains/evm/contracts/",
"chainlink-ccip-1.6.2/=dependencies/chainlink-ccip-1.6.2/chains/evm/contracts/",
"chainlink-evm/=dependencies/chainlink-evm-1.5.0/contracts/src/v0.8/",
"chainlink-evm-1.5.0/=dependencies/chainlink-evm-1.5.0/contracts/src/v0.8/",
"@chainlink/=dependencies/chainlink-evm-1.5.0/",
"@openzeppelin/[email protected]/utils/introspection/IERC165.sol=dependencies/@openzeppelin-contracts-5.2.0/contracts/utils/introspection/IERC165.sol",
"@openzeppelin/[email protected]/utils/introspection/ERC165Checker.sol=dependencies/@openzeppelin-contracts-5.2.0/contracts/utils/introspection/ERC165Checker.sol",
"@openzeppelin/[email protected]/token/ERC20/utils/SafeERC20.sol=dependencies/@openzeppelin-contracts-5.2.0/contracts/token/ERC20/utils/SafeERC20.sol",
"@openzeppelin/[email protected]/token/ERC20/IERC20.sol=dependencies/@openzeppelin-contracts-5.2.0/contracts/token/ERC20/IERC20.sol",
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"@uniswap/v2-core/=dependencies/universal-router-4.0.0/node_modules/@uniswap/v2-core/",
"@uniswap/v3-core/=dependencies/universal-router-4.0.0/node_modules/@uniswap/v3-core/",
"@uniswap/v3-periphery/=dependencies/universal-router-4.0.0/lib/v3-periphery/",
"account-abstraction-v7-0.7.0/=dependencies/account-abstraction-v7-0.7.0/contracts/",
"permit2-4.0.0/=dependencies/permit2-4.0.0/",
"universal-router-4.0.0/=dependencies/universal-router-4.0.0/",
"v3-periphery/=dependencies/universal-router-4.0.0/lib/v3-periphery/contracts/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "none",
"appendCBOR": false
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": true
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_ccipRouter","type":"address"},{"internalType":"address","name":"_ensoRouter","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"EnsoCCIPReceiver_OnlySelf","type":"error"},{"inputs":[{"internalType":"enum IEnsoCCIPReceiver.ErrorCode","name":"errorCode","type":"uint8"}],"name":"EnsoCCIPReceiver_UnsupportedErrorCode","type":"error"},{"inputs":[{"internalType":"enum IEnsoCCIPReceiver.RefundKind","name":"refundKind","type":"uint8"}],"name":"EnsoCCIPReceiver_UnsupportedRefundKind","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[{"internalType":"address","name":"router","type":"address"}],"name":"InvalidRouter","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"messageId","type":"bytes32"},{"indexed":true,"internalType":"enum IEnsoCCIPReceiver.ErrorCode","name":"code","type":"uint8"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"receiver","type":"address"}],"name":"MessageQuarantined","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"messageId","type":"bytes32"},{"indexed":true,"internalType":"enum IEnsoCCIPReceiver.ErrorCode","name":"errorCode","type":"uint8"},{"indexed":false,"internalType":"bytes","name":"errorData","type":"bytes"}],"name":"MessageValidationFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"messageId","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"err","type":"bytes"}],"name":"ShortcutExecutionFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"messageId","type":"bytes32"}],"name":"ShortcutExecutionSuccessful","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokensRecovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"messageId","type":"bytes32"},{"internalType":"uint64","name":"sourceChainSelector","type":"uint64"},{"internalType":"bytes","name":"sender","type":"bytes"},{"internalType":"bytes","name":"data","type":"bytes"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct Client.EVMTokenAmount[]","name":"destTokenAmounts","type":"tuple[]"}],"internalType":"struct Client.Any2EVMMessage","name":"message","type":"tuple"}],"name":"ccipReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes","name":"_shortcutData","type":"bytes"}],"name":"execute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getEnsoRouter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRouter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"recoverTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"typeAndVersion","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_messageId","type":"bytes32"}],"name":"wasMessageExecuted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]Contract Creation Code
60c03461013757601f61127c38819003918201601f19168301916001600160401b0383118484101761013b57808492606094604052833981010312610137576100478161014f565b61005f60406100586020850161014f565b930161014f565b916001600160a01b03811615610124576080526001600160a01b0316908115610111576001545f80546001600160a01b031981168517825560405194916001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a36001600160a81b0319166001556001600160a01b031660a0526111189081610164823960805181818161044a01526104d2015260a05181818161025401526108f80152f35b631e4fbdf760e01b5f525f60045260245ffd5b6335fdcccd60e21b5f525f60045260245ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b03821682036101375756fe6080806040526004361015610012575f80fd5b5f3560e01c90816301ffc9a71461098357508063181f5a771461092757806318eb7214146108e35780633f4ba83a146108755780635c975abb146108505780635f3e849f146107d5578063715018a61461077257806379ba5097146106ed5780638456cb591461067a57806385572ffb146104a05780638da5cb5b14610479578063b0f479a114610435578063b61d27f614610199578063e30c397814610171578063f2fde38b146100ff5763f91c46c6146100cc575f80fd5b346100fb5760203660031901126100fb576004355f526002602052602060ff60405f2054166040519015158152f35b5f80fd5b346100fb5760203660031901126100fb57610118610a51565b610120610aad565b60018060a01b0316806bffffffffffffffffffffffff60a01b600154161760015560018060a01b035f54167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e227005f80a3005b346100fb575f3660031901126100fb576001546040516001600160a01b039091168152602090f35b346100fb5760603660031901126100fb576101b2610a51565b6024356044356001600160401b0381116100fb57366023820112156100fb578060040135916001600160401b0383116100fb5736602484840101116100fb5730330361042657604080516001600160a01b0386166020820152808201839052908152916102206060846109f1565b6040519461022d866109d6565b60018652602080870194855260405163095ea7b360e01b8183019081526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166024840181905260448085019890985296835293909316929091905f9061029d6064856109f1565b83519082865af15f513d8261040a575b5050156103c6575b50506040519463b94c360960e01b865260406004870152519360048510156103b257602086809593835f9894602461030185988c98604488015251604060648801526084870190610a2d565b85810360031901828701528281529301858401378181018401859052601f01601f1916010301925af180156103a75761033657005b3d805f833e61034581836109f1565b8101906020818303126100fb578051906001600160401b0382116100fb570181601f820112156100fb57805161037a81610a12565b9261038860405194856109f1565b818452602082840101116100fb575f928160208094018483015e010152005b6040513d5f823e3d90fd5b634e487b7160e01b5f52602160045260245ffd5b610403916103fe60405163095ea7b360e01b60208201528660248201525f6044820152604481526103f86064826109f1565b82610d94565b610d94565b85806102b5565b90915061041e5750813b15155b88806102ad565b600114610417565b6377225d9360e11b5f5260045ffd5b346100fb575f3660031901126100fb576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346100fb575f3660031901126100fb575f546040516001600160a01b039091168152602090f35b346100fb5760203660031901126100fb576004356001600160401b0381116100fb5760a060031982360301126100fb577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163303610667576040519060a082018281106001600160401b03821117610653576040528060040135825260248101356001600160401b03811681036100fb57602083015260448101356001600160401b0381116100fb576105619060043691840101610a67565b604083015260648101356001600160401b0381116100fb576105899060043691840101610a67565b60608301526084810135906001600160401b0382116100fb570190366023830112156100fb576004820135916001600160401b03831161065357604051906105d760208560051b01836109f1565b838252602060048184019560061b83010101903682116100fb57602401935b81851061060e5761060c84846080820152610afe565b005b6040853603126100fb5760405190610625826109d6565b8535906001600160a01b03821682036100fb57826020926040945282880135838201528152019401936105f6565b634e487b7160e01b5f52604160045260245ffd5b6335fdcccd60e21b5f523360045260245ffd5b346100fb575f3660031901126100fb57610692610aad565b60015460ff8160a01c166106de5760ff60a01b1916600160a01b176001556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a25890602090a1005b63d93c066560e01b5f5260045ffd5b346100fb575f3660031901126100fb57600154336001600160a01b039091160361075f57600180546001600160a01b03199081169091555f805433928116831782556001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a3005b63118cdaa760e01b5f523360045260245ffd5b346100fb575f3660031901126100fb5761078a610aad565b600180546001600160a01b03199081169091555f80549182168155906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b346100fb5760603660031901126100fb576107ee610a51565b6024356001600160a01b038116918282036100fb5760207f401f439d865a766757ec78675925bd67198d5e78805aa41691b34b5d6a6cbbe6916108476044358092610837610aad565b6001600160a01b03169586610ac0565b604051908152a3005b346100fb575f3660031901126100fb57602060ff60015460a01c166040519015158152f35b346100fb575f3660031901126100fb5761088d610aad565b60015460ff8160a01c16156108d45760ff60a01b19166001556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa90602090a1005b638dfc202b60e01b5f5260045ffd5b346100fb575f3660031901126100fb576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346100fb575f3660031901126100fb5761097f6040516109486040826109f1565b60168152750456e736f43434950526563656976657220312e302e360541b6020820152604051918291602083526020830190610a2d565b0390f35b346100fb5760203660031901126100fb576004359063ffffffff60e01b82168092036100fb576020916385572ffb60e01b81149081156109c5575b5015158152f35b6301ffc9a760e01b149050836109be565b604081019081106001600160401b0382111761065357604052565b90601f801991011681019081106001600160401b0382111761065357604052565b6001600160401b03811161065357601f01601f191660200190565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b03821682036100fb57565b81601f820112156100fb57803590610a7e82610a12565b92610a8c60405194856109f1565b828452602083830101116100fb57815f926020809301838601378301015290565b5f546001600160a01b0316330361075f57565b60405163a9059cbb60e01b60208201526001600160a01b03929092166024830152604480830193909352918152610afc916103fe6064836109f1565b565b610b0781610e0d565b9591929394905f9660088210156103b25781610c7257505081515f52600260205260405f20600160ff19825416179055303b156100fb57604051635b0e93fb60e11b81526001600160a01b03909116600482018190526024820186905260606044830152925f9082908190610b80906064830190610a2d565b038183305af19081610c5d575b50610c3157610afc94903d15610c0357610bfb7f52a455caf6b7d55115eb8e33cf29187a4d25b89f36b958ced33427f91756fa22913d610bcc81610a12565b90610bda60405192836109f1565b8152809460203d92013e5b5192604051918291602083526020830190610a2d565b0390a2610ac0565b90507f52a455caf6b7d55115eb8e33cf29187a4d25b89f36b958ced33427f91756fa22610bfb606092610be5565b9150507f846efe8e8b99273df7db28675a7c9db91cd5af16ef4a3425e926455e5f9eb19a9150519180a2565b610c6a9196505f906109f1565b5f945f610b8d565b9094965082979593507f42090e785aeac16460e013dfb7241057ccbd8c1bc5fa7a0b8dfddd8bd1e33b25610cb88693945192604051918291602083526020830190610a2d565b0390a3610cc483610f2b565b9460038610156103b2578515610d8b5760018614610d5d5760028614610cf75785637bdebda360e01b5f5260045260245ffd5b7fc086f49fd14ea2d64a1968fc8bca88442a972e25dcc0b63a643c47f4c3dbf647939550866060939597515f52600260205260405f20600160ff1982541617905551946040519260018060a01b03168352602083015260018060a01b03166040820152a3565b9250909294610afc9450515f52600260205260405f20600160ff1982541617905560018060a01b0316610ac0565b50505050915050565b905f602091828151910182855af1156103a7575f513d610de357506001600160a01b0381163b155b610dc35750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b60011415610dbc565b805115610df95760200190565b634e487b7160e01b5f52603260045260245ffd5b905f905f905f9360609485938251805f52600260205260ff60405f205416610efe575060808301519586518015610eef57600110610ee1575060209050610e676001600160a01b03610e5e88610dec565b51511696610dec565b510151958615610ed45750506060610e80910151610feb565b9391949084819615610ec75750506001600160a01b03851615610ebd5760ff60015460a01c16610eb357949392915f9190565b9493929160079190565b9493929160069190565b9196955093509160059190565b9495949093909260049250565b969095509093909260039250565b50969095509093909260029250565b955050505092905060405191602083015260208252610f1e6040836109f1565b5f925f925f929160019190565b906008821015806103b25782158015610fdf575b610fd9576103b25760078214610fd3575f90600283148015610fc6575b828115610fb6575b8115610fa6575b8115610f96575b50610f8e57506315230f5760e11b5f526103b25760045260245ffd5b915050600290565b90506103b2576006831482610f72565b90506103b2576005831482610f6b565b90506103b2576004831482610f64565b505f915060038314610f5c565b60019150565b505f9150565b50505f60018314610f3f565b60608151106111095760018060a01b0360208201511691604082015191601f83166110f7578051906040841080156110e3575b6110b2576020840184116110cf57830190602082015193601f19910301908184116110b257601f8401601f19169182036110b25761105b84610a12565b9361106960405195866109f1565b808552601f1961107882610a12565b0136602087013761108d575b50506001929190565b5f5b82811061109c5750611084565b806040602092840101518282880101520161108f565b50509150506040516110c56020826109f1565b5f81525f915f9190565b634e487b7160e01b5f52601160045260245ffd5b50601f1982018281116110cf57841161101e565b509150506040516110c56020826109f1565b506040516110c56020826109f156000000000000000000000000826e0bb2276271efdf2a500597f37b94f6c153ba0000000000000000000000007c19b79d2a054114ab36ad758a36e92376e267da000000000000000000000000f75584ef6673ad213a685a1b58cc0330b8ea22cf
Deployed Bytecode
0x6080806040526004361015610012575f80fd5b5f3560e01c90816301ffc9a71461098357508063181f5a771461092757806318eb7214146108e35780633f4ba83a146108755780635c975abb146108505780635f3e849f146107d5578063715018a61461077257806379ba5097146106ed5780638456cb591461067a57806385572ffb146104a05780638da5cb5b14610479578063b0f479a114610435578063b61d27f614610199578063e30c397814610171578063f2fde38b146100ff5763f91c46c6146100cc575f80fd5b346100fb5760203660031901126100fb576004355f526002602052602060ff60405f2054166040519015158152f35b5f80fd5b346100fb5760203660031901126100fb57610118610a51565b610120610aad565b60018060a01b0316806bffffffffffffffffffffffff60a01b600154161760015560018060a01b035f54167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e227005f80a3005b346100fb575f3660031901126100fb576001546040516001600160a01b039091168152602090f35b346100fb5760603660031901126100fb576101b2610a51565b6024356044356001600160401b0381116100fb57366023820112156100fb578060040135916001600160401b0383116100fb5736602484840101116100fb5730330361042657604080516001600160a01b0386166020820152808201839052908152916102206060846109f1565b6040519461022d866109d6565b60018652602080870194855260405163095ea7b360e01b8183019081526001600160a01b037f000000000000000000000000f75584ef6673ad213a685a1b58cc0330b8ea22cf81166024840181905260448085019890985296835293909316929091905f9061029d6064856109f1565b83519082865af15f513d8261040a575b5050156103c6575b50506040519463b94c360960e01b865260406004870152519360048510156103b257602086809593835f9894602461030185988c98604488015251604060648801526084870190610a2d565b85810360031901828701528281529301858401378181018401859052601f01601f1916010301925af180156103a75761033657005b3d805f833e61034581836109f1565b8101906020818303126100fb578051906001600160401b0382116100fb570181601f820112156100fb57805161037a81610a12565b9261038860405194856109f1565b818452602082840101116100fb575f928160208094018483015e010152005b6040513d5f823e3d90fd5b634e487b7160e01b5f52602160045260245ffd5b610403916103fe60405163095ea7b360e01b60208201528660248201525f6044820152604481526103f86064826109f1565b82610d94565b610d94565b85806102b5565b90915061041e5750813b15155b88806102ad565b600114610417565b6377225d9360e11b5f5260045ffd5b346100fb575f3660031901126100fb576040517f0000000000000000000000007c19b79d2a054114ab36ad758a36e92376e267da6001600160a01b03168152602090f35b346100fb575f3660031901126100fb575f546040516001600160a01b039091168152602090f35b346100fb5760203660031901126100fb576004356001600160401b0381116100fb5760a060031982360301126100fb577f0000000000000000000000007c19b79d2a054114ab36ad758a36e92376e267da6001600160a01b03163303610667576040519060a082018281106001600160401b03821117610653576040528060040135825260248101356001600160401b03811681036100fb57602083015260448101356001600160401b0381116100fb576105619060043691840101610a67565b604083015260648101356001600160401b0381116100fb576105899060043691840101610a67565b60608301526084810135906001600160401b0382116100fb570190366023830112156100fb576004820135916001600160401b03831161065357604051906105d760208560051b01836109f1565b838252602060048184019560061b83010101903682116100fb57602401935b81851061060e5761060c84846080820152610afe565b005b6040853603126100fb5760405190610625826109d6565b8535906001600160a01b03821682036100fb57826020926040945282880135838201528152019401936105f6565b634e487b7160e01b5f52604160045260245ffd5b6335fdcccd60e21b5f523360045260245ffd5b346100fb575f3660031901126100fb57610692610aad565b60015460ff8160a01c166106de5760ff60a01b1916600160a01b176001556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a25890602090a1005b63d93c066560e01b5f5260045ffd5b346100fb575f3660031901126100fb57600154336001600160a01b039091160361075f57600180546001600160a01b03199081169091555f805433928116831782556001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a3005b63118cdaa760e01b5f523360045260245ffd5b346100fb575f3660031901126100fb5761078a610aad565b600180546001600160a01b03199081169091555f80549182168155906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b346100fb5760603660031901126100fb576107ee610a51565b6024356001600160a01b038116918282036100fb5760207f401f439d865a766757ec78675925bd67198d5e78805aa41691b34b5d6a6cbbe6916108476044358092610837610aad565b6001600160a01b03169586610ac0565b604051908152a3005b346100fb575f3660031901126100fb57602060ff60015460a01c166040519015158152f35b346100fb575f3660031901126100fb5761088d610aad565b60015460ff8160a01c16156108d45760ff60a01b19166001556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa90602090a1005b638dfc202b60e01b5f5260045ffd5b346100fb575f3660031901126100fb576040517f000000000000000000000000f75584ef6673ad213a685a1b58cc0330b8ea22cf6001600160a01b03168152602090f35b346100fb575f3660031901126100fb5761097f6040516109486040826109f1565b60168152750456e736f43434950526563656976657220312e302e360541b6020820152604051918291602083526020830190610a2d565b0390f35b346100fb5760203660031901126100fb576004359063ffffffff60e01b82168092036100fb576020916385572ffb60e01b81149081156109c5575b5015158152f35b6301ffc9a760e01b149050836109be565b604081019081106001600160401b0382111761065357604052565b90601f801991011681019081106001600160401b0382111761065357604052565b6001600160401b03811161065357601f01601f191660200190565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b03821682036100fb57565b81601f820112156100fb57803590610a7e82610a12565b92610a8c60405194856109f1565b828452602083830101116100fb57815f926020809301838601378301015290565b5f546001600160a01b0316330361075f57565b60405163a9059cbb60e01b60208201526001600160a01b03929092166024830152604480830193909352918152610afc916103fe6064836109f1565b565b610b0781610e0d565b9591929394905f9660088210156103b25781610c7257505081515f52600260205260405f20600160ff19825416179055303b156100fb57604051635b0e93fb60e11b81526001600160a01b03909116600482018190526024820186905260606044830152925f9082908190610b80906064830190610a2d565b038183305af19081610c5d575b50610c3157610afc94903d15610c0357610bfb7f52a455caf6b7d55115eb8e33cf29187a4d25b89f36b958ced33427f91756fa22913d610bcc81610a12565b90610bda60405192836109f1565b8152809460203d92013e5b5192604051918291602083526020830190610a2d565b0390a2610ac0565b90507f52a455caf6b7d55115eb8e33cf29187a4d25b89f36b958ced33427f91756fa22610bfb606092610be5565b9150507f846efe8e8b99273df7db28675a7c9db91cd5af16ef4a3425e926455e5f9eb19a9150519180a2565b610c6a9196505f906109f1565b5f945f610b8d565b9094965082979593507f42090e785aeac16460e013dfb7241057ccbd8c1bc5fa7a0b8dfddd8bd1e33b25610cb88693945192604051918291602083526020830190610a2d565b0390a3610cc483610f2b565b9460038610156103b2578515610d8b5760018614610d5d5760028614610cf75785637bdebda360e01b5f5260045260245ffd5b7fc086f49fd14ea2d64a1968fc8bca88442a972e25dcc0b63a643c47f4c3dbf647939550866060939597515f52600260205260405f20600160ff1982541617905551946040519260018060a01b03168352602083015260018060a01b03166040820152a3565b9250909294610afc9450515f52600260205260405f20600160ff1982541617905560018060a01b0316610ac0565b50505050915050565b905f602091828151910182855af1156103a7575f513d610de357506001600160a01b0381163b155b610dc35750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b60011415610dbc565b805115610df95760200190565b634e487b7160e01b5f52603260045260245ffd5b905f905f905f9360609485938251805f52600260205260ff60405f205416610efe575060808301519586518015610eef57600110610ee1575060209050610e676001600160a01b03610e5e88610dec565b51511696610dec565b510151958615610ed45750506060610e80910151610feb565b9391949084819615610ec75750506001600160a01b03851615610ebd5760ff60015460a01c16610eb357949392915f9190565b9493929160079190565b9493929160069190565b9196955093509160059190565b9495949093909260049250565b969095509093909260039250565b50969095509093909260029250565b955050505092905060405191602083015260208252610f1e6040836109f1565b5f925f925f929160019190565b906008821015806103b25782158015610fdf575b610fd9576103b25760078214610fd3575f90600283148015610fc6575b828115610fb6575b8115610fa6575b8115610f96575b50610f8e57506315230f5760e11b5f526103b25760045260245ffd5b915050600290565b90506103b2576006831482610f72565b90506103b2576005831482610f6b565b90506103b2576004831482610f64565b505f915060038314610f5c565b60019150565b505f9150565b50505f60018314610f3f565b60608151106111095760018060a01b0360208201511691604082015191601f83166110f7578051906040841080156110e3575b6110b2576020840184116110cf57830190602082015193601f19910301908184116110b257601f8401601f19169182036110b25761105b84610a12565b9361106960405195866109f1565b808552601f1961107882610a12565b0136602087013761108d575b50506001929190565b5f5b82811061109c5750611084565b806040602092840101518282880101520161108f565b50509150506040516110c56020826109f1565b5f81525f915f9190565b634e487b7160e01b5f52601160045260245ffd5b50601f1982018281116110cf57841161101e565b509150506040516110c56020826109f1565b506040516110c56020826109f156
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000826e0bb2276271efdf2a500597f37b94f6c153ba0000000000000000000000007c19b79d2a054114ab36ad758a36e92376e267da000000000000000000000000f75584ef6673ad213a685a1b58cc0330b8ea22cf
-----Decoded View---------------
Arg [0] : _owner (address): 0x826e0BB2276271eFdF2a500597f37b94f6c153bA
Arg [1] : _ccipRouter (address): 0x7c19b79D2a054114Ab36ad758A36e92376e267DA
Arg [2] : _ensoRouter (address): 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf
-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 000000000000000000000000826e0bb2276271efdf2a500597f37b94f6c153ba
Arg [1] : 0000000000000000000000007c19b79d2a054114ab36ad758a36e92376e267da
Arg [2] : 000000000000000000000000f75584ef6673ad213a685a1b58cc0330b8ea22cf
Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.