ETH Price: $3,280.09 (+1.08%)

Contract

0xab3B1C8eF95B46bA72c49ad4e6Bff34ba4B06aC1
 
Transaction Hash
Method
Block
From
To
Withdraw Collate...210165932024-10-21 21:18:1182 days ago1729545491IN
0xab3B1C8e...ba4B06aC1
0.0075869 ETH0.001377569.43719946
Withdraw Collate...210161672024-10-21 19:52:1182 days ago1729540331IN
0xab3B1C8e...ba4B06aC1
0.0075869 ETH0.0018189112.46072489
Withdraw Collate...209950512024-10-18 21:10:5985 days ago1729285859IN
0xab3B1C8e...ba4B06aC1
0.00911125 ETH0.0015113310.3527171
Deposit Collater...209919632024-10-18 10:50:5985 days ago1729248659IN
0xab3B1C8e...ba4B06aC1
0.0003866 ETH0.0050591418.80520126
Repay209760552024-10-16 5:32:2387 days ago1729056743IN
0xab3B1C8e...ba4B06aC1
0.0003866 ETH0.002957179.8632884
Withdraw Collate...209757352024-10-16 4:28:2387 days ago1729052903IN
0xab3B1C8e...ba4B06aC1
0.01126986 ETH0.0016956511.45514747
Repay209754792024-10-16 3:36:5987 days ago1729049819IN
0xab3B1C8e...ba4B06aC1
0.0003866 ETH0.0039561213.19516388
Withdraw Collate...209752182024-10-16 2:44:3587 days ago1729046675IN
0xab3B1C8e...ba4B06aC1
0.01417636 ETH0.0019231212.99179376
Deposit Collater...209691692024-10-15 6:26:5988 days ago1728973619IN
0xab3B1C8e...ba4B06aC1
0.0003866 ETH0.0034481411.50888637
Withdraw Collate...209688952024-10-15 5:31:5988 days ago1728970319IN
0xab3B1C8e...ba4B06aC1
0.01053797 ETH0.0018935412.79196794
Deposit Collater...209623532024-10-14 7:34:4789 days ago1728891287IN
0xab3B1C8e...ba4B06aC1
0.00041198 ETH0.0034954212.99273421
Withdraw Collate...209592292024-10-13 21:06:4790 days ago1728853607IN
0xab3B1C8e...ba4B06aC1
0.01427636 ETH0.002538217.38689815
Withdraw Collate...209591922024-10-13 20:59:1190 days ago1728853151IN
0xab3B1C8e...ba4B06aC1
0.01427636 ETH0.0022667315.31311111
Withdraw Collate...209576642024-10-13 15:51:5990 days ago1728834719IN
0xab3B1C8e...ba4B06aC1
0.01190848 ETH0.0022983814.32825852
Deposit Collater...209576222024-10-13 15:43:3590 days ago1728834215IN
0xab3B1C8e...ba4B06aC1
0.00141198 ETH0.0038392515.20877501
Deposit Collater...209405902024-10-11 6:27:5992 days ago1728628079IN
0xab3B1C8e...ba4B06aC1
0.00039184 ETH0.002486019.40897123
Withdraw Collate...209367722024-10-10 17:41:2393 days ago1728582083IN
0xab3B1C8e...ba4B06aC1
0.01174726 ETH0.0024814115.47045384
Withdraw Collate...209350782024-10-10 12:01:4793 days ago1728561707IN
0xab3B1C8e...ba4B06aC1
0.00882914 ETH0.001594610.92313301
Deposit Collater...209312002024-10-09 23:03:2394 days ago1728515003IN
0xab3B1C8e...ba4B06aC1
0.00039184 ETH0.002686079.98434289
Deposit Collater...209133872024-10-07 11:28:5996 days ago1728300539IN
0xab3B1C8e...ba4B06aC1
0.00039184 ETH0.0029260610.8763854
Deposit Collater...208987722024-10-05 10:35:3598 days ago1728124535IN
0xab3B1C8e...ba4B06aC1
0.00039184 ETH0.0037510514.19623699
Deposit Collater...208983432024-10-05 9:09:3598 days ago1728119375IN
0xab3B1C8e...ba4B06aC1
0.00039184 ETH0.001072343.98597712
Deposit Collater...208971302024-10-05 5:05:4798 days ago1728104747IN
0xab3B1C8e...ba4B06aC1
0.00039184 ETH0.001210764.50050058
Repay208862702024-10-03 16:46:59100 days ago1727974019IN
0xab3B1C8e...ba4B06aC1
0.00039184 ETH0.0042240713.65231125
Deposit Collater...208847992024-10-03 11:49:59100 days ago1727956199IN
0xab3B1C8e...ba4B06aC1
0.00039184 ETH0.001342274.98909916
View all transactions

Latest 25 internal transactions (View All)

Advanced mode:
Parent Transaction Hash Block
From
To
210165932024-10-21 21:18:1182 days ago1729545491
0xab3B1C8e...ba4B06aC1
0.0075869 ETH
210161672024-10-21 19:52:1182 days ago1729540331
0xab3B1C8e...ba4B06aC1
0.0075869 ETH
209950512024-10-18 21:10:5985 days ago1729285859
0xab3B1C8e...ba4B06aC1
0.00911125 ETH
209919632024-10-18 10:50:5985 days ago1729248659
0xab3B1C8e...ba4B06aC1
0.0003866 ETH
209760552024-10-16 5:32:2387 days ago1729056743
0xab3B1C8e...ba4B06aC1
0.0003866 ETH
209757352024-10-16 4:28:2387 days ago1729052903
0xab3B1C8e...ba4B06aC1
0.01126986 ETH
209754792024-10-16 3:36:5987 days ago1729049819
0xab3B1C8e...ba4B06aC1
0.0003866 ETH
209752182024-10-16 2:44:3587 days ago1729046675
0xab3B1C8e...ba4B06aC1
0.01417636 ETH
209691692024-10-15 6:26:5988 days ago1728973619
0xab3B1C8e...ba4B06aC1
0.0003866 ETH
209688952024-10-15 5:31:5988 days ago1728970319
0xab3B1C8e...ba4B06aC1
0.01053797 ETH
209623532024-10-14 7:34:4789 days ago1728891287
0xab3B1C8e...ba4B06aC1
0.00041198 ETH
209592292024-10-13 21:06:4790 days ago1728853607
0xab3B1C8e...ba4B06aC1
0.01427636 ETH
209591922024-10-13 20:59:1190 days ago1728853151
0xab3B1C8e...ba4B06aC1
0.01427636 ETH
209578492024-10-13 16:29:3590 days ago1728836975
0xab3B1C8e...ba4B06aC1
0.00651368 ETH
209578492024-10-13 16:29:3590 days ago1728836975
0xab3B1C8e...ba4B06aC1
0.00651368 ETH
209576642024-10-13 15:51:5990 days ago1728834719
0xab3B1C8e...ba4B06aC1
0.01190848 ETH
209576222024-10-13 15:43:3590 days ago1728834215
0xab3B1C8e...ba4B06aC1
0.00041198 ETH
209576222024-10-13 15:43:3590 days ago1728834215
0xab3B1C8e...ba4B06aC1
0.001 ETH
209405902024-10-11 6:27:5992 days ago1728628079
0xab3B1C8e...ba4B06aC1
0.00039184 ETH
209369562024-10-10 18:18:2393 days ago1728584303
0xab3B1C8e...ba4B06aC1
0.00582157 ETH
209369562024-10-10 18:18:2393 days ago1728584303
0xab3B1C8e...ba4B06aC1
0.00582157 ETH
209367722024-10-10 17:41:2393 days ago1728582083
0xab3B1C8e...ba4B06aC1
0.01174726 ETH
209350782024-10-10 12:01:4793 days ago1728561707
0xab3B1C8e...ba4B06aC1
0.00882914 ETH
209312002024-10-09 23:03:2394 days ago1728515003
0xab3B1C8e...ba4B06aC1
0.00039184 ETH
209133872024-10-07 11:28:5996 days ago1728300539
0xab3B1C8e...ba4B06aC1
0.00039184 ETH
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
Spoke

Compiler Version
v0.8.20+commit.a1b79de6

Optimization Enabled:
Yes with 2000 runs

Other Settings:
paris EvmVersion
File 1 of 25 : Spoke.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {TokenSender} from "@wormhole-upgradeable/WormholeRelayerSDK.sol";
import {CCTPSender, CCTPBase} from "@wormhole-upgradeable/CCTPBase.sol";
import {VaaKey} from "@wormhole-upgradeable/interfaces/IWormholeRelayer.sol";
import "@wormhole-upgradeable/Utils.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "../HubSpokeStructs.sol";
import "./SpokeGetters.sol";
import "./SpokeUtilities.sol";
import "../wormhole/TokenReceiverWithCCTP.sol";

/**
 * @title Spoke
 * @notice The Spoke contract is the point of entry for cross-chain actions; users initiate an action by calling any of
 * the `public payable` functions (ex: `#depositCollateral`, `#withdrawCollateral`) with their desired asset and amount,
 * and using Wormhole we send the payload/tokens to the Hub on the target chain; if the action concludes with sending
 * tokens back to the user, we receive the final payload/tokens from the Hub before sending tokens to the user. This
 * contract also implements wormhole's CCTP contracts to send/receive USDC.
 */

contract Spoke is HubSpokeStructs, SpokeGetters, SpokeUtilities, TokenSender, CCTPSender, TokenReceiverWithCCTP, Ownable {
    using SafeERC20 for IERC20;

    /**
     * @notice Spoke constructor - Initializes a new spoke with given parameters
     *
     * @param chainId: Chain ID of the chain that this Spoke is deployed on
     * @param wormhole: Address of the Wormhole contract on this Spoke chain
     * @param tokenBridge: Address of the TokenBridge contract on this Spoke chain
     * @param relayer: Address of the WormholeRelayer contract on this Spoke chain
     * @param hubChainId: Chain ID of the Hub
     * @param hubContractAddress: Contract address of the Hub contract (on the Hub chain)
     * @param _circleMessageTransmitter: Cicle Message Transmitter contract (cctp)
     * @param _circleTokenMessenger: Cicle Token Messenger contract (cctp)
     * @param _USDC: USDC token contract (cctp)
     */
    constructor(
        uint16 chainId,
        address wormhole,
        address tokenBridge,
        address relayer,
        uint16 hubChainId,
        address hubContractAddress,
        address _circleMessageTransmitter,
        address _circleTokenMessenger,
        address _USDC
    ) Ownable(msg.sender) {
        CCTPBase.__CCTPBase_init(
            relayer,
            tokenBridge,
            wormhole,
            _circleMessageTransmitter,
            _circleTokenMessenger,
            _USDC
        );
        _state.chainId = chainId;
        _state.hubChainId = hubChainId;
        _state.hubContractAddress = hubContractAddress;
        _state.defaultGasLimitRoundtrip = 650_000;
        _state.isUsingCCTP = _circleMessageTransmitter != address(0); // zero address would indicate not using/supported
        setRegisteredSender(hubChainId, toWormholeFormat(hubContractAddress));
        // disable the registrationOwner so that the Hub can't be changed.
        registrationOwner = address(0);
    }

    /**
     * @notice Allows the contract deployer to set the default gas limit used in wormhole relay quotes
     *
     * @param value: the new value for `defaultGasLimitRoundtrip`
     */
    function setDefaultGasLimitRoundtrip(uint256 value) external onlyOwner {
        _state.defaultGasLimitRoundtrip = value;
    }

    /**
     * @notice Allows the contract deployer to toggle whether we are using CCTP for USDC
     * NOTE: If `_circleMessageTransmitter` is the null address, it indicates CCTP is not supported on this chain, thus
     * we don't do anything.
     *
     * @param value: the new value for `isUsingCCTP`
     */
    function setIsUsingCCTP(bool value) external onlyOwner {
        if (address(circleMessageTransmitter) == address(0)) return; // zero address would indicate not using/supported

        _state.isUsingCCTP = value;
    }

    /**
     * @notice Allows the caller to initiate a cross-chain deposit. The caller must have approved the `assetAmount` of
     * `asset` and must have provided enough `msg.value` to cover the relay
     *
     * @param asset: Addresss of the asset to deposit
     * @param assetAmount: Amount of the asset to deposit
     * @param costForReturnDelivery: The quoted cost for return delivery from the Hub (for refunds)
     * @return sequence number of the message sent.
     */
    function depositCollateral(address asset, uint256 assetAmount, uint256 costForReturnDelivery)
        public
        payable
        returns (uint64 sequence)
    {
        require(msg.value >= getDeliveryCostRoundtrip(costForReturnDelivery, true), "Insufficient value sent");
        sequence = _doAction(Action.Deposit, asset, assetAmount, costForReturnDelivery, false);
    }

    /**
     * @notice Allows the caller to initiate a cross-chain withdraw. The caller must have provided enough `msg.value`
     * to cover the relay and the return delivery
     *
     * @param asset: Addresss of the asset to withdraw
     * @param assetAmount: Amount of the asset to withdraw
     * @param costForReturnDelivery: The quoted cost for return delivery from the Hub
     * @return sequence number of the message sent.
     */
    function withdrawCollateral(address asset, uint256 assetAmount, uint256 costForReturnDelivery)
        public
        payable
        returns (uint64 sequence)
    {
        require(costForReturnDelivery > 0, "Non-zero costForReturnDelivery");
        sequence = _doAction(Action.Withdraw, asset, assetAmount, costForReturnDelivery, false);
    }

    /**
     * @notice Allows the caller to initiate a cross-chain borrow. The caller must have provided enough `msg.value`
     * to cover the relay and the return delivery
     *
     * @param asset: Addresss of the asset to borrow
     * @param assetAmount: Amount of the asset to borrow
     * @param costForReturnDelivery: The quoted cost for return delivery from the Hub
     * @return sequence number of the message sent.
     */
    function borrow(address asset, uint256 assetAmount, uint256 costForReturnDelivery)
        public
        payable
        returns (uint64 sequence)
    {
        require(costForReturnDelivery > 0, "Non-zero costForReturnDelivery");
        sequence = _doAction(Action.Borrow, asset, assetAmount, costForReturnDelivery, false);
    }

    /**
     * @notice Allows the caller to initiate a cross-chain repay. The caller must have approved the `assetAmount` of
     * `asset` and must have provided enough `msg.value` to cover the relay
     *
     * @param asset: Addresss of the asset to borrow
     * @param assetAmount: Amount of the asset to borrow
     * @param costForReturnDelivery: The quoted cost for return delivery from the Hub (for refunds)
     * @return sequence number of the message sent.
     */
    function repay(address asset, uint256 assetAmount, uint256 costForReturnDelivery)
        public
        payable
        returns (uint64 sequence)
    {
        require(msg.value >= getDeliveryCostRoundtrip(costForReturnDelivery, true), "Insufficient value sent");
        sequence = _doAction(Action.Repay, asset, assetAmount, costForReturnDelivery, false);
    }

    /**
     * @notice Allows the caller to initiate a cross-chain deposit with native tokens. The caller must have provided
     * enough `msg.value` to cover the relay+return and their desired deposit amount
     * @param costForReturnDelivery: The quoted cost for return delivery from the Hub (for refunds)
     * enough `msg.value` to cover the relay and their desired deposit amount
     * @return sequence number of the message sent.
     */
    function depositCollateralNative(uint256 costForReturnDelivery) public payable returns (uint64 sequence) {
        uint256 totalCost = getDeliveryCostRoundtrip(costForReturnDelivery, true);
        require(msg.value >= totalCost, "Spoke::depositCollateralNative:Insufficient value sent");
        uint256 amount = msg.value - totalCost;

        sequence = _doAction(Action.DepositNative, address(0), amount, costForReturnDelivery, false);
    }

    /**
     * @notice Allows the caller to initiate a cross-chain repay with native tokens. The caller must have provided
     * enough `msg.value` to cover the relay+return and their desired repay amount
     * @param costForReturnDelivery: The quoted cost for return delivery from the Hub (for refunds)
     * enough `msg.value` to cover the relay and their desired repay amount
     * @return sequence number of the message sent.
     */
    function repayNative(uint256 costForReturnDelivery) public payable returns (uint64 sequence) {
        uint256 totalCost = getDeliveryCostRoundtrip(costForReturnDelivery, true);
        require(msg.value >= totalCost, "Spoke::repayNative:Insufficient value sent");
        uint256 amount = msg.value - totalCost;

        sequence = _doAction(Action.RepayNative, address(0), amount, costForReturnDelivery, false);
    }

    /**
     * @notice Allows the caller to initiate a cross-chain withdraw with native tokens. The caller must have provided
     * enough `msg.value` to cover the relay and the return delivery
     *
     * @param assetAmount: Amount of the asset to withdraw
     * @param costForReturnDelivery: The quoted cost for return delivery from the Hub
     * @param unwrap: Whether to unwrap the native tokens or not
     * @return sequence number of the message sent.
     */
    function withdrawCollateralNative(uint256 assetAmount, uint256 costForReturnDelivery, bool unwrap)
        public
        payable
        returns (uint64 sequence)
    {
        sequence = _doAction(Action.Withdraw, address(tokenBridge.WETH()), assetAmount, costForReturnDelivery, unwrap);
    }

    /**
     * @notice Allows the caller to initiate a cross-chain borrow with native tokens. The caller must have provided
     * enough `msg.value` to cover the relay and the return delivery
     *
     * @param assetAmount: Amount of the asset to borrow
     * @param costForReturnDelivery: The quoted cost for return delivery from the Hub
     * @param unwrap: Whether to unwrap the native tokens or not
     * @return sequence number of the message sent.
     */
    function borrowNative(uint256 assetAmount, uint256 costForReturnDelivery, bool unwrap)
        public
        payable
        returns (uint64 sequence)
    {
        sequence = _doAction(Action.Borrow, address(tokenBridge.WETH()), assetAmount, costForReturnDelivery, unwrap);
    }

    /**
     * @notice Get the quote for the wormhole delivery cost, accounting for a forward() call on the Hub (in case of potential
     * reverts or to receive tokens on borrow/withdraw)
     *
     * @param costForReturnDelivery: the result of Hub#getCostForReturnDelivery()
     * @param withTokenTransfer: whether to include the message fee for a token bridge transfer (on deposit or repay)
     * @return cost for the forward() call on the Hub
     */
    function getDeliveryCostRoundtrip(uint256 costForReturnDelivery, bool withTokenTransfer)
        public
        view
        returns (uint256)
    {
        (uint256 cost,) =
            wormholeRelayer.quoteEVMDeliveryPrice(hubChainId(), costForReturnDelivery, defaultGasLimitRoundtrip());

        if (withTokenTransfer) {
            return cost + tokenBridge.wormhole().messageFee();
        }

        return cost;
    }

    /**
     * @dev Initiates an action (deposit, borrow, withdraw, or repay) on the spoke by sending
     * a Wormhole message (potentially a TokenBridge message with tokens) to the Hub
     * @param action - the action to be performed. It can be Deposit, Borrow, Withdraw, Repay, DepositNative, RepayNative.
     * @param asset - the address of the relevant asset. For native tokens like ETH, AVAX, this will be the zero address.
     * @param assetAmount - the amount of the asset to be involved in the action.
     * @param costForReturnDelivery - the cost to forward tokens back from the Hub
     * @param unwrap - a boolean value indicating whether to unwrap the asset or not.
     * @return sequence number of the message sent.
     */
    function _doAction(Action action, address asset, uint256 assetAmount, uint256 costForReturnDelivery, bool unwrap)
        internal
        returns (uint64 sequence)
    {

        require(assetAmount > 0, "No zero asset amount");

        bool withCCTP = asset == USDC && _state.isUsingCCTP;

        // for token transfers, only validate amount if we're using the token bridge
        if (asset != address(0) && !withCCTP) {
            requireAssetAmountValidForTokenBridge(asset, assetAmount);
        }

        Action hubAction = action;

        if (action == Action.DepositNative) {
            hubAction = Action.Deposit;
        } else if (action == Action.RepayNative) {
            hubAction = Action.Repay;
        }

        bool sendingTokens = action == Action.Deposit || action == Action.Repay;
        bool sendingUSDC = withCCTP && sendingTokens;
        bool receivingUSDC = withCCTP && !sendingTokens;
        bytes memory userPayload = abi.encode(uint8(hubAction), msg.sender, asset, unwrap, sendingUSDC, receivingUSDC);
        bytes memory payload = sendingUSDC
            ? userPayload
            : abi.encode(assetAmount, userPayload); // encoding again so it's the same format as cctp messages

        if (sendingTokens) {
            sequence = withCCTP
                ? _sendUSDCWithPayload(payload, assetAmount, costForReturnDelivery)
                : _sendTokenBridgeMessage(payload, asset, assetAmount, costForReturnDelivery);
        } else if (action == Action.Withdraw || action == Action.Borrow) {
            sequence = wormholeRelayer.sendPayloadToEvm{value: msg.value}(
                hubChainId(),
                hubContractAddress(),
                payload,
                costForReturnDelivery,
                defaultGasLimitRoundtrip(),
                chainId(), // refundChain
                msg.sender // refundAddress
            );
        } else if (action == Action.DepositNative || action == Action.RepayNative) {
            sequence = _sendTokenBridgeMessageNative(payload, assetAmount, costForReturnDelivery);
        }
    }

    /**
     * @dev Sends EVM Worlhole Relayer a message with the given payload and value
     * @param value - amount of ETH to be sent to relayer
     * @param payload - the payload to be sent
     * @param costForReturnDelivery - the cost to forward tokens back from the Hub
     * @param refundAddress The address on `refundChain` to deliver any refund to
     * @return sequence - the sequence number of the message sent
     */
    function _sendToEvmWormHoleRelayer(
        uint256 value,
        bytes memory payload,
        uint256 costForReturnDelivery,
        address refundAddress
    ) internal returns(uint64 sequence) {
        return wormholeRelayer.sendPayloadToEvm{value: value}(
            hubChainId(),
            hubContractAddress(),
            payload,
            costForReturnDelivery,
            defaultGasLimitRoundtrip(),
            hubChainId(),
            refundAddress
        );
    }

    /**
     * @dev Sends a TokenBridge message with the given payload, asset, and amount
     * @param payload - the payload to be sent
     * @param asset - the address of the asset to be sent
     * @param amount - the amount of the asset to be sent
     * @param costForReturnDelivery - the cost to forward tokens back from the Hub
     * @return sequence - the sequence number of the message sent
     */
    function _sendTokenBridgeMessage(bytes memory payload, address asset, uint256 amount, uint256 costForReturnDelivery)
        internal
        returns (uint64)
    {
        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);

        return sendTokenWithPayloadToEvm(
            hubChainId(),
            hubContractAddress(),
            payload,
            costForReturnDelivery,
            defaultGasLimitRoundtrip(),
            asset,
            amount,
            chainId(), // refundChain
            msg.sender // refundAddress
        );
    }

    /**
     * @dev Sends a TokenBridge message with the given payload and amount in native tokens
     * @param payload - the payload to be sent
     * @param amount - the amount of native tokens to be sent
     * @param costForReturnDelivery - the cost to forward tokens back from the Hub
     * @return sequence - the sequence number of the message sent
     */
    function _sendTokenBridgeMessageNative(bytes memory payload, uint256 amount, uint256 costForReturnDelivery)
        internal
        returns (uint64)
    {
        uint256 amountPlusFee = amount + tokenBridge.wormhole().messageFee();
        uint64 sequence = tokenBridge.wrapAndTransferETHWithPayload{value: amountPlusFee}(
            hubChainId(), toWormholeFormat(hubContractAddress()), 0, payload
        );

        VaaKey[] memory additionalVaas = new VaaKey[](1);
        additionalVaas[0] = VaaKey(chainId(), toWormholeFormat(address(tokenBridge)), sequence);

        uint256 deliveryCost = getDeliveryCostRoundtrip(costForReturnDelivery, false);
        return wormholeRelayer.sendVaasToEvm{value: deliveryCost}(
            hubChainId(),
            hubContractAddress(),
            payload,
            costForReturnDelivery,
            defaultGasLimitRoundtrip(),
            additionalVaas,
            chainId(), // refundChain
            msg.sender // refundAddress
        );
    }

    /**
     * @dev Sends USDC with the given payload via wormhole's CCTP integration
     * @param payload - the payload to be sent
     * @param amount - the amount of the asset to be sent
     * @param costForReturnDelivery - the cost to forward tokens back from the Hub
     * @return sequence - the sequence number of the message sent
     */
    function _sendUSDCWithPayload(bytes memory payload, uint256 amount, uint256 costForReturnDelivery)
        internal
        returns (uint64)
    {
        IERC20(USDC).safeTransferFrom(msg.sender, address(this), amount);

        return sendUSDCWithPayloadToEvm(
            hubChainId(),
            hubContractAddress(),
            payload,
            costForReturnDelivery,
            defaultGasLimitRoundtrip(),
            amount,
            chainId(), // refundChain
            msg.sender // refundAddress
        );
    }

    /**
     * @dev Returns whether we are using CCTP while receiving wormhole messages, as specified in the encoded `payload`
     * @param payload - the payload received
     */
    function messageWithCCTP(bytes memory payload) internal pure override returns (bool) {
        (,, bool withCCTP) = _decodePayload(payload);

        // NOTE: we are not checking _state.isUsingCCTP here in order to handle it as best effort
        return withCCTP;
    }

    function receiveWormholeMessages(
          bytes memory payload,
          bytes[] memory additionalVaas,
          bytes32 sourceAddress,
          uint16 sourceChain,
          bytes32 deliveryHash
    ) public virtual override(TokenReceiverWithCCTP) payable {
        if (additionalVaas.length == 0) {
            (address recipient,,) = _decodePayload(payload);
            // send any refund back to the recipient
            if (msg.value > 0) {
                (bool refundSuccess,) = recipient.call{value: msg.value}("");
                require(refundSuccess, "refund failed");
            }
        } else {
            super.receiveWormholeMessages(payload, additionalVaas, sourceAddress, sourceChain, deliveryHash);
        }
    }

    /**
     * @dev Receives a payload and tokens, and processes them
     * @param payload - the payload received
     * @param receivedTokens - the tokens received
     * @param sourceAddress - the source address of the tokens
     * @param sourceChain - the source chain of the tokens
     * @param deliveryHash - the delivery hash of the tokens
     */
    function receivePayloadAndTokens(
        bytes memory payload,
        TokenReceived[] memory receivedTokens,
        bytes32 sourceAddress,
        uint16 sourceChain,
        bytes32 deliveryHash
    )
        internal
        override
        onlyWormholeRelayer
        isRegisteredSender(sourceChain, sourceAddress)
        replayProtect(deliveryHash)
    {
        require(receivedTokens.length == 1, "Expecting one transfer");

        TokenReceived memory receivedToken = receivedTokens[0];
        (address recipient, bool unwrap,) = _decodePayload(payload);
        if (unwrap) {
            // unwrap and transfer to recipient
            tokenBridge.WETH().withdraw(receivedToken.amount);
            (bool withdrawSuccess,) = recipient.call{value: receivedToken.amount}("");
            require(withdrawSuccess, "withdraw to native failed");
        } else {
            IERC20(receivedToken.tokenAddress).safeTransfer(recipient, receivedToken.amount);
        }

        // send any refund back to the recipient
        if (msg.value > 0) {
            (bool refundSuccess,) = recipient.call{value: msg.value}("");
            require(refundSuccess, "refund failed");
        }
    }

    /**
     * @dev Receives a payload and USDC via CCTP
     * @param userPayload - the payload received
     * @param amountUSDCReceived - the amount of USDC received
     * @param sourceAddress - the source address of the tokens
     * @param sourceChain - the source chain of the tokens
     * @param deliveryHash - the delivery hash of the tokens
     */
    function receivePayloadAndUSDC(
        bytes memory userPayload,
        uint256 amountUSDCReceived,
        bytes32 sourceAddress,
        uint16 sourceChain,
        bytes32 deliveryHash
    )
        internal
        override
        onlyWormholeRelayer
        isRegisteredSender(sourceChain, sourceAddress)
        replayProtect(deliveryHash)
    {
        (address recipient,,) = abi.decode(userPayload, (address, bool, bool));

        IERC20(USDC).safeTransfer(recipient, amountUSDCReceived);

        // send any refund back to the recipient
        if (msg.value > 0) {
            (bool refundSuccess,) = recipient.call{value: msg.value}("");
            require(refundSuccess, "refund failed");
        }
    }

    /**
     * @dev Decodes `payload` into expected arguments
     * @param payload - the payload received
     */
    function _decodePayload(
        bytes memory payload
    ) internal pure returns (address recipient, bool unwrap, bool withCCTP) {
        (, bytes memory userPayload) = abi.decode(payload, (uint256, bytes));
        (recipient, unwrap, withCCTP) = abi.decode(userPayload, (address, bool, bool));
    }

    /**
     * @notice fallback function to receive unwrapped native asset
     */
    fallback() external payable {}

    /**
     * @notice Function to receive ETH
     */
    receive() external payable {}
}

File 2 of 25 : Ownable.sol
// 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);
    }
}

File 3 of 25 : IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

File 4 of 25 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
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);
}

File 5 of 25 : SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 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 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 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.
     */
    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.
     */
    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.
     */
    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 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).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            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 silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}

File 6 of 25 : Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

File 7 of 25 : Context.sol
// 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;
    }
}

File 8 of 25 : CCTPBase.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.13;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/IWormholeReceiver.sol";
import "./interfaces/IWormholeRelayer.sol";
import "./interfaces/ITokenBridge.sol";
import "./interfaces/CCTPInterfaces/ITokenMessenger.sol";
import "./interfaces/CCTPInterfaces/IMessageTransmitter.sol";

import "./Utils.sol";
import {TokenBase} from "./WormholeRelayerSDK.sol";

library CCTPMessageLib {
    uint8 constant CCTP_KEY_TYPE = 2;

    // encoded using abi.encodePacked(domain, nonce)
    struct CCTPKey {
        uint32 domain;
        uint64 nonce;
    }

    // encoded using abi.encode(message, signature)
    struct CCTPMessage {
        bytes message;
        bytes signature;
    }
}

abstract contract CCTPBase is TokenBase {
    ITokenMessenger public circleTokenMessenger;
    IMessageTransmitter public circleMessageTransmitter;
    address public USDC;

    function __CCTPBase_init(
        address _wormholeRelayer,
        address _tokenBridge,
        address _wormhole,
        address _circleMessageTransmitter,
        address _circleTokenMessenger,
        address _USDC
    ) public {
        require(!_wormholeRelayerInitialized, "WRI");
        TokenBase.__TokenBase_init(_wormholeRelayer, _tokenBridge, _wormhole);
        circleTokenMessenger = ITokenMessenger(_circleTokenMessenger);
        circleMessageTransmitter = IMessageTransmitter(_circleMessageTransmitter);
        USDC = _USDC;
    }

    function getCCTPDomain(uint16 chain) internal pure returns (uint32) {
        if (chain == 2) {
            return 0;
        } else if (chain == 6) {
            return 1;
        } else if (chain == 23) {
            return 3;
        } else if (chain == 24) {
            return 2;
        } else {
            revert("Wrong CCTP Domain");
        }
    }

    function redeemUSDC(bytes memory cctpMessage) internal returns (uint256 amount) {
        (bytes memory message, bytes memory signature) = abi.decode(cctpMessage, (bytes, bytes));
        uint256 beforeBalance = IERC20(USDC).balanceOf(address(this));
        circleMessageTransmitter.receiveMessage(message, signature);
        return IERC20(USDC).balanceOf(address(this)) - beforeBalance;
    }
}

abstract contract CCTPSender is CCTPBase {
    uint8 internal constant CONSISTENCY_LEVEL_FINALIZED = 15;

    using CCTPMessageLib for *;

    /**
     * transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer
     * - approves tokenBridge to spend 'amount' of 'token'
     * - emits token transfer VAA
     * - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument
     *
     * Note: this requires that only the targetAddress can redeem transfers.
     *
     */

    function transferUSDC(uint256 amount, uint16 targetChain, address targetAddress)
        internal
        returns (MessageKey memory)
    {
        SafeERC20.forceApprove(IERC20(USDC), address(circleTokenMessenger), amount);
        uint64 nonce = circleTokenMessenger.depositForBurnWithCaller(
            amount,
            getCCTPDomain(targetChain),
            addressToBytes32CCTP(targetAddress),
            USDC,
            addressToBytes32CCTP(targetAddress)
        );
        return MessageKey(
            CCTPMessageLib.CCTP_KEY_TYPE, abi.encodePacked(getCCTPDomain(wormhole.chainId()), nonce)
        );
    }

    function sendUSDCWithPayloadToEvm(
        uint16 targetChain,
        address targetAddress,
        bytes memory payload,
        uint256 receiverValue,
        uint256 gasLimit,
        uint256 amount,
        uint16 refundChain,
        address refundAddress
    ) internal returns (uint64 sequence) {
        MessageKey[] memory messageKeys = new MessageKey[](1);
        messageKeys[0] = transferUSDC(amount, targetChain, targetAddress);

        address defaultDeliveryProvider = wormholeRelayer.getDefaultDeliveryProvider();

        (uint256 cost,) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit);

        sequence = wormholeRelayer.sendToEvm{value: cost}(
            targetChain,
            targetAddress,
            abi.encode(amount, payload),
            receiverValue,
            0,
            gasLimit,
            refundChain,
            refundAddress,
            defaultDeliveryProvider,
            messageKeys,
            CONSISTENCY_LEVEL_FINALIZED
        );
    }

    function addressToBytes32CCTP(address addr) private pure returns (bytes32) {
        return bytes32(uint256(uint160(addr)));
    }
}

abstract contract CCTPReceiver is CCTPBase {
    function receiveWormholeMessages(
        bytes memory payload,
        bytes[] memory additionalMessages,
        bytes32 sourceAddress,
        uint16 sourceChain,
        bytes32 deliveryHash
    ) external virtual payable {
        _receiveWormholeMessagesWithCCTP(payload, additionalMessages, sourceAddress, sourceChain, deliveryHash);
    }

    function _receiveWormholeMessagesWithCCTP(
        bytes memory payload,
        bytes[] memory additionalMessages,
        bytes32 sourceAddress,
        uint16 sourceChain,
        bytes32 deliveryHash
    ) internal {
        require(additionalMessages.length <= 1, "CCTP: At most one Message is supported");

        uint256 amountUSDCReceived;
        if (additionalMessages.length == 1) {
            amountUSDCReceived = redeemUSDC(additionalMessages[0]);
        }

        (uint256 amount, bytes memory userPayload) = abi.decode(payload, (uint256, bytes));

        // Check that the correct amount was received
        // It is important to verify that the 'USDC' received is
        require(amount == amountUSDCReceived, "Wrong amount received");

        receivePayloadAndUSDC(userPayload, amountUSDCReceived, sourceAddress, sourceChain, deliveryHash);
    }

    function receivePayloadAndUSDC(
        bytes memory payload,
        uint256 amountUSDCReceived,
        bytes32 sourceAddress,
        uint16 sourceChain,
        bytes32 deliveryHash
    ) internal virtual {}
}

File 9 of 25 : IMessageTransmitter.sol
/*
 * Copyright (c) 2022, Circle Internet Financial Limited.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
pragma solidity ^0.8.0;

import "./IRelayer.sol";
import "./IReceiver.sol";

/**
 * @title IMessageTransmitter
 * @notice Interface for message transmitters, which both relay and receive messages.
 */
interface IMessageTransmitter is IRelayer, IReceiver {

}

File 10 of 25 : IReceiver.sol
/*
 * Copyright (c) 2022, Circle Internet Financial Limited.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
pragma solidity ^0.8.0;

/**
 * @title IReceiver
 * @notice Receives messages on destination chain and forwards them to IMessageDestinationHandler
 */
interface IReceiver {
    /**
     * @notice Receives an incoming message, validating the header and passing
     * the body to application-specific handler.
     * @param message The message raw bytes
     * @param signature The message signature
     * @return success bool, true if successful
     */
    function receiveMessage(bytes calldata message, bytes calldata signature)
        external
        returns (bool success);
}

File 11 of 25 : IRelayer.sol
/*
 * Copyright (c) 2022, Circle Internet Financial Limited.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
pragma solidity ^0.8.0;

/**
 * @title IRelayer
 * @notice Sends messages from source domain to destination domain
 */
interface IRelayer {
    /**
     * @notice Sends an outgoing message from the source domain.
     * @dev Increment nonce, format the message, and emit `MessageSent` event with message information.
     * @param destinationDomain Domain of destination chain
     * @param recipient Address of message recipient on destination domain as bytes32
     * @param messageBody Raw bytes content of message
     * @return nonce reserved by message
     */
    function sendMessage(
        uint32 destinationDomain,
        bytes32 recipient,
        bytes calldata messageBody
    ) external returns (uint64);

    /**
     * @notice Sends an outgoing message from the source domain, with a specified caller on the
     * destination domain.
     * @dev Increment nonce, format the message, and emit `MessageSent` event with message information.
     * WARNING: if the `destinationCaller` does not represent a valid address as bytes32, then it will not be possible
     * to broadcast the message on the destination domain. This is an advanced feature, and the standard
     * sendMessage() should be preferred for use cases where a specific destination caller is not required.
     * @param destinationDomain Domain of destination chain
     * @param recipient Address of message recipient on destination domain as bytes32
     * @param destinationCaller caller on the destination domain, as bytes32
     * @param messageBody Raw bytes content of message
     * @return nonce reserved by message
     */
    function sendMessageWithCaller(
        uint32 destinationDomain,
        bytes32 recipient,
        bytes32 destinationCaller,
        bytes calldata messageBody
    ) external returns (uint64);

    /**
     * @notice Replace a message with a new message body and/or destination caller.
     * @dev The `originalAttestation` must be a valid attestation of `originalMessage`.
     * @param originalMessage original message to replace
     * @param originalAttestation attestation of `originalMessage`
     * @param newMessageBody new message body of replaced message
     * @param newDestinationCaller the new destination caller
     */
    function replaceMessage(
        bytes calldata originalMessage,
        bytes calldata originalAttestation,
        bytes calldata newMessageBody,
        bytes32 newDestinationCaller
    ) external;
}

File 12 of 25 : ITokenMessenger.sol
pragma solidity ^0.8.0;

interface ITokenMessenger {
   /**
     * @notice Deposits and burns tokens from sender to be minted on destination domain. The mint
     * on the destination domain must be called by `destinationCaller`.
     * WARNING: if the `destinationCaller` does not represent a valid address as bytes32, then it will not be possible
     * to broadcast the message on the destination domain. This is an advanced feature, and the standard
     * depositForBurn() should be preferred for use cases where a specific destination caller is not required.
     * Emits a `DepositForBurn` event.
     * @dev reverts if:
     * - given destinationCaller is zero address
     * - given burnToken is not supported
     * - given destinationDomain has no TokenMessenger registered
     * - transferFrom() reverts. For example, if sender's burnToken balance or approved allowance
     * to this contract is less than `amount`.
     * - burn() reverts. For example, if `amount` is 0.
     * - MessageTransmitter returns false or reverts.
     * @param amount amount of tokens to burn
     * @param destinationDomain destination domain
     * @param mintRecipient address of mint recipient on destination domain
     * @param burnToken address of contract to burn deposited tokens, on local domain
     * @param destinationCaller caller on the destination domain, as bytes32
     * @return nonce unique nonce reserved by message
     */
    function depositForBurnWithCaller(
        uint256 amount,
        uint32 destinationDomain,
        bytes32 mintRecipient,
        address burnToken,
        bytes32 destinationCaller
    ) external returns (uint64 nonce);
}

File 13 of 25 : ITokenBridge.sol
// contracts/Bridge.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;

import "./IWETH.sol";
import "./IWormhole.sol";

interface ITokenBridge {
    struct Transfer {
        uint8 payloadID;
        uint256 amount;
        bytes32 tokenAddress;
        uint16 tokenChain;
        bytes32 to;
        uint16 toChain;
        uint256 fee;
    }

    struct TransferWithPayload {
        uint8 payloadID;
        uint256 amount;
        bytes32 tokenAddress;
        uint16 tokenChain;
        bytes32 to;
        uint16 toChain;
        bytes32 fromAddress;
        bytes payload;
    }

    struct AssetMeta {
        uint8 payloadID;
        bytes32 tokenAddress;
        uint16 tokenChain;
        uint8 decimals;
        bytes32 symbol;
        bytes32 name;
    }

    struct RegisterChain {
        bytes32 module;
        uint8 action;
        uint16 chainId;
        uint16 emitterChainID;
        bytes32 emitterAddress;
    }

    struct UpgradeContract {
        bytes32 module;
        uint8 action;
        uint16 chainId;
        bytes32 newContract;
    }

    struct RecoverChainId {
        bytes32 module;
        uint8 action;
        uint256 evmChainId;
        uint16 newChainId;
    }

    event ContractUpgraded(address indexed oldContract, address indexed newContract);

    function _parseTransferCommon(bytes memory encoded) external pure returns (Transfer memory transfer);

    function attestToken(address tokenAddress, uint32 nonce) external payable returns (uint64 sequence);

    function wrapAndTransferETH(uint16 recipientChain, bytes32 recipient, uint256 arbiterFee, uint32 nonce)
        external
        payable
        returns (uint64 sequence);

    function wrapAndTransferETHWithPayload(uint16 recipientChain, bytes32 recipient, uint32 nonce, bytes memory payload)
        external
        payable
        returns (uint64 sequence);

    function transferTokens(
        address token,
        uint256 amount,
        uint16 recipientChain,
        bytes32 recipient,
        uint256 arbiterFee,
        uint32 nonce
    ) external payable returns (uint64 sequence);

    function transferTokensWithPayload(
        address token,
        uint256 amount,
        uint16 recipientChain,
        bytes32 recipient,
        uint32 nonce,
        bytes memory payload
    ) external payable returns (uint64 sequence);

    function updateWrapped(bytes memory encodedVm) external returns (address token);

    function createWrapped(bytes memory encodedVm) external returns (address token);

    function completeTransferWithPayload(bytes memory encodedVm) external returns (bytes memory);

    function completeTransferAndUnwrapETHWithPayload(bytes memory encodedVm) external returns (bytes memory);

    function completeTransfer(bytes memory encodedVm) external;

    function completeTransferAndUnwrapETH(bytes memory encodedVm) external;

    function encodeAssetMeta(AssetMeta memory meta) external pure returns (bytes memory encoded);

    function encodeTransfer(Transfer memory transfer) external pure returns (bytes memory encoded);

    function encodeTransferWithPayload(TransferWithPayload memory transfer)
        external
        pure
        returns (bytes memory encoded);

    function parsePayloadID(bytes memory encoded) external pure returns (uint8 payloadID);

    function parseAssetMeta(bytes memory encoded) external pure returns (AssetMeta memory meta);

    function parseTransfer(bytes memory encoded) external pure returns (Transfer memory transfer);

    function parseTransferWithPayload(bytes memory encoded)
        external
        pure
        returns (TransferWithPayload memory transfer);

    function governanceActionIsConsumed(bytes32 hash) external view returns (bool);

    function isInitialized(address impl) external view returns (bool);

    function isTransferCompleted(bytes32 hash) external view returns (bool);

    function wormhole() external view returns (IWormhole);

    function chainId() external view returns (uint16);

    function evmChainId() external view returns (uint256);

    function isFork() external view returns (bool);

    function governanceChainId() external view returns (uint16);

    function governanceContract() external view returns (bytes32);

    function wrappedAsset(uint16 tokenChainId, bytes32 tokenAddress) external view returns (address);

    function bridgeContracts(uint16 chainId_) external view returns (bytes32);

    function tokenImplementation() external view returns (address);

    function WETH() external view returns (IWETH);

    function outstandingBridged(address token) external view returns (uint256);

    function isWrappedAsset(address token) external view returns (bool);

    function finality() external view returns (uint8);

    function implementation() external view returns (address);

    function initialize() external;

    function registerChain(bytes memory encodedVM) external;

    function upgrade(bytes memory encodedVM) external;

    function submitRecoverChainId(bytes memory encodedVM) external;

    function parseRegisterChain(bytes memory encoded) external pure returns (RegisterChain memory chain);

    function parseUpgrade(bytes memory encoded) external pure returns (UpgradeContract memory chain);

    function parseRecoverChainId(bytes memory encodedRecoverChainId)
        external
        pure
        returns (RecoverChainId memory rci);
}

File 14 of 25 : IWETH.sol
// contracts/Bridge.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IWETH is IERC20 {
    function deposit() external payable;
    function withdraw(uint256 amount) external;
}

File 15 of 25 : IWormhole.sol
// contracts/Messages.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;

interface IWormhole {
    struct GuardianSet {
        address[] keys;
        uint32 expirationTime;
    }

    struct Signature {
        bytes32 r;
        bytes32 s;
        uint8 v;
        uint8 guardianIndex;
    }

    struct VM {
        uint8 version;
        uint32 timestamp;
        uint32 nonce;
        uint16 emitterChainId;
        bytes32 emitterAddress;
        uint64 sequence;
        uint8 consistencyLevel;
        bytes payload;
        uint32 guardianSetIndex;
        Signature[] signatures;
        bytes32 hash;
    }

    struct ContractUpgrade {
        bytes32 module;
        uint8 action;
        uint16 chain;
        address newContract;
    }

    struct GuardianSetUpgrade {
        bytes32 module;
        uint8 action;
        uint16 chain;
        GuardianSet newGuardianSet;
        uint32 newGuardianSetIndex;
    }

    struct SetMessageFee {
        bytes32 module;
        uint8 action;
        uint16 chain;
        uint256 messageFee;
    }

    struct TransferFees {
        bytes32 module;
        uint8 action;
        uint16 chain;
        uint256 amount;
        bytes32 recipient;
    }

    struct RecoverChainId {
        bytes32 module;
        uint8 action;
        uint256 evmChainId;
        uint16 newChainId;
    }

    event LogMessagePublished(
        address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel
    );
    event ContractUpgraded(address indexed oldContract, address indexed newContract);
    event GuardianSetAdded(uint32 indexed index);

    function publishMessage(uint32 nonce, bytes memory payload, uint8 consistencyLevel)
        external
        payable
        returns (uint64 sequence);

    function initialize() external;

    function parseAndVerifyVM(bytes calldata encodedVM)
        external
        view
        returns (VM memory vm, bool valid, string memory reason);

    function verifyVM(VM memory vm) external view returns (bool valid, string memory reason);

    function verifySignatures(bytes32 hash, Signature[] memory signatures, GuardianSet memory guardianSet)
        external
        pure
        returns (bool valid, string memory reason);

    function parseVM(bytes memory encodedVM) external pure returns (VM memory vm);

    function quorum(uint256 numGuardians) external pure returns (uint256 numSignaturesRequiredForQuorum);

    function getGuardianSet(uint32 index) external view returns (GuardianSet memory);

    function getCurrentGuardianSetIndex() external view returns (uint32);

    function getGuardianSetExpiry() external view returns (uint32);

    function governanceActionIsConsumed(bytes32 hash) external view returns (bool);

    function isInitialized(address impl) external view returns (bool);

    function chainId() external view returns (uint16);

    function isFork() external view returns (bool);

    function governanceChainId() external view returns (uint16);

    function governanceContract() external view returns (bytes32);

    function messageFee() external view returns (uint256);

    function evmChainId() external view returns (uint256);

    function nextSequence(address emitter) external view returns (uint64);

    function parseContractUpgrade(bytes memory encodedUpgrade) external pure returns (ContractUpgrade memory cu);

    function parseGuardianSetUpgrade(bytes memory encodedUpgrade)
        external
        pure
        returns (GuardianSetUpgrade memory gsu);

    function parseSetMessageFee(bytes memory encodedSetMessageFee) external pure returns (SetMessageFee memory smf);

    function parseTransferFees(bytes memory encodedTransferFees) external pure returns (TransferFees memory tf);

    function parseRecoverChainId(bytes memory encodedRecoverChainId)
        external
        pure
        returns (RecoverChainId memory rci);

    function submitContractUpgrade(bytes memory _vm) external;

    function submitSetMessageFee(bytes memory _vm) external;

    function submitNewGuardianSet(bytes memory _vm) external;

    function submitTransferFees(bytes memory _vm) external;

    function submitRecoverChainId(bytes memory _vm) external;
}

File 16 of 25 : IWormholeReceiver.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;

/**
 * @notice Interface for a contract which can receive Wormhole messages.
 */
interface IWormholeReceiver {
    /**
     * @notice When a `send` is performed with this contract as the target, this function will be
     *     invoked by the WormholeRelayer contract
     *
     * NOTE: This function should be restricted such that only the Wormhole Relayer contract can call it.
     *
     * We also recommend that this function:
     *   - Stores all received `deliveryHash`s in a mapping `(bytes32 => bool)`, and
     *       on every call, checks that deliveryHash has not already been stored in the
     *       map (This is to prevent other users maliciously trying to relay the same message)
     *   - Checks that `sourceChain` and `sourceAddress` are indeed who
     *       you expect to have requested the calling of `send` on the source chain
     *
     * The invocation of this function corresponding to the `send` request will have msg.value equal
     *   to the receiverValue specified in the send request.
     *
     * If the invocation of this function reverts or exceeds the gas limit
     *   specified by the send requester, this delivery will result in a `ReceiverFailure`.
     *
     * @param payload - an arbitrary message which was included in the delivery by the
     *     requester.
     * @param additionalVaas - Additional VAAs which were requested to be included in this delivery.
     *   They are guaranteed to all be included and in the same order as was specified in the
     *     delivery request.
     * @param sourceAddress - the (wormhole format) address on the sending chain which requested
     *     this delivery.
     * @param sourceChain - the wormhole chain ID where this delivery was requested.
     * @param deliveryHash - the VAA hash of the deliveryVAA.
     *
     * NOTE: These signedVaas are NOT verified by the Wormhole core contract prior to being provided
     *     to this call. Always make sure `parseAndVerify()` is called on the Wormhole core contract
     *     before trusting the content of a raw VAA, otherwise the VAA may be invalid or malicious.
     */
    function receiveWormholeMessages(
        bytes memory payload,
        bytes[] memory additionalVaas,
        bytes32 sourceAddress,
        uint16 sourceChain,
        bytes32 deliveryHash
    ) external payable;
}

File 17 of 25 : IWormholeRelayer.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;

/**
 * @title WormholeRelayer
 * @author
 * @notice This project allows developers to build cross-chain applications powered by Wormhole without needing to
 * write and run their own relaying infrastructure
 *
 * We implement the IWormholeRelayer interface that allows users to request a delivery provider to relay a payload (and/or additional VAAs)
 * to a chain and address of their choice.
 */

/**
 * @notice VaaKey identifies a wormhole message
 *
 * @custom:member chainId Wormhole chain ID of the chain where this VAA was emitted from
 * @custom:member emitterAddress Address of the emitter of the VAA, in Wormhole bytes32 format
 * @custom:member sequence Sequence number of the VAA
 */
struct VaaKey {
    uint16 chainId;
    bytes32 emitterAddress;
    uint64 sequence;
}

// 0-127 are reserved for standardized KeyTypes, 128-255 are for custom use
uint8 constant VAA_KEY_TYPE = 1;

struct MessageKey {
    uint8 keyType; // 0-127 are reserved for standardized KeyTypes, 128-255 are for custom use
    bytes encodedKey;
}


interface IWormholeRelayerBase {
    event SendEvent(
        uint64 indexed sequence, uint256 deliveryQuote, uint256 paymentForExtraReceiverValue
    );

    function getRegisteredWormholeRelayerContract(uint16 chainId) external view returns (bytes32);

    /**
     * @notice Returns true if a delivery has been attempted for the given deliveryHash
     * Note: invalid deliveries where the tx reverts are not considered attempted
     */
    function deliveryAttempted(bytes32 deliveryHash) external view returns (bool attempted);

    /**
     * @notice block number at which a delivery was successfully executed
     */
    function deliverySuccessBlock(bytes32 deliveryHash) external view returns (uint256 blockNumber);

    /**
     * @notice block number of the latest attempt to execute a delivery that failed
     */
    function deliveryFailureBlock(bytes32 deliveryHash) external view returns (uint256 blockNumber);
}

/**
 * @title IWormholeRelayerSend
 * @notice The interface to request deliveries
 */
interface IWormholeRelayerSend is IWormholeRelayerBase {

    /**
     * @notice Publishes an instruction for the default delivery provider
     * to relay a payload to the address `targetAddress` on chain `targetChain`
     * with gas limit `gasLimit` and `msg.value` equal to `receiverValue`
     *
     * `targetAddress` must implement the IWormholeReceiver interface
     *
     * This function must be called with `msg.value` equal to `quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit)`
     *
     * Any refunds (from leftover gas) will be paid to the delivery provider. In order to receive the refunds, use the `sendPayloadToEvm` function
     * with `refundChain` and `refundAddress` as parameters
     *
     * @param targetChain in Wormhole Chain ID format
     * @param targetAddress address to call on targetChain (that implements IWormholeReceiver)
     * @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
     * @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
     * @param gasLimit gas limit with which to call `targetAddress`.
     * @return sequence sequence number of published VAA containing delivery instructions
     */
    function sendPayloadToEvm(
        uint16 targetChain,
        address targetAddress,
        bytes memory payload,
        uint256 receiverValue,
        uint256 gasLimit
    ) external payable returns (uint64 sequence);

    /**
     * @notice Publishes an instruction for the default delivery provider
     * to relay a payload to the address `targetAddress` on chain `targetChain`
     * with gas limit `gasLimit` and `msg.value` equal to `receiverValue`
     *
     * Any refunds (from leftover gas) will be sent to `refundAddress` on chain `refundChain`
     * `targetAddress` must implement the IWormholeReceiver interface
     *
     * This function must be called with `msg.value` equal to `quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit)`
     *
     * @param targetChain in Wormhole Chain ID format
     * @param targetAddress address to call on targetChain (that implements IWormholeReceiver)
     * @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
     * @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
     * @param gasLimit gas limit with which to call `targetAddress`. Any units of gas unused will be refunded according to the
     *        `targetChainRefundPerGasUnused` rate quoted by the delivery provider
     * @param refundChain The chain to deliver any refund to, in Wormhole Chain ID format
     * @param refundAddress The address on `refundChain` to deliver any refund to
     * @return sequence sequence number of published VAA containing delivery instructions
     */
    function sendPayloadToEvm(
        uint16 targetChain,
        address targetAddress,
        bytes memory payload,
        uint256 receiverValue,
        uint256 gasLimit,
        uint16 refundChain,
        address refundAddress
    ) external payable returns (uint64 sequence);

    /**
     * @notice Publishes an instruction for the default delivery provider
     * to relay a payload and VAAs specified by `vaaKeys` to the address `targetAddress` on chain `targetChain`
     * with gas limit `gasLimit` and `msg.value` equal to `receiverValue`
     *
     * `targetAddress` must implement the IWormholeReceiver interface
     *
     * This function must be called with `msg.value` equal to `quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit)`
     *
     * Any refunds (from leftover gas) will be paid to the delivery provider. In order to receive the refunds, use the `sendVaasToEvm` function
     * with `refundChain` and `refundAddress` as parameters
     *
     * @param targetChain in Wormhole Chain ID format
     * @param targetAddress address to call on targetChain (that implements IWormholeReceiver)
     * @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
     * @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
     * @param gasLimit gas limit with which to call `targetAddress`.
     * @param vaaKeys Additional VAAs to pass in as parameter in call to `targetAddress`
     * @return sequence sequence number of published VAA containing delivery instructions
     */
    function sendVaasToEvm(
        uint16 targetChain,
        address targetAddress,
        bytes memory payload,
        uint256 receiverValue,
        uint256 gasLimit,
        VaaKey[] memory vaaKeys
    ) external payable returns (uint64 sequence);

    /**
     * @notice Publishes an instruction for the default delivery provider
     * to relay a payload and VAAs specified by `vaaKeys` to the address `targetAddress` on chain `targetChain`
     * with gas limit `gasLimit` and `msg.value` equal to `receiverValue`
     *
     * Any refunds (from leftover gas) will be sent to `refundAddress` on chain `refundChain`
     * `targetAddress` must implement the IWormholeReceiver interface
     *
     * This function must be called with `msg.value` equal to `quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit)`
     *
     * @param targetChain in Wormhole Chain ID format
     * @param targetAddress address to call on targetChain (that implements IWormholeReceiver)
     * @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
     * @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
     * @param gasLimit gas limit with which to call `targetAddress`. Any units of gas unused will be refunded according to the
     *        `targetChainRefundPerGasUnused` rate quoted by the delivery provider
     * @param vaaKeys Additional VAAs to pass in as parameter in call to `targetAddress`
     * @param refundChain The chain to deliver any refund to, in Wormhole Chain ID format
     * @param refundAddress The address on `refundChain` to deliver any refund to
     * @return sequence sequence number of published VAA containing delivery instructions
     */
    function sendVaasToEvm(
        uint16 targetChain,
        address targetAddress,
        bytes memory payload,
        uint256 receiverValue,
        uint256 gasLimit,
        VaaKey[] memory vaaKeys,
        uint16 refundChain,
        address refundAddress
    ) external payable returns (uint64 sequence);

    /**
     * @notice Publishes an instruction for the delivery provider at `deliveryProviderAddress`
     * to relay a payload and VAAs specified by `vaaKeys` to the address `targetAddress` on chain `targetChain`
     * with gas limit `gasLimit` and `msg.value` equal to
     * receiverValue + (arbitrary amount that is paid for by paymentForExtraReceiverValue of this chain's wei) in targetChain wei.
     *
     * Any refunds (from leftover gas) will be sent to `refundAddress` on chain `refundChain`
     * `targetAddress` must implement the IWormholeReceiver interface
     *
     * This function must be called with `msg.value` equal to
     * quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit, deliveryProviderAddress) + paymentForExtraReceiverValue
     *
     * @param targetChain in Wormhole Chain ID format
     * @param targetAddress address to call on targetChain (that implements IWormholeReceiver)
     * @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
     * @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
     * @param paymentForExtraReceiverValue amount (in current chain currency units) to spend on extra receiverValue
     *        (in addition to the `receiverValue` specified)
     * @param gasLimit gas limit with which to call `targetAddress`. Any units of gas unused will be refunded according to the
     *        `targetChainRefundPerGasUnused` rate quoted by the delivery provider
     * @param refundChain The chain to deliver any refund to, in Wormhole Chain ID format
     * @param refundAddress The address on `refundChain` to deliver any refund to
     * @param deliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
     * @param vaaKeys Additional VAAs to pass in as parameter in call to `targetAddress`
     * @param consistencyLevel Consistency level with which to publish the delivery instructions - see
     *        https://book.wormhole.com/wormhole/3_coreLayerContracts.html?highlight=consistency#consistency-levels
     * @return sequence sequence number of published VAA containing delivery instructions
     */
    function sendToEvm(
        uint16 targetChain,
        address targetAddress,
        bytes memory payload,
        uint256 receiverValue,
        uint256 paymentForExtraReceiverValue,
        uint256 gasLimit,
        uint16 refundChain,
        address refundAddress,
        address deliveryProviderAddress,
        VaaKey[] memory vaaKeys,
        uint8 consistencyLevel
    ) external payable returns (uint64 sequence);

    /**
     * @notice Publishes an instruction for the delivery provider at `deliveryProviderAddress`
     * to relay a payload and external messages specified by `messageKeys` to the address `targetAddress` on chain `targetChain`
     * with gas limit `gasLimit` and `msg.value` equal to
     * receiverValue + (arbitrary amount that is paid for by paymentForExtraReceiverValue of this chain's wei) in targetChain wei.
     *
     * Any refunds (from leftover gas) will be sent to `refundAddress` on chain `refundChain`
     * `targetAddress` must implement the IWormholeReceiver interface
     *
     * This function must be called with `msg.value` equal to
     * quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit, deliveryProviderAddress) + paymentForExtraReceiverValue
     *
     * MessageKeys can specify wormhole messages (VaaKeys) or other types of messages (ex. USDC CCTP attestations). Ensure the selected
     * Note: DeliveryProvider supports all the MessageKey.keyType values specified or it will not be delivered!
     *
     * @param targetChain in Wormhole Chain ID format
     * @param targetAddress address to call on targetChain (that implements IWormholeReceiver)
     * @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
     * @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
     * @param paymentForExtraReceiverValue amount (in current chain currency units) to spend on extra receiverValue
     *        (in addition to the `receiverValue` specified)
     * @param gasLimit gas limit with which to call `targetAddress`. Any units of gas unused will be refunded according to the
     *        `targetChainRefundPerGasUnused` rate quoted by the delivery provider
     * @param refundChain The chain to deliver any refund to, in Wormhole Chain ID format
     * @param refundAddress The address on `refundChain` to deliver any refund to
     * @param deliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
     * @param messageKeys Additional messagess to pass in as parameter in call to `targetAddress`
     * @param consistencyLevel Consistency level with which to publish the delivery instructions - see
     *        https://book.wormhole.com/wormhole/3_coreLayerContracts.html?highlight=consistency#consistency-levels
     * @return sequence sequence number of published VAA containing delivery instructions
     */
    function sendToEvm(
        uint16 targetChain,
        address targetAddress,
        bytes memory payload,
        uint256 receiverValue,
        uint256 paymentForExtraReceiverValue,
        uint256 gasLimit,
        uint16 refundChain,
        address refundAddress,
        address deliveryProviderAddress,
        MessageKey[] memory messageKeys,
        uint8 consistencyLevel
    ) external payable returns (uint64 sequence);

    /**
     * @notice Publishes an instruction for the delivery provider at `deliveryProviderAddress`
     * to relay a payload and VAAs specified by `vaaKeys` to the address `targetAddress` on chain `targetChain`
     * with `msg.value` equal to
     * receiverValue + (arbitrary amount that is paid for by paymentForExtraReceiverValue of this chain's wei) in targetChain wei.
     *
     * Any refunds (from leftover gas) will be sent to `refundAddress` on chain `refundChain`
     * `targetAddress` must implement the IWormholeReceiver interface
     *
     * This function must be called with `msg.value` equal to
     * quoteDeliveryPrice(targetChain, receiverValue, encodedExecutionParameters, deliveryProviderAddress) + paymentForExtraReceiverValue
     *
     * @param targetChain in Wormhole Chain ID format
     * @param targetAddress address to call on targetChain (that implements IWormholeReceiver), in Wormhole bytes32 format
     * @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
     * @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
     * @param paymentForExtraReceiverValue amount (in current chain currency units) to spend on extra receiverValue
     *        (in addition to the `receiverValue` specified)
     * @param encodedExecutionParameters encoded information on how to execute delivery that may impact pricing
     *        e.g. for version EVM_V1, this is a struct that encodes the `gasLimit` with which to call `targetAddress`
     * @param refundChain The chain to deliver any refund to, in Wormhole Chain ID format
     * @param refundAddress The address on `refundChain` to deliver any refund to, in Wormhole bytes32 format
     * @param deliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
     * @param vaaKeys Additional VAAs to pass in as parameter in call to `targetAddress`
     * @param consistencyLevel Consistency level with which to publish the delivery instructions - see
     *        https://book.wormhole.com/wormhole/3_coreLayerContracts.html?highlight=consistency#consistency-levels
     * @return sequence sequence number of published VAA containing delivery instructions
     */
    function send(
        uint16 targetChain,
        bytes32 targetAddress,
        bytes memory payload,
        uint256 receiverValue,
        uint256 paymentForExtraReceiverValue,
        bytes memory encodedExecutionParameters,
        uint16 refundChain,
        bytes32 refundAddress,
        address deliveryProviderAddress,
        VaaKey[] memory vaaKeys,
        uint8 consistencyLevel
    ) external payable returns (uint64 sequence);

    /**
     * @notice Publishes an instruction for the delivery provider at `deliveryProviderAddress`
     * to relay a payload and VAAs specified by `vaaKeys` to the address `targetAddress` on chain `targetChain`
     * with `msg.value` equal to
     * receiverValue + (arbitrary amount that is paid for by paymentForExtraReceiverValue of this chain's wei) in targetChain wei.
     *
     * Any refunds (from leftover gas) will be sent to `refundAddress` on chain `refundChain`
     * `targetAddress` must implement the IWormholeReceiver interface
     *
     * This function must be called with `msg.value` equal to
     * quoteDeliveryPrice(targetChain, receiverValue, encodedExecutionParameters, deliveryProviderAddress) + paymentForExtraReceiverValue
     *
     * MessageKeys can specify wormhole messages (VaaKeys) or other types of messages (ex. USDC CCTP attestations). Ensure the selected
     * Note: DeliveryProvider supports all the MessageKey.keyType values specified or it will not be delivered!
     *
     * @param targetChain in Wormhole Chain ID format
     * @param targetAddress address to call on targetChain (that implements IWormholeReceiver), in Wormhole bytes32 format
     * @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
     * @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
     * @param paymentForExtraReceiverValue amount (in current chain currency units) to spend on extra receiverValue
     *        (in addition to the `receiverValue` specified)
     * @param encodedExecutionParameters encoded information on how to execute delivery that may impact pricing
     *        e.g. for version EVM_V1, this is a struct that encodes the `gasLimit` with which to call `targetAddress`
     * @param refundChain The chain to deliver any refund to, in Wormhole Chain ID format
     * @param refundAddress The address on `refundChain` to deliver any refund to, in Wormhole bytes32 format
     * @param deliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
     * @param messageKeys Additional messagess to pass in as parameter in call to `targetAddress`
     * @param consistencyLevel Consistency level with which to publish the delivery instructions - see
     *        https://book.wormhole.com/wormhole/3_coreLayerContracts.html?highlight=consistency#consistency-levels
     * @return sequence sequence number of published VAA containing delivery instructions
     */
    function send(
        uint16 targetChain,
        bytes32 targetAddress,
        bytes memory payload,
        uint256 receiverValue,
        uint256 paymentForExtraReceiverValue,
        bytes memory encodedExecutionParameters,
        uint16 refundChain,
        bytes32 refundAddress,
        address deliveryProviderAddress,
        MessageKey[] memory messageKeys,
        uint8 consistencyLevel
    ) external payable returns (uint64 sequence);

    /**
     * @notice Requests a previously published delivery instruction to be redelivered
     * (e.g. with a different delivery provider)
     *
     * This function must be called with `msg.value` equal to
     * quoteEVMDeliveryPrice(targetChain, newReceiverValue, newGasLimit, newDeliveryProviderAddress)
     *
     *  @notice *** This will only be able to succeed if the following is true **
     *         - newGasLimit >= gas limit of the old instruction
     *         - newReceiverValue >= receiver value of the old instruction
     *         - newDeliveryProvider's `targetChainRefundPerGasUnused` >= old relay provider's `targetChainRefundPerGasUnused`
     *
     * @param deliveryVaaKey VaaKey identifying the wormhole message containing the
     *        previously published delivery instructions
     * @param targetChain The target chain that the original delivery targeted. Must match targetChain from original delivery instructions
     * @param newReceiverValue new msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
     * @param newGasLimit gas limit with which to call `targetAddress`. Any units of gas unused will be refunded according to the
     *        `targetChainRefundPerGasUnused` rate quoted by the delivery provider, to the refund chain and address specified in the original request
     * @param newDeliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
     * @return sequence sequence number of published VAA containing redelivery instructions
     *
     * @notice *** This will only be able to succeed if the following is true **
     *         - newGasLimit >= gas limit of the old instruction
     *         - newReceiverValue >= receiver value of the old instruction
     *         - newDeliveryProvider's `targetChainRefundPerGasUnused` >= old relay provider's `targetChainRefundPerGasUnused`
     */
    function resendToEvm(
        VaaKey memory deliveryVaaKey,
        uint16 targetChain,
        uint256 newReceiverValue,
        uint256 newGasLimit,
        address newDeliveryProviderAddress
    ) external payable returns (uint64 sequence);

    /**
     * @notice Requests a previously published delivery instruction to be redelivered
     *
     *
     * This function must be called with `msg.value` equal to
     * quoteDeliveryPrice(targetChain, newReceiverValue, newEncodedExecutionParameters, newDeliveryProviderAddress)
     *
     * @param deliveryVaaKey VaaKey identifying the wormhole message containing the
     *        previously published delivery instructions
     * @param targetChain The target chain that the original delivery targeted. Must match targetChain from original delivery instructions
     * @param newReceiverValue new msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
     * @param newEncodedExecutionParameters new encoded information on how to execute delivery that may impact pricing
     *        e.g. for version EVM_V1, this is a struct that encodes the `gasLimit` with which to call `targetAddress`
     * @param newDeliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
     * @return sequence sequence number of published VAA containing redelivery instructions
     *
     *  @notice *** This will only be able to succeed if the following is true **
     *         - (For EVM_V1) newGasLimit >= gas limit of the old instruction
     *         - newReceiverValue >= receiver value of the old instruction
     *         - (For EVM_V1) newDeliveryProvider's `targetChainRefundPerGasUnused` >= old relay provider's `targetChainRefundPerGasUnused`
     */
    function resend(
        VaaKey memory deliveryVaaKey,
        uint16 targetChain,
        uint256 newReceiverValue,
        bytes memory newEncodedExecutionParameters,
        address newDeliveryProviderAddress
    ) external payable returns (uint64 sequence);

    /**
     * @notice Returns the price to request a relay to chain `targetChain`, using the default delivery provider
     *
     * @param targetChain in Wormhole Chain ID format
     * @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
     * @param gasLimit gas limit with which to call `targetAddress`.
     * @return nativePriceQuote Price, in units of current chain currency, that the delivery provider charges to perform the relay
     * @return targetChainRefundPerGasUnused amount of target chain currency that will be refunded per unit of gas unused,
     *         if a refundAddress is specified
     */
    function quoteEVMDeliveryPrice(
        uint16 targetChain,
        uint256 receiverValue,
        uint256 gasLimit
    ) external view returns (uint256 nativePriceQuote, uint256 targetChainRefundPerGasUnused);

    /**
     * @notice Returns the price to request a relay to chain `targetChain`, using delivery provider `deliveryProviderAddress`
     *
     * @param targetChain in Wormhole Chain ID format
     * @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
     * @param gasLimit gas limit with which to call `targetAddress`.
     * @param deliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
     * @return nativePriceQuote Price, in units of current chain currency, that the delivery provider charges to perform the relay
     * @return targetChainRefundPerGasUnused amount of target chain currency that will be refunded per unit of gas unused,
     *         if a refundAddress is specified
     */
    function quoteEVMDeliveryPrice(
        uint16 targetChain,
        uint256 receiverValue,
        uint256 gasLimit,
        address deliveryProviderAddress
    ) external view returns (uint256 nativePriceQuote, uint256 targetChainRefundPerGasUnused);

    /**
     * @notice Returns the price to request a relay to chain `targetChain`, using delivery provider `deliveryProviderAddress`
     *
     * @param targetChain in Wormhole Chain ID format
     * @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
     * @param encodedExecutionParameters encoded information on how to execute delivery that may impact pricing
     *        e.g. for version EVM_V1, this is a struct that encodes the `gasLimit` with which to call `targetAddress`
     * @param deliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
     * @return nativePriceQuote Price, in units of current chain currency, that the delivery provider charges to perform the relay
     * @return encodedExecutionInfo encoded information on how the delivery will be executed
     *        e.g. for version EVM_V1, this is a struct that encodes the `gasLimit` and `targetChainRefundPerGasUnused`
     *             (which is the amount of target chain currency that will be refunded per unit of gas unused,
     *              if a refundAddress is specified)
     */
    function quoteDeliveryPrice(
        uint16 targetChain,
        uint256 receiverValue,
        bytes memory encodedExecutionParameters,
        address deliveryProviderAddress
    ) external view returns (uint256 nativePriceQuote, bytes memory encodedExecutionInfo);

    /**
     * @notice Returns the (extra) amount of target chain currency that `targetAddress`
     * will be called with, if the `paymentForExtraReceiverValue` field is set to `currentChainAmount`
     *
     * @param targetChain in Wormhole Chain ID format
     * @param currentChainAmount The value that `paymentForExtraReceiverValue` will be set to
     * @param deliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
     * @return targetChainAmount The amount such that if `targetAddress` will be called with `msg.value` equal to
     *         receiverValue + targetChainAmount
     */
    function quoteNativeForChain(
        uint16 targetChain,
        uint256 currentChainAmount,
        address deliveryProviderAddress
    ) external view returns (uint256 targetChainAmount);

    /**
     * @notice Returns the address of the current default delivery provider
     * @return deliveryProvider The address of (the default delivery provider)'s contract on this source
     *   chain. This must be a contract that implements IDeliveryProvider.
     */
    function getDefaultDeliveryProvider() external view returns (address deliveryProvider);
}

/**
 * @title IWormholeRelayerDelivery
 * @notice The interface to execute deliveries. Only relevant for Delivery Providers
 */
interface IWormholeRelayerDelivery is IWormholeRelayerBase {
    enum DeliveryStatus {
        SUCCESS,
        RECEIVER_FAILURE
    }

    enum RefundStatus {
        REFUND_SENT,
        REFUND_FAIL,
        CROSS_CHAIN_REFUND_SENT,
        CROSS_CHAIN_REFUND_FAIL_PROVIDER_NOT_SUPPORTED,
        CROSS_CHAIN_REFUND_FAIL_NOT_ENOUGH
    }

    /**
     * @custom:member recipientContract - The target contract address
     * @custom:member sourceChain - The chain which this delivery was requested from (in wormhole
     *     ChainID format)
     * @custom:member sequence - The wormhole sequence number of the delivery VAA on the source chain
     *     corresponding to this delivery request
     * @custom:member deliveryVaaHash - The hash of the delivery VAA corresponding to this delivery
     *     request
     * @custom:member gasUsed - The amount of gas that was used to call your target contract
     * @custom:member status:
     *   - RECEIVER_FAILURE, if the target contract reverts
     *   - SUCCESS, if the target contract doesn't revert
     * @custom:member additionalStatusInfo:
     *   - If status is SUCCESS, then this is empty.
     *   - If status is RECEIVER_FAILURE, this is `RETURNDATA_TRUNCATION_THRESHOLD` bytes of the
     *       return data (i.e. potentially truncated revert reason information).
     * @custom:member refundStatus - Result of the refund. REFUND_SUCCESS or REFUND_FAIL are for
     *     refunds where targetChain=refundChain; the others are for targetChain!=refundChain,
     *     where a cross chain refund is necessary
     * @custom:member overridesInfo:
     *   - If not an override: empty bytes array
     *   - Otherwise: An encoded `DeliveryOverride`
     */
    event Delivery(
        address indexed recipientContract,
        uint16 indexed sourceChain,
        uint64 indexed sequence,
        bytes32 deliveryVaaHash,
        DeliveryStatus status,
        uint256 gasUsed,
        RefundStatus refundStatus,
        bytes additionalStatusInfo,
        bytes overridesInfo
    );

    /**
     * @notice The delivery provider calls `deliver` to relay messages as described by one delivery instruction
     *
     * The delivery provider must pass in the specified (by VaaKeys[]) signed wormhole messages (VAAs) from the source chain
     * as well as the signed wormhole message with the delivery instructions (the delivery VAA)
     *
     * The messages will be relayed to the target address (with the specified gas limit and receiver value) iff the following checks are met:
     * - the delivery VAA has a valid signature
     * - the delivery VAA's emitter is one of these WormholeRelayer contracts
     * - the delivery provider passed in at least enough of this chain's currency as msg.value (enough meaning the maximum possible refund)
     * - the instruction's target chain is this chain
     * - the relayed signed VAAs match the descriptions in container.messages (the VAA hashes match, or the emitter address, sequence number pair matches, depending on the description given)
     *
     * @param encodedVMs - An array of signed wormhole messages (all from the same source chain
     *     transaction)
     * @param encodedDeliveryVAA - Signed wormhole message from the source chain's WormholeRelayer
     *     contract with payload being the encoded delivery instruction container
     * @param relayerRefundAddress - The address to which any refunds to the delivery provider
     *     should be sent
     * @param deliveryOverrides - Optional overrides field which must be either an empty bytes array or
     *     an encoded DeliveryOverride struct
     */
    function deliver(
        bytes[] memory encodedVMs,
        bytes memory encodedDeliveryVAA,
        address payable relayerRefundAddress,
        bytes memory deliveryOverrides
    ) external payable;
}

interface IWormholeRelayer is IWormholeRelayerDelivery, IWormholeRelayerSend {}

/*
 *  Errors thrown by IWormholeRelayer contract
 */

// Bound chosen by the following formula: `memoryWord * 4 + selectorSize`.
// This means that an error identifier plus four fixed size arguments should be available to developers.
// In the case of a `require` revert with error message, this should provide 2 memory word's worth of data.
uint256 constant RETURNDATA_TRUNCATION_THRESHOLD = 132;

//When msg.value was not equal to `delivery provider's quoted delivery price` + `paymentForExtraReceiverValue`
error InvalidMsgValue(uint256 msgValue, uint256 totalFee);

error RequestedGasLimitTooLow();

error DeliveryProviderDoesNotSupportTargetChain(address relayer, uint16 chainId);
error DeliveryProviderCannotReceivePayment();
error DeliveryProviderDoesNotSupportMessageKeyType(uint8 keyType);

//When calling `delivery()` a second time even though a delivery is already in progress
error ReentrantDelivery(address msgSender, address lockedBy);

error InvalidPayloadId(uint8 parsed, uint8 expected);
error InvalidPayloadLength(uint256 received, uint256 expected);
error InvalidVaaKeyType(uint8 parsed);
error TooManyMessageKeys(uint256 numMessageKeys);

error InvalidDeliveryVaa(string reason);
//When the delivery VAA (signed wormhole message with delivery instructions) was not emitted by the
//  registered WormholeRelayer contract
error InvalidEmitter(bytes32 emitter, bytes32 registered, uint16 chainId);
error MessageKeysLengthDoesNotMatchMessagesLength(uint256 keys, uint256 vaas);
error VaaKeysDoNotMatchVaas(uint8 index);
//When someone tries to call an external function of the WormholeRelayer that is only intended to be
//  called by the WormholeRelayer itself (to allow retroactive reverts for atomicity)
error RequesterNotWormholeRelayer();

//When trying to relay a `DeliveryInstruction` to any other chain but the one it was specified for
error TargetChainIsNotThisChain(uint16 targetChain);
//When a `DeliveryOverride` contains a gas limit that's less than the original
error InvalidOverrideGasLimit();
//When a `DeliveryOverride` contains a receiver value that's less than the original
error InvalidOverrideReceiverValue();
//When a `DeliveryOverride` contains a 'refund per unit of gas unused' that's less than the original
error InvalidOverrideRefundPerGasUnused();

//When the delivery provider doesn't pass in sufficient funds (i.e. msg.value does not cover the
// maximum possible refund to the user)
error InsufficientRelayerFunds(uint256 msgValue, uint256 minimum);

//When a bytes32 field can't be converted into a 20 byte EVM address, because the 12 padding bytes
//  are non-zero (duplicated from Utils.sol)
error NotAnEvmAddress(bytes32);

File 18 of 25 : Utils.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "./interfaces/IWormholeRelayer.sol";

function toWormholeFormat(address addr) pure returns (bytes32) {
    return bytes32(uint256(uint160(addr)));
}

function fromWormholeFormat(bytes32 whFormatAddress) pure returns (address) {
    if (uint256(whFormatAddress) >> 160 != 0) {
        revert NotAnEvmAddress(whFormatAddress);
    }
    return address(uint160(uint256(whFormatAddress)));
}

File 19 of 25 : WormholeRelayerSDK.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/IWormholeReceiver.sol";
import "./interfaces/IWormholeRelayer.sol";
import "./interfaces/ITokenBridge.sol";

import "./Utils.sol";

abstract contract Base {
    IWormholeRelayer public wormholeRelayer;
    IWormhole public wormhole;

    mapping(bytes32 => bool) public seenDeliveryVaaHashes;

    address registrationOwner;
    mapping(uint16 => bytes32) registeredSenders;

    bool internal _wormholeRelayerInitialized;

    function __Base_init(address _wormholeRelayer, address _wormhole) public {
        require(!_wormholeRelayerInitialized, "WRI");
        _wormholeRelayerInitialized = true;
        wormholeRelayer = IWormholeRelayer(_wormholeRelayer);
        wormhole = IWormhole(_wormhole);
        registrationOwner = msg.sender;
    }

    modifier onlyWormholeRelayer() {
        require(msg.sender == address(wormholeRelayer), "Msg.sender is not Wormhole Relayer");
        _;
    }

    modifier replayProtect(bytes32 deliveryHash) {
        require(!seenDeliveryVaaHashes[deliveryHash], "Message already processed");
        seenDeliveryVaaHashes[deliveryHash] = true;
        _;
    }

    modifier isRegisteredSender(uint16 sourceChain, bytes32 sourceAddress) {
        require(registeredSenders[sourceChain] == sourceAddress, "Not registered sender");
        _;
    }

    /**
     * Sets the registered address for 'sourceChain' to 'sourceAddress'
     * So that for messages from 'sourceChain', only ones from 'sourceAddress' are valid
     *
     * Assumes only one sender per chain is valid
     * Sender is the address that called 'send' on the Wormhole Relayer contract on the source chain)
     */
    function setRegisteredSender(uint16 sourceChain, bytes32 sourceAddress) public {
        require(msg.sender == registrationOwner, "Not allowed to set registered sender");
        registeredSenders[sourceChain] = sourceAddress;
    }
}

abstract contract TokenBase is Base {
    ITokenBridge public tokenBridge;

    function __TokenBase_init(address _wormholeRelayer, address _tokenBridge, address _wormhole) public {
        require(!_wormholeRelayerInitialized, "WRI");
        Base.__Base_init(_wormholeRelayer, _wormhole);
        tokenBridge = ITokenBridge(_tokenBridge);
    }

    function getDecimals(address tokenAddress) internal view returns (uint8 decimals) {
        // query decimals
        (, bytes memory queriedDecimals) = address(tokenAddress).staticcall(abi.encodeWithSignature("decimals()"));
        decimals = abi.decode(queriedDecimals, (uint8));
    }

    function getTokenAddressOnThisChain(uint16 tokenHomeChain, bytes32 tokenHomeAddress)
        internal
        view
        returns (address tokenAddressOnThisChain)
    {
        return tokenHomeChain == wormhole.chainId()
            ? fromWormholeFormat(tokenHomeAddress)
            : tokenBridge.wrappedAsset(tokenHomeChain, tokenHomeAddress);
    }
}

abstract contract TokenSender is TokenBase {
    /**
     * transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer
     * - approves tokenBridge to spend 'amount' of 'token'
     * - emits token transfer VAA
     * - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument
     *
     * Note: this function uses transferTokensWithPayload instead of transferTokens since the former requires that only the targetAddress
     *       can redeem transfers. Otherwise it's possible for another address to redeem the transfer before the targetContract is invoked by
     *       the offchain relayer and the target contract would have to be hardened against this.
     *
     */
    function transferTokens(address token, uint256 amount, uint16 targetChain, address targetAddress)
        internal
        returns (VaaKey memory)
    {
        return transferTokens(token, amount, targetChain, targetAddress, bytes(""));
    }

    /**
     * transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer.
     * A payload can be included in the transfer vaa. By including a payload here instead of the deliveryVaa,
     * fewer trust assumptions are placed on the WormholeRelayer contract.
     *
     * - approves tokenBridge to spend 'amount' of 'token'
     * - emits token transfer VAA
     * - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument
     *
     * Note: this function uses transferTokensWithPayload instead of transferTokens since the former requires that only the targetAddress
     *       can redeem transfers. Otherwise it's possible for another address to redeem the transfer before the targetContract is invoked by
     *       the offchain relayer and the target contract would have to be hardened against this.
     */
    function transferTokens(
        address token,
        uint256 amount,
        uint16 targetChain,
        address targetAddress,
        bytes memory payload
    ) internal returns (VaaKey memory) {
        SafeERC20.forceApprove(IERC20(token), address(tokenBridge), amount);
        uint64 sequence = tokenBridge.transferTokensWithPayload{value: wormhole.messageFee()}(
            token, amount, targetChain, toWormholeFormat(targetAddress), 0, payload
        );
        return VaaKey({
            emitterAddress: toWormholeFormat(address(tokenBridge)),
            chainId: wormhole.chainId(),
            sequence: sequence
        });
    }

    function sendTokenWithPayloadToEvm(
        uint16 targetChain,
        address targetAddress,
        bytes memory payload,
        uint256 receiverValue,
        uint256 gasLimit,
        address token,
        uint256 amount
    ) internal returns (uint64) {
        VaaKey[] memory vaaKeys = new VaaKey[](1);
        vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress);

        (uint256 cost,) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit);
        return wormholeRelayer.sendVaasToEvm{value: cost}(
            targetChain, targetAddress, payload, receiverValue, gasLimit, vaaKeys
        );
    }

    function sendTokenWithPayloadToEvm(
        uint16 targetChain,
        address targetAddress,
        bytes memory payload,
        uint256 receiverValue,
        uint256 gasLimit,
        address token,
        uint256 amount,
        uint16 refundChain,
        address refundAddress
    ) internal returns (uint64) {
        VaaKey[] memory vaaKeys = new VaaKey[](1);
        vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress);

        (uint256 cost,) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit);
        return wormholeRelayer.sendVaasToEvm{value: cost}(
            targetChain, targetAddress, payload, receiverValue, gasLimit, vaaKeys, refundChain, refundAddress
        );
    }
}

abstract contract TokenReceiver is TokenBase {
    struct TokenReceived {
        bytes32 tokenHomeAddress;
        uint16 tokenHomeChain;
        address tokenAddress; // wrapped address if tokenHomeChain !== this chain, else tokenHomeAddress (in evm address format)
        uint256 amount;
        uint256 amountNormalized; // if decimals > 8, normalized to 8 decimal places
    }

    function receiveWormholeMessages(
        bytes memory payload,
        bytes[] memory additionalVaas,
        bytes32 sourceAddress,
        uint16 sourceChain,
        bytes32 deliveryHash
    ) external virtual payable {
        _receiveWormholeMessages(payload, additionalVaas, sourceAddress, sourceChain, deliveryHash);
    }

    function _receiveWormholeMessages(
        bytes memory payload,
        bytes[] memory additionalVaas,
        bytes32 sourceAddress,
        uint16 sourceChain,
        bytes32 deliveryHash
    ) internal {
        TokenReceived[] memory receivedTokens = new TokenReceived[](additionalVaas.length);

        for (uint256 i = 0; i < additionalVaas.length; ++i) {
            IWormhole.VM memory parsed = wormhole.parseVM(additionalVaas[i]);
            require(
                parsed.emitterAddress == tokenBridge.bridgeContracts(parsed.emitterChainId), "Not a Token Bridge VAA"
            );
            ITokenBridge.TransferWithPayload memory transfer = tokenBridge.parseTransferWithPayload(parsed.payload);
            require(
                transfer.to == toWormholeFormat(address(this)) && transfer.toChain == wormhole.chainId(),
                "Token was not sent to this address"
            );

            tokenBridge.completeTransferWithPayload(additionalVaas[i]);

            address thisChainTokenAddress = getTokenAddressOnThisChain(transfer.tokenChain, transfer.tokenAddress);
            uint8 decimals = getDecimals(thisChainTokenAddress);
            uint256 denormalizedAmount = transfer.amount;
            if (decimals > 8) denormalizedAmount *= uint256(10) ** (decimals - 8);

            receivedTokens[i] = TokenReceived({
                tokenHomeAddress: transfer.tokenAddress,
                tokenHomeChain: transfer.tokenChain,
                tokenAddress: thisChainTokenAddress,
                amount: denormalizedAmount,
                amountNormalized: transfer.amount
            });
        }

        // call into overriden method
        receivePayloadAndTokens(payload, receivedTokens, sourceAddress, sourceChain, deliveryHash);
    }

    function receivePayloadAndTokens(
        bytes memory payload,
        TokenReceived[] memory receivedTokens,
        bytes32 sourceAddress,
        uint16 sourceChain,
        bytes32 deliveryHash
    ) internal virtual {}
}

File 20 of 25 : HubSpokeStructs.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

/**
 * @title HubSpokeStructs
 * @notice A set of structs and enums used in the Hub and Spoke contracts
 */
contract HubSpokeStructs {
    /**
     * @param wormhole: Address of the Wormhole contract
     * @param tokenBridge: Address of the TokenBridge contract
     * @param wormholeRelayer: Address of the WormholeRelayer contract
     * @param consistencyLevel: Desired level of finality the Wormhole guardians will reach before signing the messages
     * NOTE: consistencyLevel = 200 will result in an instant message, while all other values will wait for finality
     * Recommended finality levels can be found here: https://book.wormhole.com/reference/contracts.html
     * @param pythAddress: Address of the Pyth oracle on the Hub chain
     * @param priceStandardDeviations: priceStandardDeviations = (psd * priceStandardDeviationsPrecision), where psd is
     * the number of standard deviations that we use for our price intervals in calculations relating to allowing
     * withdraws, borrows, or liquidations
     * @param priceStandardDeviationsPrecision: A precision number that allows us to represent our desired noninteger
     * price standard deviation as an integer (psd = priceStandardDeviations/priceStandardDeviationsPrecision)
     * @param maxLiquidationPortionPrecision: A precision number that allows us to represent our desired noninteger
     * max liquidation portion mlp as an integer (mlp = maxLiquidationPortion/maxLiquidationPortionPrecision)
     * @param interestAccrualIndexPrecision: A precision number that allows us to represent our noninteger interest
     * accrual indices as integers; we store each index as its true value multiplied by interestAccrualIndexPrecision
     * @param collateralizationRatioPrecision: A precision number that allows us to represent our noninteger
     * collateralization ratios as integers; we store each ratio as its true value multiplied by
     * collateralizationRatioPrecision
     * @param liquidationFee: The fee taken by the protocol on liquidation
     * @param _circleMessageTransmitter: Cicle Message Transmitter contract (cctp)
     * @param _circleTokenMessenger: Cicle Token Messenger contract (cctp)
     * @param _USDC: USDC token contract (cctp)
     */
    struct ConstructorArgs {
        /* Wormhole Information */
        address wormhole;
        address tokenBridge;
        address wormholeRelayer;
        uint8 consistencyLevel;
        /* Liquidation Information */
        uint256 interestAccrualIndexPrecision;
        uint256 liquidationFee;
        uint256 liquidationFeePrecision;
        /* CCTP Information */
        address circleMessageTransmitter;
        address circleTokenMessenger;
        address USDC;
    }

    struct StoredVaultAmount {
        DenormalizedVaultAmount amounts;
        AccrualIndices accrualIndices;
    }

    struct DenormalizedVaultAmount {
        uint256 deposited;
        uint256 borrowed;
    }

    struct NotionalVaultAmount {
        uint256 deposited;
        uint256 borrowed;
    }

    struct AccrualIndices {
        uint256 deposited;
        uint256 borrowed;
    }

    struct AssetInfo {
        uint256 collateralizationRatioDeposit;
        uint256 collateralizationRatioBorrow;
        uint8 decimals;
        address interestRateCalculator;
        bool exists;
        uint256 borrowLimit;
        uint256 supplyLimit;
        uint256 maxLiquidationPortion;
        uint256 maxLiquidationBonus; // 1e6 precision; 130e4 = 130% = 1.3; the liquidator gets 30% over what he repays
    }

    /**
     * @dev Struct to hold the decoded data from a Wormhole payload
     * @param action The action to be performed (e.g., Deposit, Borrow, Withdraw, Repay)
     * @param sender The address of the sender initiating the action
     * @param wrappedAsset The address of the wrapped asset involved in the action
     * @param amount The amount of the wrapped asset involved in the action
     * @param unwrap A boolean indicating whether to unwrap the asset or not for native withdraws and borrows
     */
    struct PayloadData {
        Action action;
        address sender;
        address wrappedAsset;
        uint256 amount;
        bool unwrap;
    }

    enum Action {
        Deposit,
        Borrow,
        Withdraw,
        Repay,
        DepositNative,
        RepayNative
    }

    enum Round {
        UP,
        DOWN
    }
}

File 21 of 25 : SpokeGetters.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "./SpokeState.sol";

/**
 * @title SpokeGetters
 * @notice A set of public getter functions
 */
contract SpokeGetters is SpokeState {
    function chainId() public view returns (uint16) {
        return _state.chainId;
    }

    function consistencyLevel() public view returns (uint8) {
        return _state.consistencyLevel;
    }

    function hubChainId() public view returns (uint16) {
        return _state.hubChainId;
    }

    function hubContractAddress() public view returns (address) {
        return _state.hubContractAddress;
    }

    function defaultGasLimitRoundtrip() public view returns (uint256) {
        return _state.defaultGasLimitRoundtrip;
    }
}

File 22 of 25 : SpokeState.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "../HubSpokeStructs.sol";

/**
 * @title SpokeStorage
 * @notice Contract defining state variables for the Spoke contract
 */
contract SpokeStorage is HubSpokeStructs {
    struct State {
        uint16 chainId;
        // number of confirmations for wormhole messages
        uint8 consistencyLevel;
        uint16 hubChainId;
        address hubContractAddress;
        uint256 defaultGasLimitRoundtrip;
        bool isUsingCCTP;
        // @dev storage gap
        uint256[50] ______gap;
    }
}

/**
 * @title SpokeState
 * @notice Contract holding state variable for the Spoke contract
 */
contract SpokeState {
    SpokeStorage.State _state;
}

File 23 of 25 : SpokeUtilities.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "../../interfaces/IERC20decimals.sol";

/**
 * @title SpokeUtilities
 * @notice A set of internal utility functions
 */
contract SpokeUtilities {
    /**
     * @dev This function checks if the asset amount is valid for the token bridge
     * @param assetAddress The address of the asset
     * @param assetAmount The amount of the asset
     */
    function requireAssetAmountValidForTokenBridge(address assetAddress, uint256 assetAmount) internal view {
        uint8 decimals = IERC20decimals(assetAddress).decimals();
        require(
            deNormalizeAmount(normalizeAmount(assetAmount, decimals), decimals) == assetAmount,
            "Too many decimal places"
        );
    }

    /**
     * @dev This function normalizes the amount based on the decimals
     * @param amount The amount to be normalized
     * @param decimals The number of decimals
     * @return The normalized amount
     */
    function normalizeAmount(uint256 amount, uint8 decimals) internal pure returns (uint256) {
        if (decimals > 8) {
            amount /= 10 ** (decimals - 8);
        } else if (decimals < 8){
            amount *= 10 ** (8 - decimals);
        }

        return amount;
    }

    /**
     * @dev This function denormalizes the amount based on the decimals
     * @param amount The amount to be denormalized
     * @param decimals The number of decimals
     * @return The denormalized amount
     */
    function deNormalizeAmount(uint256 amount, uint8 decimals) internal pure returns (uint256) {
        if (decimals > 8) {
            amount *= 10 ** (decimals - 8);
        } else if (decimals < 8) {
            amount /= 10 ** (8 - decimals);
        }

        return amount;
    }
}

File 24 of 25 : TokenReceiverWithCCTP.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {TokenReceiver} from "@wormhole-upgradeable/WormholeRelayerSDK.sol";
import {CCTPReceiver} from "@wormhole-upgradeable/CCTPBase.sol";

abstract contract TokenReceiverWithCCTP is CCTPReceiver, TokenReceiver {
    /**
     * @dev Overriding the superclasses' function to choose whether to use CCTP or not, based on the implemented
     * `isUsingCCTP` function
     * @param payload - the payload received
     * @param additionalVaas - any wormhole VAAs received
     * @param sourceAddress - the source address of the tokens
     * @param sourceChain - the source chain of the tokens
     * @param deliveryHash - the delivery hash of the tokens
     */
    function receiveWormholeMessages(
          bytes memory payload,
          bytes[] memory additionalVaas,
          bytes32 sourceAddress,
          uint16 sourceChain,
          bytes32 deliveryHash
    ) public virtual override(TokenReceiver, CCTPReceiver) payable {
        if (messageWithCCTP(payload)) {
            _receiveWormholeMessagesWithCCTP(payload, additionalVaas, sourceAddress, sourceChain, deliveryHash);
        } else {
            _receiveWormholeMessages(payload, additionalVaas, sourceAddress, sourceChain, deliveryHash);
        }
    }

    /**
     * @dev Virtual function to decode `payload` and determine if using CCTP or not
     * @param payload - the payload received
     */
    function messageWithCCTP(bytes memory payload) internal view virtual returns (bool);
}

File 25 of 25 : IERC20decimals.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IERC20decimals is IERC20 {
    function decimals() external view returns (uint8);
}

Settings
{
  "viaIR": true,
  "optimizer": {
    "enabled": true,
    "runs": 2000
  },
  "evmVersion": "paris",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "metadata": {
    "useLiteralContent": true
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"uint16","name":"chainId","type":"uint16"},{"internalType":"address","name":"wormhole","type":"address"},{"internalType":"address","name":"tokenBridge","type":"address"},{"internalType":"address","name":"relayer","type":"address"},{"internalType":"uint16","name":"hubChainId","type":"uint16"},{"internalType":"address","name":"hubContractAddress","type":"address"},{"internalType":"address","name":"_circleMessageTransmitter","type":"address"},{"internalType":"address","name":"_circleTokenMessenger","type":"address"},{"internalType":"address","name":"_USDC","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"NotAnEvmAddress","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":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"USDC","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_wormholeRelayer","type":"address"},{"internalType":"address","name":"_wormhole","type":"address"}],"name":"__Base_init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_wormholeRelayer","type":"address"},{"internalType":"address","name":"_tokenBridge","type":"address"},{"internalType":"address","name":"_wormhole","type":"address"},{"internalType":"address","name":"_circleMessageTransmitter","type":"address"},{"internalType":"address","name":"_circleTokenMessenger","type":"address"},{"internalType":"address","name":"_USDC","type":"address"}],"name":"__CCTPBase_init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_wormholeRelayer","type":"address"},{"internalType":"address","name":"_tokenBridge","type":"address"},{"internalType":"address","name":"_wormhole","type":"address"}],"name":"__TokenBase_init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetAmount","type":"uint256"},{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"}],"name":"borrow","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assetAmount","type":"uint256"},{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"},{"internalType":"bool","name":"unwrap","type":"bool"}],"name":"borrowNative","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"chainId","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"circleMessageTransmitter","outputs":[{"internalType":"contract IMessageTransmitter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"circleTokenMessenger","outputs":[{"internalType":"contract ITokenMessenger","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"consistencyLevel","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"defaultGasLimitRoundtrip","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetAmount","type":"uint256"},{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"}],"name":"depositCollateral","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"}],"name":"depositCollateralNative","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"},{"internalType":"bool","name":"withTokenTransfer","type":"bool"}],"name":"getDeliveryCostRoundtrip","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"hubChainId","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"hubContractAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"},{"internalType":"bytes[]","name":"additionalVaas","type":"bytes[]"},{"internalType":"bytes32","name":"sourceAddress","type":"bytes32"},{"internalType":"uint16","name":"sourceChain","type":"uint16"},{"internalType":"bytes32","name":"deliveryHash","type":"bytes32"}],"name":"receiveWormholeMessages","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetAmount","type":"uint256"},{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"}],"name":"repay","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"}],"name":"repayNative","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"seenDeliveryVaaHashes","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"setDefaultGasLimitRoundtrip","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"value","type":"bool"}],"name":"setIsUsingCCTP","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"sourceChain","type":"uint16"},{"internalType":"bytes32","name":"sourceAddress","type":"bytes32"}],"name":"setRegisteredSender","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"tokenBridge","outputs":[{"internalType":"contract ITokenBridge","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetAmount","type":"uint256"},{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"}],"name":"withdrawCollateral","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assetAmount","type":"uint256"},{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"},{"internalType":"bool","name":"unwrap","type":"bool"}],"name":"withdrawCollateralNative","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"wormhole","outputs":[{"internalType":"contract IWormhole","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"wormholeRelayer","outputs":[{"internalType":"contract IWormholeRelayer","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]

6080346200023257601f620052e438819003918201601f19168301916001600160401b0383118484101762000237578084926101209460405283398101031262000232576200004e816200024d565b6200005c602083016200025d565b6200006a604084016200025d565b9162000079606085016200025d565b9162000088608086016200024d565b906200009760a087016200025d565b93620000a660c088016200025d565b91620000c4610100620000bc60e08b016200025d565b99016200025d565b9233156200021957600198603e54908a8060a01b03199a8b953387851617603e55818060a01b039b8c978880988197823391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a381603a54956200014660ff881615620001358162000272565b620001408162000272565b62000272565b16836035541617603555169060365416176036558d339060385416176038556101008360a81b039060081b1690828060a81b0319161717603a551689603b541617603b5516918288603c541617603c551686603d541617603d556000549261ffff938465010000000000600160c81b038760281b1692169062ff0001600160c81b0319161764ffff0000008460181b1617176000556209eb1060015560ff8019600254169115151617600255166000526039602052166040600020556038541660385560405161503e9081620002a68239f35b604051631e4fbdf760e01b815260006004820152602490fd5b600080fd5b634e487b7160e01b600052604160045260246000fd5b519061ffff821682036200023257565b51906001600160a01b03821682036200023257565b156200027a57565b60405162461bcd60e51b815260206004820152600360248201526257524960e81b6044820152606490fdfe60806040526004361015610018575b361561001657005b005b6000803560e01c80630c59272014613ffc57806314f29f8814613fd1578063180f6cc214613fa25780631a74856b146134ef5780632fd8f7e6146131235780633757e53d146130ee578063378717b7146130bf5780633d3a066f14612e23578063529dca3214611da9578063715018a614611d405780637816976814611d2257806384acd1bb14611cfb57806389a3027114611cd45780638c1577c914611cad5780638cd2e0c71461119d5780638da5cb5b14611176578063929f5840146111525780639a8a059214611131578063a489e28a14611088578063a7b4629e14610e68578063a8cbeb2914610a97578063c1bce0b714610777578063c6328a461461074d578063d8c71a9214610724578063da25b725146106fd578063dd12d68b1461064a578063decfba6114610614578063dfe6f20014610273578063e1cc30ee1461024c578063e8dfd508146102295763f2fde38b14610179575061000e565b346102265760206003193601126102265761019261401f565b61019a6141ee565b6001600160a01b038091169081156101f557603e548273ffffffffffffffffffffffffffffffffffffffff19821617603e55167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a380f35b602483604051907f1e4fbdf70000000000000000000000000000000000000000000000000000000082526004820152fd5b80fd5b503461022657806003193601126102265760ff6020915460101c16604051908152f35b503461022657806003193601126102265760206001600160a01b03603c5416604051908152f35b5061027d366141cb565b9291603a5490604051936315ab88c960e31b855260209485816004816001600160a01b03809860081c165afa918215610608579084926105db575b50169080156105975782603d541682148061058b575b82151580610583575b610443575b808697819796979261043a575b81610431575b6040519460018887015233604087015260608601521515608085015281151560a0850152151560c084015260c08352610327836140f5565b156103fd57505b61037d8260355416916000549560015494604051978896879586956312d729bd60e21b875261ffff94858560181c1660048901528460281c16602488015260e0604488015260e487019061474c565b93606486015260848501521660a48301523360c4830152039134905af19081156103f1576000916103bc575b5067ffffffffffffffff60405191168152f35b908282813d83116103ea575b6103d2818361412d565b8101031261022657506103e490614771565b386103a9565b503d6103c8565b6040513d6000823e3d90fd5b61042c9061041e60405193849287840152604080840152606083019061474c565b03601f19810183528261412d565b61032e565b600191506102ef565b600092506102e9565b60405163313ce56760e01b81528681600481875afa80156103f157839160009161054c575b5060ff8116906008821190838215610528576104959061048f61048a84614da6565b614de8565b90614e0c565b80926000146104ff57506104b6925061048a6104b091614da6565b90614df9565b146102dc576064866040519062461bcd60e51b82526004820152601760248201527f546f6f206d616e7920646563696d616c20706c616365730000000000000000006044820152fd5b9192600811610510575b50506104b6565b610521925061048a61048f91614dd7565b3880610509565b6008841015610495575061054761054161048a83614dd7565b85614df9565b610495565b91508782813d831161057c575b610563818361412d565b8101031261022657506105768391614d98565b38610468565b503d610559565b5080156102d7565b5060ff600254166102ce565b6064856040519062461bcd60e51b82526004820152601460248201527f4e6f207a65726f20617373657420616d6f756e740000000000000000000000006044820152fd5b6105fb9150863d8811610601575b6105f3818361412d565b81019061441a565b386102b8565b503d6105e9565b604051903d90823e3d90fd5b50346102265760606003193601126102265761064761063161401f565b610639614035565b61064161404b565b916142d9565b80f35b50346102265760406003193601126102265760043561ffff81168091036106f8576001600160a01b0360385416330361068f5781526039602052602435604082205580f35b608460405162461bcd60e51b8152602060048201526024808201527f4e6f7420616c6c6f77656420746f20736574207265676973746572656420736560448201527f6e646572000000000000000000000000000000000000000000000000000000006064820152fd5b600080fd5b503461022657806003193601126102265760206001600160a01b0360355416604051908152f35b50346102265780600319360112610226576001600160a01b036020915460281c16604051908152f35b503461022657806003193601126102265760206001600160a01b03603a5460081c16604051908152f35b61078036614061565b909161078d8215156143ac565b8215610a53576001600160a01b038080603d541692169182149384610a46575b849083151580610a3e575b610907575b816108fe575b856108f5575b60405191602096879560018786015233604086015260608501526000608085015281151560a0850152151560c084015260c08352610806836140f5565b156108cf57505b61085c8260355416916000549560015494604051978896879586956312d729bd60e21b875261ffff94858560181c1660048901528460281c16602488015260e0604488015260e487019061474c565b93606486015260848501521660a48301523360c4830152039134905af19081156103f15760009161089a575067ffffffffffffffff60405191168152f35b908282813d83116108c8575b6108b0818361412d565b8101031261022657506108c290614771565b826103a9565b503d6108a6565b6108f09061041e60405193849287840152604080840152606083019061474c565b61080d565b600195506107c9565b600091506107c3565b905060405163313ce56760e01b8152602081600481875afa80156103f1578291600091610a03575b5060ff81169060088211908382156109e5576109519061048f61048a84614da6565b80926000146109bc575061096c925061048a6104b091614da6565b036109785784906107bd565b606460405162461bcd60e51b815260206004820152601760248201527f546f6f206d616e7920646563696d616c20706c616365730000000000000000006044820152fd5b91926008116109cd575b505061096c565b6109de925061048a61048f91614dd7565b87806109c6565b600884101561095157506109fe61054161048a83614dd7565b610951565b91506020823d8211610a36575b81610a1d6020938361412d565b810103126102265750610a308291614d98565b8761092f565b3d9150610a10565b5081156107b8565b60025460ff1694506107ad565b606460405162461bcd60e51b815260206004820152601460248201527f4e6f207a65726f20617373657420616d6f756e740000000000000000000000006044820152fd5b50602090816003193601126102265750600435610ab38161445c565b803410610dfe57610ac490346143f7565b8015610dba576001600160a01b039081603d54161580610dae575b8080610da6575b81610d9d575b60405191600387840152336040840152600060608401526000608084015281151560a0840152151560c083015260c08252610b26826140f5565b15610d76575b82603a5460081c16916040516384acd1bb60e01b81528681600481875afa9081156103f15785918891600091610d59575b50600460405180948193631a90a21960e01b8352165afa9081156103f157600091610d2a575b50610b8d9161444f565b928560005494604051948580927fbee9cdfc0000000000000000000000000000000000000000000000000000000082528561ffff998a8160181c16600485015260281c166024830152600060448301526080606483015281610bf2608482018961474c565b03925af19384156103f1578693600095610ced575b50610c10614968565b9160005491610c998184169280603a5460081c1660405190610c31826140bd565b8582528982015267ffffffffffffffff809a166040820152610c52876149a4565b52610c5c866149a4565b50610c668a6145b3565b938160355416966001546040519c8d9a8b998a986319597e0760e11b8a5233978260281c169160181c1660048a016149db565b03925af19182156103f157600092610cb6575b5060405191168152f35b90918382813d8311610ce6575b610ccd818361412d565b810103126102265750610cdf90614771565b9038610cac565b503d610cc3565b91929382819692963d8311610d23575b610d07818361412d565b81010312610226575090610d1c869392614771565b9338610c07565b503d610cfd565b908782813d8311610d52575b610d40818361412d565b81010312610226575051610b8d610b83565b503d610d36565b610d709150823d8411610601576105f3818361412d565b38610b5d565b610d9861041e916040519283918589840152604080840152606083019061474c565b610b2c565b60019150610aec565b506000610ae6565b5060ff60025416610adf565b6064836040519062461bcd60e51b82526004820152601460248201527f4e6f207a65726f20617373657420616d6f756e740000000000000000000000006044820152fd5b6084836040519062461bcd60e51b82526004820152602a60248201527f53706f6b653a3a72657061794e61746976653a496e73756666696369656e742060448201527f76616c75652073656e74000000000000000000000000000000000000000000006064820152fd5b50610e72366141cb565b9291603a5490604051936315ab88c960e31b855260209485816004816001600160a01b03809860081c165afa9182156106085790849261106b575b50169080156105975782603d541682148061105f575b82151580611057575b610f2e575b8086978197969792610f25575b81610f1c575b6040519460028887015233604087015260608601521515608085015281151560a0850152151560c084015260c08352610327836140f5565b60019150610ee4565b60009250610ede565b60405163313ce56760e01b81528681600481875afa80156103f1578391600091611020575b5060ff811690600882119083821561100257610f759061048f61048a84614da6565b8092600014610fd95750610f90925061048a6104b091614da6565b14610ed1576064866040519062461bcd60e51b82526004820152601760248201527f546f6f206d616e7920646563696d616c20706c616365730000000000000000006044820152fd5b9192600811610fea575b5050610f90565b610ffb925061048a61048f91614dd7565b3880610fe3565b6008841015610f75575061101b61054161048a83614dd7565b610f75565b91508782813d8311611050575b611037818361412d565b81010312610226575061104a8391614d98565b38610f53565b503d61102d565b508015610ecc565b5060ff60025416610ec3565b6110829150863d8811610601576105f3818361412d565b38610ead565b50346102265760c0600319360112610226576110a261401f565b6110aa614035565b6110b261404b565b91606435906001600160a01b03938483168093036106f857608435938585168095036106f85760a4359586168096036106f8576110fd926110f860ff603a541615614232565b6142d9565b73ffffffffffffffffffffffffffffffffffffffff199182603b541617603b5581603c541617603c55603d541617603d5580f35b503461022657806003193601126102265761ffff6020915416604051908152f35b503461022657806003193601126102265761ffff6020915460181c16604051908152f35b503461022657806003193601126102265760206001600160a01b03603e5416604051908152f35b6111a636614061565b906111bb6111b38361445c565b341015614361565b8015610a53576001600160a01b0380603d541693169283149283611ca0575b80151580611c98575b611b6a575b838080611b62575b81611b59575b60405191600360208401523360408401528360608401526000608084015281151560a0840152151560c083015260c08252611230826140f5565b15611b3057935b6000901561176f57505061125a816001600160a01b03603d541630903390614786565b60005491600154906040519161126f836140d9565b6001835260005b602081106117585750611287614bd9565b506112aa846001600160a01b03603d54166001600160a01b03603b541690614a9e565b6001600160a01b03603b5416602063ffffffff60a46112cf61ffff8a60181c16614bf3565b60006001600160a01b03603d54169560405196879586947ff856ddb60000000000000000000000000000000000000000000000000000000086528d60048701521660248501526001600160a01b038d60281c16604485015260648401526001600160a01b038c60281c1660848401525af19081156103f15760009161171e575b50600460206001600160a01b036036541660405192838092634d4502c960e11b82525afa9081156103f1576000916116bf575b506113ad7fffffffffffffffff00000000000000000000000000000000000000000000000091614bf3565b917fffffffff000000000000000000000000000000000000000000000000000000006040519360e01b16602084015260c01b166024820152600c81526113f2816140d9565b604051906113ff826140d9565b600282526020820152611411846149a4565b5261141b836149a4565b506001600160a01b036035541694604051917f24320c9f0000000000000000000000000000000000000000000000000000000083526020836004818a5afa9283156103f15760009361169e575b506040805163c23ee3c360e01b815261ffff601885901c16600482015260248101869052604481018390529690876064818b5afa9687156103f157600097611655575b506001600160a01b03926115439997969594926114f161ffff936114e36040519b8c926020840152604080840152606083019061474c565b03601f1981018b528a61412d565b6040519a8b997fc055120e000000000000000000000000000000000000000000000000000000008b52848460181c1660048c0152868460281c1660248c015261016060448c01526101648b019061474c565b9560648a0152600060848a015260a48901521660c48701523360e48701521661010485015260031984820301610124850152815180825260208201916020808360051b8301019401926000915b83831061160e57505050505060209391838092600f61014483015203925af180156103f1576000906115d4575b602091505b67ffffffffffffffff60405191168152f35b6020823d602011611606575b816115ed6020938361412d565b810103126102265750611601602091614771565b6115bd565b3d91506115e0565b9193959650919360208061164283601f1986600196030187526040838b5160ff81511684520151918185820152019061474c565b9701930193019092889695949293611590565b6001600160a01b039493919750916114f161168a61ffff9460403d604011611697575b611682818361412d565b810190614439565b50989294955050916114ab565b503d611678565b6116b891935060203d602011610601576105f3818361412d565b9188611468565b906020823d602011611716575b816116d96020938361412d565b8101031261022657506113ad61170f7fffffffffffffffff00000000000000000000000000000000000000000000000092614a8f565b9150611382565b3d91506116cc565b906020823d602011611750575b816117386020938361412d565b81010312610226575061174a90614771565b8761134f565b3d915061172b565b602090611763614bd9565b82828701015201611276565b9190929361177f82303387614786565b8254936001549061178e614968565b93611797614949565b506004604051926117a784614111565b8784526117b2614949565b506117cc836001600160a01b03603a5460081c1683614a9e565b6001600160a01b03603a5460081c169360206001600160a01b036036541660405194858092631a90a21960e01b82525afa928315611b2557908a949392918a93611ae4575b509160209493916118896001600160a01b039794604051988997889687957fc5a5ebda0000000000000000000000000000000000000000000000000000000087526004870152602486015261ffff8160181c16604486015260281c1660648401528c608484015260c060a484015260c483019061474c565b03925af18015611ad9578590611a9b575b600491506001600160a01b03603a5460081c1660206001600160a01b036036541660405194858092634d4502c960e11b82525afa928315611a90578793611a45575b509067ffffffffffffffff9161ffff604051946118f8866140bd565b168452602084015216604082015261190f846149a4565b52611919836149a4565b506001600160a01b0360355416916040519663c23ee3c360e01b885260408880611964868561ffff8d60181c166004850160409194939261ffff606083019616825260208201520152565b0381875afa918215611a3a5786979860209793611a14575b506119bb90604051998a97889687956319597e0760e11b8752339461ffff85169461ffff6001600160a01b038260281c169160181c1660048a016149db565b03925af19081156106085780916119d7575b50602091506115c2565b90506020823d602011611a0c575b816119f26020938361412d565b810103126102265750611a06602091614771565b826119cd565b3d91506119e5565b6119bb919350611a329060403d60401161169757611682818361412d565b50929061197c565b6040513d88823e3d90fd5b9092506020813d602011611a88575b81611a616020938361412d565b81010312611a845790611a7c67ffffffffffffffff92614a8f565b9290916118dc565b8680fd5b3d9150611a54565b6040513d89823e3d90fd5b506020813d602011611ad1575b81611ab56020938361412d565b81010312611acd57611ac8600491614771565b61189a565b8480fd5b3d9150611aa8565b6040513d87823e3d90fd5b9192509293506020813d602011611b1d575b81611b036020938361412d565b81010312611b1957518993929091906020611811565b8880fd5b3d9150611af6565b6040513d8b823e3d90fd5b611b5361041e91604051928391866020840152604080840152606083019061474c565b93611237565b600091506111f6565b5060016111f0565b60405163313ce56760e01b8152602081600481855afa80156103f1578391600091611c5d575b5060ff8116906008821190838215611c3f57611bb29061048f61048a84614da6565b8092600014611c165750611bcd925061048a6104b091614da6565b146111e857606460405162461bcd60e51b815260206004820152601760248201527f546f6f206d616e7920646563696d616c20706c616365730000000000000000006044820152fd5b9192600811611c27575b5050611bcd565b611c38925061048a61048f91614dd7565b8680611c20565b6008841015611bb25750611c5861054161048a83614dd7565b611bb2565b91506020823d8211611c90575b81611c776020938361412d565b810103126102265750611c8a8391614d98565b86611b90565b3d9150611c6a565b5083156111e3565b60025460ff1693506111da565b503461022657806003193601126102265760206001600160a01b03603b5416604051908152f35b503461022657806003193601126102265760206001600160a01b03603d5416604051908152f35b503461022657806003193601126102265760206001600160a01b0360365416604051908152f35b50346102265780600319360112610226576020600154604051908152f35b5034610226578060031936011261022657611d596141ee565b60006001600160a01b03603e5473ffffffffffffffffffffffffffffffffffffffff198116603e55167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b5060031960a081360112612e1f5760043567ffffffffffffffff8111612d2657611dd790369060040161416c565b60243567ffffffffffffffff8111612e1b5736602382011215612e1b57806004013590611e03826141b3565b91611e11604051938461412d565b80835260051b810160240160208301368211611a845760248301905b828210612deb57505050506064359261ffff84168403611acd578151611e7f575050611e599150614d62565b50508134611e6657505080f35b8080806106479434905af1611e79614886565b50614c71565b611e8a839293614d62565b91505060001461221f5760018351116121b55784926001815114611fa1575b5050611ebf816020808594518301019101614cfe565b9203611f5d5761ffff92611f44611f52936001600160a01b03958693611eea85603554163314614eac565b1687526039602052611f03604435604089205414614f1d565b6084358088526037602052611f1f60ff60408a20541615614f68565b8752603760205260408720600160ff1982541617905560208082518301019101614d2c565b5050168093603d5416614fb3565b8134611e6657505080f35b606460405162461bcd60e51b815260206004820152601560248201527f57726f6e6720616d6f756e7420726563656976656400000000000000000000006044820152fd5b611fac9193506149a4565b51918251830192604081602086019503126121b157602081015167ffffffffffffffff8111611a8457846020611fe492840101614cbc565b9360408201519167ffffffffffffffff83116121ad576120079201602001614cbc565b906001600160a01b0380603d541692604051956020876024817f70a0823100000000000000000000000000000000000000000000000000000000988982523060048301525afa968715611b25578997612176575b50906020916120ab8a85603c5416926120ba604051988996879586947f57ecfd2800000000000000000000000000000000000000000000000000000000865260406004870152604486019061474c565b9184830301602485015261474c565b03925af18015611a905761213c575b60209150603d54169160246040518094819382523060048301525afa908115611ad9578591612108575b50611ebf92612101916143f7565b9138611ea9565b90506020813d602011612134575b816121236020938361412d565b810103126106f85751611ebf6120f3565b3d9150612116565b6020823d60201161216e575b816121556020938361412d565b81010312611a84576121686020926147e5565b506120c9565b3d9150612148565b919096506020823d6020116121a5575b816121936020938361412d565b810103126106f857905195602061205b565b3d9150612186565b8780fd5b8580fd5b608460405162461bcd60e51b815260206004820152602660248201527f434354503a204174206d6f7374206f6e65204d6573736167652069732073757060448201527f706f7274656400000000000000000000000000000000000000000000000000006064820152fd5b50929190805191601f1961224b612235856141b3565b94612243604051968761412d565b8086526141b3565b01845b818110612db6575050835b8251811015612b6e576122b86001600160a01b03603654168661227c84876149c7565b51604051809481927fa9e1189300000000000000000000000000000000000000000000000000000000835260206004840152602483019061474c565b0381845afa918215611a90578792612929575b50608082015190603a549161ffff60608501511690604051917fad66a5f100000000000000000000000000000000000000000000000000000000835260048301526020826024816001600160a01b038860081c165afa91821561291e578a926128ea575b50036128a6578760e0612379940151604051809581927fea63738d00000000000000000000000000000000000000000000000000000000835260206004840152602483019061474c565b03816001600160a01b038660081c165afa92831561289b578893612787575b5030608084015114908161270f575b50156126a557866124036001600160a01b03926123c486896149c7565b51836040519586809581947fc3f511c100000000000000000000000000000000000000000000000000000000835260206004840152602483019061474c565b039360081c165af18015611a9057612663575b5061ffff606082015116604082015190600460206001600160a01b036036541660405192838092634d4502c960e11b82525afa8015611b25578990612628575b61ffff1682141590506125a857508060a01c612577576001600160a01b03165b8680604051602081019063313ce56760e01b825260048152612497816140d9565b5190845afa506124a5614886565b6020818051810103126121ad5760206124be9101614d98565b602083019182519160ff9060088282161161254f575b50506001600160a01b039061ffff6060604087015196015116935193604051956124fd8761408b565b865260208601521660408401526060830152608082015261251e82866149c7565b5261252981856149c7565b50600019811461253b57600101612259565b602485634e487b7160e01b81526011600452fd5b926104b061256f926125696001600160a01b039596614da6565b16614e3d565b9190386124d4565b602490604051907f33b960d00000000000000000000000000000000000000000000000000000000082526004820152fd5b9060209060446001600160a01b03603a5460081c169360405194859384927f1ff1e286000000000000000000000000000000000000000000000000000000008452600484015260248301525afa908115611a90578791612609575b50612476565b612622915060203d602011610601576105f3818361412d565b38612603565b506020813d60201161265b575b816126426020938361412d565b81010312611b195761265661ffff91614a8f565b612456565b3d9150612635565b3d8088833e612672818361412d565b60208282810103126121ad5781519067ffffffffffffffff8211611b195761269e929081019101614cbc565b5038612416565b608460405162461bcd60e51b815260206004820152602260248201527f546f6b656e20776173206e6f742073656e7420746f207468697320616464726560448201527f73730000000000000000000000000000000000000000000000000000000000006064820152fd5b90506004602061ffff60a0860151169260405192838092634d4502c960e11b82525afa8015611b2557899061274c575b61ffff91501614386123a7565b506020813d60201161277f575b816127666020938361412d565b81010312611b195761277a61ffff91614a8f565b61273f565b3d9150612759565b9092503d908189823e61279a828261412d565b6020818381010312611b195780519167ffffffffffffffff831161289757610100838301828401031261289757604051928361010081011067ffffffffffffffff610100860111176128835761010084016040526127f9818401614d98565b8452828101602081810151908601526040808201519086015261281e90606001614a8f565b606085015260808184010151608085015261283d60a082850101614a8f565b60a085015282810160c0818101519086015260e001519167ffffffffffffffff831161287f576128739390810192910101614cbc565b60e08201529138612398565b8b80fd5b60248b634e487b7160e01b81526041600452fd5b8980fd5b6040513d8a823e3d90fd5b606460405162461bcd60e51b815260206004820152601660248201527f4e6f74206120546f6b656e2042726964676520564141000000000000000000006044820152fd5b9091506020813d602011612916575b816129066020938361412d565b810103126128975751903861232f565b3d91506128f9565b6040513d8c823e3d90fd5b9091503d908188823e61293c828261412d565b60208183810103126121ad5780519067ffffffffffffffff8211611b1957610160928383830182840103126128975760405193848181011067ffffffffffffffff8287011117612883578401604052612996838301614d98565b84526129a6602084840101614e2c565b60208501526129b9604084840101614e2c565b60408501526129cc606084840101614a8f565b60608501526080838301015160808501526129eb60a084840101614771565b60a085015260c06129ff8185850101614d98565b9085015260e0838301015167ffffffffffffffff8111612b6a57612a2a908284019085850101614cbc565b60e0850152610100612a3f8185850101614e2c565b90850152610120838301015167ffffffffffffffff8111612b6a57818301601f8286860101011215612b6a57808484010151612a7a816141b3565b92612a88604051948561412d565b818452602084019281860160208460071b838a8a0101010111612b66576020818888010101935b60208460071b838a8a010101018510612ae057505050505061012084015201610140908101519082015290386122cb565b6080858489010312612b625760405180608081011067ffffffffffffffff608083011117612b4d57608091818360209301604052875181528288015183820152612b2c60408901614d98565b6040820152612b3d60608901614d98565b6060820152815201940193612aaf565b5060248f634e487b7160e01b81526041600452fd5b8e80fd5b8d80fd5b8a80fd5b50929391905061ffff6001600160a01b0393612b8f85603554163314614eac565b1684526039602052612ba8604435604086205414614f1d565b6084358085526037602052612bc460ff60408720541615614f68565b8452603760205260408420600160ff198254161790556001815103612d7257612bf7612bf085926149a4565b5192614d62565b5090809491600014612d545750806020600492603a5460081c16604051938480926315ab88c960e31b82525afa8015612d49576060928491612d2a575b5016920191825190803b15612d26576024839260405194859384927f2e1a7d4d00000000000000000000000000000000000000000000000000000000845260048401525af18015612d1b57612ceb575b50828080809351855af1612c96614886565b5015612ca7578134611e6657505080f35b606460405162461bcd60e51b815260206004820152601960248201527f776974686472617720746f206e6174697665206661696c6564000000000000006044820152fd5b67ffffffffffffffff8194929411612d07576040529138612c84565b602482634e487b7160e01b81526041600452fd5b6040513d86823e3d90fd5b8280fd5b612d43915060203d602011610601576105f3818361412d565b38612c34565b6040513d85823e3d90fd5b908392506060906040612d6d9501511692015191614fb3565b611f52565b606460405162461bcd60e51b815260206004820152601660248201527f457870656374696e67206f6e65207472616e73666572000000000000000000006044820152fd5b602090604051612dc58161408b565b87815287838201528760408201528760608201528760808201528282880101520161224e565b813567ffffffffffffffff8111611b1957602091612e1083926024369189010161416c565b815201910190611e2d565b8380fd5b5080fd5b50612e2d36614061565b909291612e3b8215156143ac565b8315610a53576001600160a01b038080603d5416921691821494856130b2575b8590831515806130aa575b612fb6575b81612fae575b86612fa5575b604051916020978895600287860152336040860152606085015287608085015281151560a0850152151560c084015260c08352612eb3836140f5565b15612f7f57505b612f0882603554169186549560015494604051978896879586956312d729bd60e21b875261ffff94858560181c1660048901528460281c16602488015260e0604488015260e487019061474c565b93606486015260848501521660a48301523360c4830152039134905af1918215610608578092612f47575b505067ffffffffffffffff60405191168152f35b9091508282813d8311612f78575b612f5f818361412d565b810103126102265750612f7190614771565b3880612f33565b503d612f55565b612fa09061041e60405193849287840152604080840152606083019061474c565b612eba565b60019650612e77565b859150612e71565b905060405163313ce56760e01b8152602081600481875afa908115611a3a57908291879161306f575b5060ff8116906008821190838215613051576130019061048f61048a84614da6565b8092600014613028575061301c925061048a6104b091614da6565b03610978578590612e6b565b9192600811613039575b505061301c565b61304a925061048a61048f91614dd7565b3880613032565b6008841015613001575061306a61054161048a83614dd7565b613001565b9150506020813d82116130a2575b8161308a6020938361412d565b810103126121b15761309c8291614d98565b38612fdf565b3d915061307d565b508115612e66565b60025460ff169550612e5b565b5034610226576020600319360112610226576004358015158103612e1f57610647906130e96141ee565b61433a565b50346102265760406003193601126102265760243590811515820361022657602061311b8360043561462b565b604051908152f35b50602090816003193601126102265760043561313e8161445c565b8034106134855761314f90346143f7565b908115613441576001600160a01b0380603d54161580613435575b808061342e575b81613425575b60405191868884015233604084015286606084015286608084015281151560a0840152151560c083015260c082526131ae826140f5565b156133fd57905b80603a5460081c16936040516384acd1bb60e01b81528781600481895afa908115611a90578391899189916133e0575b50600460405180948193631a90a21960e01b8352165afa908115611a905787916133b1575b506132149161444f565b86865491604051968780927fbee9cdfc0000000000000000000000000000000000000000000000000000000082528661ffff96878160181c16600485015260281c1660248301528a60448301526080606483015281613276608482018a61474c565b03925af1948515611a3a5790879392918796613375575b50613296614968565b9187549161331e8184169280603a5460081c16604051906132b6826140bd565b8582528982015267ffffffffffffffff809b1660408201526132d7876149a4565b526132e1866149a4565b506132eb896145b3565b938160355416966001546040519b8c9a8b998a986319597e0760e11b8a5233978260281c169160181c1660048a016149db565b03925af192831561060857809361333b575b505060405191168152f35b909192508382813d831161336e575b613354818361412d565b81010312610226575061336690614771565b903880613330565b503d61334a565b84819394959297503d83116133aa575b61338f818361412d565b810103126121b157906133a3879392614771565b943861328d565b503d613385565b90508781813d83116133d9575b6133c8818361412d565b81010312611a84575161321461320a565b503d6133be565b6133f79150823d8411610601576105f3818361412d565b386131e5565b61341f61041e91604051928391878a840152604080840152606083019061474c565b906131b5565b60019150613177565b5084613171565b5060ff6002541661316a565b6064846040519062461bcd60e51b82526004820152601460248201527f4e6f207a65726f20617373657420616d6f756e740000000000000000000000006044820152fd5b6084846040519062461bcd60e51b82526004820152603660248201527f53706f6b653a3a6465706f736974436f6c6c61746572616c4e61746976653a4960448201527f6e73756666696369656e742076616c75652073656e74000000000000000000006064820152fd5b506134f936614061565b906135096111b38395949561445c565b8015610a53576001600160a01b0380603d541694169384149384613f95575b80151580613f8d575b613e5e575b848080613e56575b81613e4e575b6040519186602084015233604084015283606084015286608084015281151560a0840152151560c083015260c0825261357c826140f5565b15613e2557945b15613a9e57506135a2816001600160a01b03603d541630903390614786565b82549160015490604051916135b6836140d9565b60018352855b60208110613a8757506135cd614bd9565b506135f0846001600160a01b03603d54166001600160a01b03603b541690614a9e565b6001600160a01b03603b5416602063ffffffff60a461361561ffff8a60181c16614bf3565b8a6001600160a01b03603d54169560405196879586947ff856ddb60000000000000000000000000000000000000000000000000000000086528d60048701521660248501526001600160a01b038d60281c16604485015260648401526001600160a01b038c60281c1660848401525af1908115611a90578791613a4d575b50600460206001600160a01b036036541660405192838092634d4502c960e11b82525afa90811561289b5788916139ee575b506136f07fffffffffffffffff00000000000000000000000000000000000000000000000091614bf3565b917fffffffff000000000000000000000000000000000000000000000000000000006040519360e01b16602084015260c01b166024820152600c8152613735816140d9565b60405190613742826140d9565b600282526020820152613754846149a4565b5261375e836149a4565b506001600160a01b036035541694604051917f24320c9f0000000000000000000000000000000000000000000000000000000083526020836004818a5afa92831561289b5788936139cd575b506040805163c23ee3c360e01b815261ffff601885901c166004820152602481018690526044810183905299908a6064818b5afa968715611b255760449a8a9861398e575b5061387b9a509261ffff9197969594926138296001600160a01b03956114e36040519b8c926020840152604080840152606083019061474c565b6040519b8c997fc055120e000000000000000000000000000000000000000000000000000000008b52848460181c1660048c0152868460281c1660248c015261016060448c01526101648b019061474c565b9560648a01528a60848a015260a48901521660c48701523360e48701521661010485015260031984820301610124850152815180825260208201916020808360051b83010194019288915b83831061394757505050505060209391838092600f61014483015203925af190811561060857809161390a575b506020915067ffffffffffffffff60405191168152f35b90506020823d60201161393f575b816139256020938361412d565b810103126102265750613939602091614771565b386138f3565b3d9150613918565b9193959650919360208061397b83601f1986600196030187526040838b5160ff81511684520151918185820152019061474c565b97019301930190928996959492936138c6565b61ffff939198506001600160a01b039594926138296139be61387b9360403d60401161169757611682818361412d565b509a93955050929495506137ef565b6139e791935060203d602011610601576105f3818361412d565b91386137aa565b90506020813d602011613a45575b81613a096020938361412d565b810103126121ad576136f0613a3e7fffffffffffffffff00000000000000000000000000000000000000000000000092614a8f565b91506136c5565b3d91506139fc565b90506020813d602011613a7f575b81613a686020938361412d565b81010312611a8457613a7990614771565b38613693565b3d9150613a5b565b602090613a92614bd9565b828287010152016135bc565b929193613aad82303387614786565b82549360015490613abc614968565b93613ac5614949565b50600460405192613ad584614111565b878452613ae0614949565b50613afa836001600160a01b03603a5460081c1683614a9e565b6001600160a01b03603a5460081c169360206001600160a01b036036541660405194858092631a90a21960e01b82525afa928315611b2557908a949392918a93613de8575b50916020949391613bb76001600160a01b039794604051988997889687957fc5a5ebda0000000000000000000000000000000000000000000000000000000087526004870152602486015261ffff8160181c16604486015260281c1660648401528c608484015260c060a484015260c483019061474c565b03925af18015611ad9578590613dae575b600491506001600160a01b03603a5460081c1660206001600160a01b036036541660405194858092634d4502c960e11b82525afa928315611a90578793613d67575b509067ffffffffffffffff9161ffff60405194613c26866140bd565b1684526020840152166040820152613c3d846149a4565b52613c47836149a4565b506001600160a01b0360355416916040519663c23ee3c360e01b885260408880613c92868561ffff8d60181c166004850160409194939261ffff606083019616825260208201520152565b0381875afa918215611a3a5786979860209793613d41575b50613ce990604051998a97889687956319597e0760e11b8752339461ffff85169461ffff6001600160a01b038260281c169160181c1660048a016149db565b03925af1908115610608578091613d045750602091506115c2565b90506020823d602011613d39575b81613d1f6020938361412d565b810103126102265750613d33602091614771565b386119cd565b3d9150613d12565b613ce9919350613d5f9060403d60401161169757611682818361412d565b509290613caa565b9092506020813d602011613da6575b81613d836020938361412d565b81010312611a845790613d9e67ffffffffffffffff92614a8f565b929091613c0a565b3d9150613d76565b506020813d602011613de0575b81613dc86020938361412d565b81010312611acd57613ddb600491614771565b613bc8565b3d9150613dbb565b9192509293506020813d602011613e1d575b81613e076020938361412d565b81010312611b1957518993929091906020613b3f565b3d9150613dfa565b613e4861041e91604051928391866020840152604080840152606083019061474c565b94613583565b859150613544565b50600161353e565b60405163313ce56760e01b8152602081600481855afa908115611ad9579083918691613f52575b5060ff8116906008821190838215613f3457613ea79061048f61048a84614da6565b8092600014613f0b5750613ec2925061048a6104b091614da6565b1461353657606460405162461bcd60e51b815260206004820152601760248201527f546f6f206d616e7920646563696d616c20706c616365730000000000000000006044820152fd5b9192600811613f1c575b5050613ec2565b613f2d925061048a61048f91614dd7565b3880613f15565b6008841015613ea75750613f4d61054161048a83614dd7565b613ea7565b9150506020813d8211613f85575b81613f6d6020938361412d565b81010312611acd57613f7f8391614d98565b38613e85565b3d9150613f60565b508415613531565b60025460ff169450613528565b50346102265760206003193601126102265760ff60406020926004358152603784522054166040519015158152f35b503461022657604060031936011261022657610647613fee61401f565b613ff6614035565b9061427d565b5034610226576020600319360112610226576140166141ee565b60043560015580f35b600435906001600160a01b03821682036106f857565b602435906001600160a01b03821682036106f857565b604435906001600160a01b03821682036106f857565b60031960609101126106f8576004356001600160a01b03811681036106f857906024359060443590565b60a0810190811067ffffffffffffffff8211176140a757604052565b634e487b7160e01b600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176140a757604052565b6040810190811067ffffffffffffffff8211176140a757604052565b60e0810190811067ffffffffffffffff8211176140a757604052565b6020810190811067ffffffffffffffff8211176140a757604052565b90601f601f19910116810190811067ffffffffffffffff8211176140a757604052565b67ffffffffffffffff81116140a757601f01601f191660200190565b81601f820112156106f85780359061418382614150565b92614191604051948561412d565b828452602083830101116106f857816000926020809301838601378301015290565b67ffffffffffffffff81116140a75760051b60200190565b60031960609101126106f857600435906024359060443580151581036106f85790565b6001600160a01b03603e5416330361420257565b60246040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152fd5b1561423957565b606460405162461bcd60e51b815260206004820152600360248201527f57524900000000000000000000000000000000000000000000000000000000006044820152fd5b90600160ff19603a5461429360ff821615614232565b1617603a556001600160a01b03908173ffffffffffffffffffffffffffffffffffffffff1993168360355416176035551681603654161760365533906038541617603855565b916142f391926142ee60ff603a541615614232565b61427d565b7fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff00603a549260081b16911617603a55565b6001600160a01b03603c54161561435e5760ff60ff19600254169115151617600255565b50565b1561436857565b606460405162461bcd60e51b815260206004820152601760248201527f496e73756666696369656e742076616c75652073656e740000000000000000006044820152fd5b156143b357565b606460405162461bcd60e51b815260206004820152601e60248201527f4e6f6e2d7a65726f20636f7374466f7252657475726e44656c697665727900006044820152fd5b9190820391821161440457565b634e487b7160e01b600052601160045260246000fd5b908160209103126106f857516001600160a01b03811681036106f85790565b91908260409103126106f8576020825192015190565b9190820180921161440457565b603554600080546001546040805163c23ee3c360e01b815260189390931c61ffff16600484015260248301959095526044820152909392916001600160a01b039083908390831681806064810103915afa9182156145a9578592614589575b5080603a5460081c16835180916384acd1bb60e01b825281600460209485935afa90811561457f579082918891614562575b506004865180958193631a90a21960e01b8352165afa93841561455957508593614522575b505061451f92935061444f565b90565b9080929350813d8311614552575b61453a818361412d565b81010312612e1b5761451f9293505190839238614512565b503d614530565b513d87823e3d90fd5b6145799150823d8411610601576105f3818361412d565b386144ed565b85513d89823e3d90fd5b6145a1919250833d851161169757611682818361412d565b5090386144bb565b83513d87823e3d90fd5b6035546000546001546040805163c23ee3c360e01b815260189390931c61ffff16600484015260248301949094526044820152919082906001600160a01b031681806064810103915afa9081156103f15760009161460f575090565b614627915060403d811161169757611682818361412d565b5090565b603554600080546001546040805163c23ee3c360e01b815260189390931c61ffff16600484015260248301959095526044820152939092916001600160a01b03919083908690841681806064810103915afa94851561471c5784956146fc575b506146965750505090565b80603a95949293955460081c16835180916384acd1bb60e01b825281600460209485935afa90811561457f57908291889161456257506004865180958193631a90a21960e01b8352165afa9384156145595750859361452257505061451f92935061444f565b614714919550833d851161169757611682818361412d565b50933861468b565b50505051903d90823e3d90fd5b60005b83811061473c5750506000910152565b818101518382015260200161472c565b90601f19601f60209361476a81518092818752878088019101614729565b0116010190565b519067ffffffffffffffff821682036106f857565b90926147e393604051937f23b872dd0000000000000000000000000000000000000000000000000000000060208601526001600160a01b0380921660248601521660448401526064830152606482526147de8261408b565b6147f2565b565b519081151582036106f857565b6000806001600160a01b0361481c93169360208151910182865af1614815614886565b90836148b6565b8051908115159182614863575b50506148325750565b602490604051907f5274afe70000000000000000000000000000000000000000000000000000000082526004820152fd5b81925090602091810103126106f857602061487e91016147e5565b153880614829565b3d156148b1573d9061489782614150565b916148a5604051938461412d565b82523d6000602084013e565b606090565b906148f557508051156148cb57805190602001fd5b60046040517f1425ea42000000000000000000000000000000000000000000000000000000008152fd5b81511580614940575b614906575090565b6024906001600160a01b03604051917f9996b315000000000000000000000000000000000000000000000000000000008352166004820152fd5b50803b156148fe565b60405190614956826140bd565b60006040838281528260208201520152565b60405190614975826140d9565b600182528160005b6020908181101561499f57602091614993614949565b9082850101520161497d565b505050565b8051156149b15760200190565b634e487b7160e01b600052603260045260246000fd5b80518210156149b15760209160051b010190565b9591949896979390929861ffff80941687526001600160a01b0395866020911681890152614a166101009b8c60409d8e8c01528a019061474c565b91606093848a0152608089015287820360a08901528080855193848152019401926000905b838210614a5b57505050505060e09495969750961660c085015216910152565b845180518816875280840151878501528d015167ffffffffffffffff168d8701529485019493820193600190910190614a3b565b519061ffff821682036106f857565b6040517f095ea7b300000000000000000000000000000000000000000000000000000000602082018181526001600160a01b038516602484015260448084019690965294825294939092614af360648561412d565b6001600160a01b0390818416600080809588519082855af190614b14614886565b82614b9f575b5081614b94575b5015614b31575b50505050509050565b60405196602088015216602486015280604486015260448552608085019085821067ffffffffffffffff831117614b805750614b7593946147de91604052826147f2565b803880808080614b28565b80634e487b7160e01b602492526041600452fd5b90503b151538614b21565b80519192508115918215614bb7575b50509038614b1a565b8192509060209181010312611acd576020614bd291016147e5565b3880614bae565b60405190614be6826140d9565b6060602083600081520152565b61ffff1660028103614c055750600090565b60068103614c135750600190565b60178103614c215750600390565b601803614c2d57600290565b606460405162461bcd60e51b815260206004820152601160248201527f57726f6e67204343545020446f6d61696e0000000000000000000000000000006044820152fd5b15614c7857565b606460405162461bcd60e51b815260206004820152600d60248201527f726566756e64206661696c6564000000000000000000000000000000000000006044820152fd5b81601f820112156106f8578051614cd281614150565b92614ce0604051948561412d565b818452602082840101116106f85761451f9160208085019101614729565b9190916040818403126106f857805192602082015167ffffffffffffffff81116106f85761451f9201614cbc565b908160609103126106f85780516001600160a01b03811681036106f8579161451f6040614d5b602085016147e5565b93016147e5565b614d91614d806001600160a01b039260208082518301019101614cfe565b905060208082518301019101614d2c565b9190921692565b519060ff821682036106f857565b60ff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff89116019060ff821161440457565b60ff166008039060ff821161440457565b60ff16604d811161440457600a0a90565b8181029291811591840414171561440457565b8115614e16570490565b634e487b7160e01b600052601260045260246000fd5b519063ffffffff821682036106f857565b8015614ea657600190602081108216604e8210831617614e9e578190600a925b808211614e7557505081600019048111614404570290565b90928060001904811161440457818416614e95575b800292811c90614e5d565b80920291614e8a565b9050600a0a90565b50600190565b15614eb357565b608460405162461bcd60e51b815260206004820152602260248201527f4d73672e73656e646572206973206e6f7420576f726d686f6c652052656c617960448201527f65720000000000000000000000000000000000000000000000000000000000006064820152fd5b15614f2457565b606460405162461bcd60e51b815260206004820152601560248201527f4e6f7420726567697374657265642073656e64657200000000000000000000006044820152fd5b15614f6f57565b606460405162461bcd60e51b815260206004820152601960248201527f4d65737361676520616c72656164792070726f636573736564000000000000006044820152fd5b6040517fa9059cbb0000000000000000000000000000000000000000000000000000000060208201526001600160a01b039290921660248301526044808301939093529181526147e3916147de60648361412d56fea2646970667358221220c01bb022dcabde12b63571b0b163c202062a26bed1b2436335bad59d81d8542364736f6c63430008140033000000000000000000000000000000000000000000000000000000000000000200000000000000000000000098f3c9e6e3face36baad05fe09d375ef1464288b0000000000000000000000003ee18b2214aff97000d974cf647e7c347e8fa58500000000000000000000000027428dd2d3dd32a4d7f7c497eaaa23130d89491100000000000000000000000000000000000000000000000000000000000000170000000000000000000000001e3f1f1ca8c62aabcb3b78d87223e988dfa3780e0000000000000000000000000a992d191deec32afe36203ad87d7d289a738f81000000000000000000000000bd3fa81b58ba92a82136038b25adec7066af3155000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48

Deployed Bytecode

0x60806040526004361015610018575b361561001657005b005b6000803560e01c80630c59272014613ffc57806314f29f8814613fd1578063180f6cc214613fa25780631a74856b146134ef5780632fd8f7e6146131235780633757e53d146130ee578063378717b7146130bf5780633d3a066f14612e23578063529dca3214611da9578063715018a614611d405780637816976814611d2257806384acd1bb14611cfb57806389a3027114611cd45780638c1577c914611cad5780638cd2e0c71461119d5780638da5cb5b14611176578063929f5840146111525780639a8a059214611131578063a489e28a14611088578063a7b4629e14610e68578063a8cbeb2914610a97578063c1bce0b714610777578063c6328a461461074d578063d8c71a9214610724578063da25b725146106fd578063dd12d68b1461064a578063decfba6114610614578063dfe6f20014610273578063e1cc30ee1461024c578063e8dfd508146102295763f2fde38b14610179575061000e565b346102265760206003193601126102265761019261401f565b61019a6141ee565b6001600160a01b038091169081156101f557603e548273ffffffffffffffffffffffffffffffffffffffff19821617603e55167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a380f35b602483604051907f1e4fbdf70000000000000000000000000000000000000000000000000000000082526004820152fd5b80fd5b503461022657806003193601126102265760ff6020915460101c16604051908152f35b503461022657806003193601126102265760206001600160a01b03603c5416604051908152f35b5061027d366141cb565b9291603a5490604051936315ab88c960e31b855260209485816004816001600160a01b03809860081c165afa918215610608579084926105db575b50169080156105975782603d541682148061058b575b82151580610583575b610443575b808697819796979261043a575b81610431575b6040519460018887015233604087015260608601521515608085015281151560a0850152151560c084015260c08352610327836140f5565b156103fd57505b61037d8260355416916000549560015494604051978896879586956312d729bd60e21b875261ffff94858560181c1660048901528460281c16602488015260e0604488015260e487019061474c565b93606486015260848501521660a48301523360c4830152039134905af19081156103f1576000916103bc575b5067ffffffffffffffff60405191168152f35b908282813d83116103ea575b6103d2818361412d565b8101031261022657506103e490614771565b386103a9565b503d6103c8565b6040513d6000823e3d90fd5b61042c9061041e60405193849287840152604080840152606083019061474c565b03601f19810183528261412d565b61032e565b600191506102ef565b600092506102e9565b60405163313ce56760e01b81528681600481875afa80156103f157839160009161054c575b5060ff8116906008821190838215610528576104959061048f61048a84614da6565b614de8565b90614e0c565b80926000146104ff57506104b6925061048a6104b091614da6565b90614df9565b146102dc576064866040519062461bcd60e51b82526004820152601760248201527f546f6f206d616e7920646563696d616c20706c616365730000000000000000006044820152fd5b9192600811610510575b50506104b6565b610521925061048a61048f91614dd7565b3880610509565b6008841015610495575061054761054161048a83614dd7565b85614df9565b610495565b91508782813d831161057c575b610563818361412d565b8101031261022657506105768391614d98565b38610468565b503d610559565b5080156102d7565b5060ff600254166102ce565b6064856040519062461bcd60e51b82526004820152601460248201527f4e6f207a65726f20617373657420616d6f756e740000000000000000000000006044820152fd5b6105fb9150863d8811610601575b6105f3818361412d565b81019061441a565b386102b8565b503d6105e9565b604051903d90823e3d90fd5b50346102265760606003193601126102265761064761063161401f565b610639614035565b61064161404b565b916142d9565b80f35b50346102265760406003193601126102265760043561ffff81168091036106f8576001600160a01b0360385416330361068f5781526039602052602435604082205580f35b608460405162461bcd60e51b8152602060048201526024808201527f4e6f7420616c6c6f77656420746f20736574207265676973746572656420736560448201527f6e646572000000000000000000000000000000000000000000000000000000006064820152fd5b600080fd5b503461022657806003193601126102265760206001600160a01b0360355416604051908152f35b50346102265780600319360112610226576001600160a01b036020915460281c16604051908152f35b503461022657806003193601126102265760206001600160a01b03603a5460081c16604051908152f35b61078036614061565b909161078d8215156143ac565b8215610a53576001600160a01b038080603d541692169182149384610a46575b849083151580610a3e575b610907575b816108fe575b856108f5575b60405191602096879560018786015233604086015260608501526000608085015281151560a0850152151560c084015260c08352610806836140f5565b156108cf57505b61085c8260355416916000549560015494604051978896879586956312d729bd60e21b875261ffff94858560181c1660048901528460281c16602488015260e0604488015260e487019061474c565b93606486015260848501521660a48301523360c4830152039134905af19081156103f15760009161089a575067ffffffffffffffff60405191168152f35b908282813d83116108c8575b6108b0818361412d565b8101031261022657506108c290614771565b826103a9565b503d6108a6565b6108f09061041e60405193849287840152604080840152606083019061474c565b61080d565b600195506107c9565b600091506107c3565b905060405163313ce56760e01b8152602081600481875afa80156103f1578291600091610a03575b5060ff81169060088211908382156109e5576109519061048f61048a84614da6565b80926000146109bc575061096c925061048a6104b091614da6565b036109785784906107bd565b606460405162461bcd60e51b815260206004820152601760248201527f546f6f206d616e7920646563696d616c20706c616365730000000000000000006044820152fd5b91926008116109cd575b505061096c565b6109de925061048a61048f91614dd7565b87806109c6565b600884101561095157506109fe61054161048a83614dd7565b610951565b91506020823d8211610a36575b81610a1d6020938361412d565b810103126102265750610a308291614d98565b8761092f565b3d9150610a10565b5081156107b8565b60025460ff1694506107ad565b606460405162461bcd60e51b815260206004820152601460248201527f4e6f207a65726f20617373657420616d6f756e740000000000000000000000006044820152fd5b50602090816003193601126102265750600435610ab38161445c565b803410610dfe57610ac490346143f7565b8015610dba576001600160a01b039081603d54161580610dae575b8080610da6575b81610d9d575b60405191600387840152336040840152600060608401526000608084015281151560a0840152151560c083015260c08252610b26826140f5565b15610d76575b82603a5460081c16916040516384acd1bb60e01b81528681600481875afa9081156103f15785918891600091610d59575b50600460405180948193631a90a21960e01b8352165afa9081156103f157600091610d2a575b50610b8d9161444f565b928560005494604051948580927fbee9cdfc0000000000000000000000000000000000000000000000000000000082528561ffff998a8160181c16600485015260281c166024830152600060448301526080606483015281610bf2608482018961474c565b03925af19384156103f1578693600095610ced575b50610c10614968565b9160005491610c998184169280603a5460081c1660405190610c31826140bd565b8582528982015267ffffffffffffffff809a166040820152610c52876149a4565b52610c5c866149a4565b50610c668a6145b3565b938160355416966001546040519c8d9a8b998a986319597e0760e11b8a5233978260281c169160181c1660048a016149db565b03925af19182156103f157600092610cb6575b5060405191168152f35b90918382813d8311610ce6575b610ccd818361412d565b810103126102265750610cdf90614771565b9038610cac565b503d610cc3565b91929382819692963d8311610d23575b610d07818361412d565b81010312610226575090610d1c869392614771565b9338610c07565b503d610cfd565b908782813d8311610d52575b610d40818361412d565b81010312610226575051610b8d610b83565b503d610d36565b610d709150823d8411610601576105f3818361412d565b38610b5d565b610d9861041e916040519283918589840152604080840152606083019061474c565b610b2c565b60019150610aec565b506000610ae6565b5060ff60025416610adf565b6064836040519062461bcd60e51b82526004820152601460248201527f4e6f207a65726f20617373657420616d6f756e740000000000000000000000006044820152fd5b6084836040519062461bcd60e51b82526004820152602a60248201527f53706f6b653a3a72657061794e61746976653a496e73756666696369656e742060448201527f76616c75652073656e74000000000000000000000000000000000000000000006064820152fd5b50610e72366141cb565b9291603a5490604051936315ab88c960e31b855260209485816004816001600160a01b03809860081c165afa9182156106085790849261106b575b50169080156105975782603d541682148061105f575b82151580611057575b610f2e575b8086978197969792610f25575b81610f1c575b6040519460028887015233604087015260608601521515608085015281151560a0850152151560c084015260c08352610327836140f5565b60019150610ee4565b60009250610ede565b60405163313ce56760e01b81528681600481875afa80156103f1578391600091611020575b5060ff811690600882119083821561100257610f759061048f61048a84614da6565b8092600014610fd95750610f90925061048a6104b091614da6565b14610ed1576064866040519062461bcd60e51b82526004820152601760248201527f546f6f206d616e7920646563696d616c20706c616365730000000000000000006044820152fd5b9192600811610fea575b5050610f90565b610ffb925061048a61048f91614dd7565b3880610fe3565b6008841015610f75575061101b61054161048a83614dd7565b610f75565b91508782813d8311611050575b611037818361412d565b81010312610226575061104a8391614d98565b38610f53565b503d61102d565b508015610ecc565b5060ff60025416610ec3565b6110829150863d8811610601576105f3818361412d565b38610ead565b50346102265760c0600319360112610226576110a261401f565b6110aa614035565b6110b261404b565b91606435906001600160a01b03938483168093036106f857608435938585168095036106f85760a4359586168096036106f8576110fd926110f860ff603a541615614232565b6142d9565b73ffffffffffffffffffffffffffffffffffffffff199182603b541617603b5581603c541617603c55603d541617603d5580f35b503461022657806003193601126102265761ffff6020915416604051908152f35b503461022657806003193601126102265761ffff6020915460181c16604051908152f35b503461022657806003193601126102265760206001600160a01b03603e5416604051908152f35b6111a636614061565b906111bb6111b38361445c565b341015614361565b8015610a53576001600160a01b0380603d541693169283149283611ca0575b80151580611c98575b611b6a575b838080611b62575b81611b59575b60405191600360208401523360408401528360608401526000608084015281151560a0840152151560c083015260c08252611230826140f5565b15611b3057935b6000901561176f57505061125a816001600160a01b03603d541630903390614786565b60005491600154906040519161126f836140d9565b6001835260005b602081106117585750611287614bd9565b506112aa846001600160a01b03603d54166001600160a01b03603b541690614a9e565b6001600160a01b03603b5416602063ffffffff60a46112cf61ffff8a60181c16614bf3565b60006001600160a01b03603d54169560405196879586947ff856ddb60000000000000000000000000000000000000000000000000000000086528d60048701521660248501526001600160a01b038d60281c16604485015260648401526001600160a01b038c60281c1660848401525af19081156103f15760009161171e575b50600460206001600160a01b036036541660405192838092634d4502c960e11b82525afa9081156103f1576000916116bf575b506113ad7fffffffffffffffff00000000000000000000000000000000000000000000000091614bf3565b917fffffffff000000000000000000000000000000000000000000000000000000006040519360e01b16602084015260c01b166024820152600c81526113f2816140d9565b604051906113ff826140d9565b600282526020820152611411846149a4565b5261141b836149a4565b506001600160a01b036035541694604051917f24320c9f0000000000000000000000000000000000000000000000000000000083526020836004818a5afa9283156103f15760009361169e575b506040805163c23ee3c360e01b815261ffff601885901c16600482015260248101869052604481018390529690876064818b5afa9687156103f157600097611655575b506001600160a01b03926115439997969594926114f161ffff936114e36040519b8c926020840152604080840152606083019061474c565b03601f1981018b528a61412d565b6040519a8b997fc055120e000000000000000000000000000000000000000000000000000000008b52848460181c1660048c0152868460281c1660248c015261016060448c01526101648b019061474c565b9560648a0152600060848a015260a48901521660c48701523360e48701521661010485015260031984820301610124850152815180825260208201916020808360051b8301019401926000915b83831061160e57505050505060209391838092600f61014483015203925af180156103f1576000906115d4575b602091505b67ffffffffffffffff60405191168152f35b6020823d602011611606575b816115ed6020938361412d565b810103126102265750611601602091614771565b6115bd565b3d91506115e0565b9193959650919360208061164283601f1986600196030187526040838b5160ff81511684520151918185820152019061474c565b9701930193019092889695949293611590565b6001600160a01b039493919750916114f161168a61ffff9460403d604011611697575b611682818361412d565b810190614439565b50989294955050916114ab565b503d611678565b6116b891935060203d602011610601576105f3818361412d565b9188611468565b906020823d602011611716575b816116d96020938361412d565b8101031261022657506113ad61170f7fffffffffffffffff00000000000000000000000000000000000000000000000092614a8f565b9150611382565b3d91506116cc565b906020823d602011611750575b816117386020938361412d565b81010312610226575061174a90614771565b8761134f565b3d915061172b565b602090611763614bd9565b82828701015201611276565b9190929361177f82303387614786565b8254936001549061178e614968565b93611797614949565b506004604051926117a784614111565b8784526117b2614949565b506117cc836001600160a01b03603a5460081c1683614a9e565b6001600160a01b03603a5460081c169360206001600160a01b036036541660405194858092631a90a21960e01b82525afa928315611b2557908a949392918a93611ae4575b509160209493916118896001600160a01b039794604051988997889687957fc5a5ebda0000000000000000000000000000000000000000000000000000000087526004870152602486015261ffff8160181c16604486015260281c1660648401528c608484015260c060a484015260c483019061474c565b03925af18015611ad9578590611a9b575b600491506001600160a01b03603a5460081c1660206001600160a01b036036541660405194858092634d4502c960e11b82525afa928315611a90578793611a45575b509067ffffffffffffffff9161ffff604051946118f8866140bd565b168452602084015216604082015261190f846149a4565b52611919836149a4565b506001600160a01b0360355416916040519663c23ee3c360e01b885260408880611964868561ffff8d60181c166004850160409194939261ffff606083019616825260208201520152565b0381875afa918215611a3a5786979860209793611a14575b506119bb90604051998a97889687956319597e0760e11b8752339461ffff85169461ffff6001600160a01b038260281c169160181c1660048a016149db565b03925af19081156106085780916119d7575b50602091506115c2565b90506020823d602011611a0c575b816119f26020938361412d565b810103126102265750611a06602091614771565b826119cd565b3d91506119e5565b6119bb919350611a329060403d60401161169757611682818361412d565b50929061197c565b6040513d88823e3d90fd5b9092506020813d602011611a88575b81611a616020938361412d565b81010312611a845790611a7c67ffffffffffffffff92614a8f565b9290916118dc565b8680fd5b3d9150611a54565b6040513d89823e3d90fd5b506020813d602011611ad1575b81611ab56020938361412d565b81010312611acd57611ac8600491614771565b61189a565b8480fd5b3d9150611aa8565b6040513d87823e3d90fd5b9192509293506020813d602011611b1d575b81611b036020938361412d565b81010312611b1957518993929091906020611811565b8880fd5b3d9150611af6565b6040513d8b823e3d90fd5b611b5361041e91604051928391866020840152604080840152606083019061474c565b93611237565b600091506111f6565b5060016111f0565b60405163313ce56760e01b8152602081600481855afa80156103f1578391600091611c5d575b5060ff8116906008821190838215611c3f57611bb29061048f61048a84614da6565b8092600014611c165750611bcd925061048a6104b091614da6565b146111e857606460405162461bcd60e51b815260206004820152601760248201527f546f6f206d616e7920646563696d616c20706c616365730000000000000000006044820152fd5b9192600811611c27575b5050611bcd565b611c38925061048a61048f91614dd7565b8680611c20565b6008841015611bb25750611c5861054161048a83614dd7565b611bb2565b91506020823d8211611c90575b81611c776020938361412d565b810103126102265750611c8a8391614d98565b86611b90565b3d9150611c6a565b5083156111e3565b60025460ff1693506111da565b503461022657806003193601126102265760206001600160a01b03603b5416604051908152f35b503461022657806003193601126102265760206001600160a01b03603d5416604051908152f35b503461022657806003193601126102265760206001600160a01b0360365416604051908152f35b50346102265780600319360112610226576020600154604051908152f35b5034610226578060031936011261022657611d596141ee565b60006001600160a01b03603e5473ffffffffffffffffffffffffffffffffffffffff198116603e55167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b5060031960a081360112612e1f5760043567ffffffffffffffff8111612d2657611dd790369060040161416c565b60243567ffffffffffffffff8111612e1b5736602382011215612e1b57806004013590611e03826141b3565b91611e11604051938461412d565b80835260051b810160240160208301368211611a845760248301905b828210612deb57505050506064359261ffff84168403611acd578151611e7f575050611e599150614d62565b50508134611e6657505080f35b8080806106479434905af1611e79614886565b50614c71565b611e8a839293614d62565b91505060001461221f5760018351116121b55784926001815114611fa1575b5050611ebf816020808594518301019101614cfe565b9203611f5d5761ffff92611f44611f52936001600160a01b03958693611eea85603554163314614eac565b1687526039602052611f03604435604089205414614f1d565b6084358088526037602052611f1f60ff60408a20541615614f68565b8752603760205260408720600160ff1982541617905560208082518301019101614d2c565b5050168093603d5416614fb3565b8134611e6657505080f35b606460405162461bcd60e51b815260206004820152601560248201527f57726f6e6720616d6f756e7420726563656976656400000000000000000000006044820152fd5b611fac9193506149a4565b51918251830192604081602086019503126121b157602081015167ffffffffffffffff8111611a8457846020611fe492840101614cbc565b9360408201519167ffffffffffffffff83116121ad576120079201602001614cbc565b906001600160a01b0380603d541692604051956020876024817f70a0823100000000000000000000000000000000000000000000000000000000988982523060048301525afa968715611b25578997612176575b50906020916120ab8a85603c5416926120ba604051988996879586947f57ecfd2800000000000000000000000000000000000000000000000000000000865260406004870152604486019061474c565b9184830301602485015261474c565b03925af18015611a905761213c575b60209150603d54169160246040518094819382523060048301525afa908115611ad9578591612108575b50611ebf92612101916143f7565b9138611ea9565b90506020813d602011612134575b816121236020938361412d565b810103126106f85751611ebf6120f3565b3d9150612116565b6020823d60201161216e575b816121556020938361412d565b81010312611a84576121686020926147e5565b506120c9565b3d9150612148565b919096506020823d6020116121a5575b816121936020938361412d565b810103126106f857905195602061205b565b3d9150612186565b8780fd5b8580fd5b608460405162461bcd60e51b815260206004820152602660248201527f434354503a204174206d6f7374206f6e65204d6573736167652069732073757060448201527f706f7274656400000000000000000000000000000000000000000000000000006064820152fd5b50929190805191601f1961224b612235856141b3565b94612243604051968761412d565b8086526141b3565b01845b818110612db6575050835b8251811015612b6e576122b86001600160a01b03603654168661227c84876149c7565b51604051809481927fa9e1189300000000000000000000000000000000000000000000000000000000835260206004840152602483019061474c565b0381845afa918215611a90578792612929575b50608082015190603a549161ffff60608501511690604051917fad66a5f100000000000000000000000000000000000000000000000000000000835260048301526020826024816001600160a01b038860081c165afa91821561291e578a926128ea575b50036128a6578760e0612379940151604051809581927fea63738d00000000000000000000000000000000000000000000000000000000835260206004840152602483019061474c565b03816001600160a01b038660081c165afa92831561289b578893612787575b5030608084015114908161270f575b50156126a557866124036001600160a01b03926123c486896149c7565b51836040519586809581947fc3f511c100000000000000000000000000000000000000000000000000000000835260206004840152602483019061474c565b039360081c165af18015611a9057612663575b5061ffff606082015116604082015190600460206001600160a01b036036541660405192838092634d4502c960e11b82525afa8015611b25578990612628575b61ffff1682141590506125a857508060a01c612577576001600160a01b03165b8680604051602081019063313ce56760e01b825260048152612497816140d9565b5190845afa506124a5614886565b6020818051810103126121ad5760206124be9101614d98565b602083019182519160ff9060088282161161254f575b50506001600160a01b039061ffff6060604087015196015116935193604051956124fd8761408b565b865260208601521660408401526060830152608082015261251e82866149c7565b5261252981856149c7565b50600019811461253b57600101612259565b602485634e487b7160e01b81526011600452fd5b926104b061256f926125696001600160a01b039596614da6565b16614e3d565b9190386124d4565b602490604051907f33b960d00000000000000000000000000000000000000000000000000000000082526004820152fd5b9060209060446001600160a01b03603a5460081c169360405194859384927f1ff1e286000000000000000000000000000000000000000000000000000000008452600484015260248301525afa908115611a90578791612609575b50612476565b612622915060203d602011610601576105f3818361412d565b38612603565b506020813d60201161265b575b816126426020938361412d565b81010312611b195761265661ffff91614a8f565b612456565b3d9150612635565b3d8088833e612672818361412d565b60208282810103126121ad5781519067ffffffffffffffff8211611b195761269e929081019101614cbc565b5038612416565b608460405162461bcd60e51b815260206004820152602260248201527f546f6b656e20776173206e6f742073656e7420746f207468697320616464726560448201527f73730000000000000000000000000000000000000000000000000000000000006064820152fd5b90506004602061ffff60a0860151169260405192838092634d4502c960e11b82525afa8015611b2557899061274c575b61ffff91501614386123a7565b506020813d60201161277f575b816127666020938361412d565b81010312611b195761277a61ffff91614a8f565b61273f565b3d9150612759565b9092503d908189823e61279a828261412d565b6020818381010312611b195780519167ffffffffffffffff831161289757610100838301828401031261289757604051928361010081011067ffffffffffffffff610100860111176128835761010084016040526127f9818401614d98565b8452828101602081810151908601526040808201519086015261281e90606001614a8f565b606085015260808184010151608085015261283d60a082850101614a8f565b60a085015282810160c0818101519086015260e001519167ffffffffffffffff831161287f576128739390810192910101614cbc565b60e08201529138612398565b8b80fd5b60248b634e487b7160e01b81526041600452fd5b8980fd5b6040513d8a823e3d90fd5b606460405162461bcd60e51b815260206004820152601660248201527f4e6f74206120546f6b656e2042726964676520564141000000000000000000006044820152fd5b9091506020813d602011612916575b816129066020938361412d565b810103126128975751903861232f565b3d91506128f9565b6040513d8c823e3d90fd5b9091503d908188823e61293c828261412d565b60208183810103126121ad5780519067ffffffffffffffff8211611b1957610160928383830182840103126128975760405193848181011067ffffffffffffffff8287011117612883578401604052612996838301614d98565b84526129a6602084840101614e2c565b60208501526129b9604084840101614e2c565b60408501526129cc606084840101614a8f565b60608501526080838301015160808501526129eb60a084840101614771565b60a085015260c06129ff8185850101614d98565b9085015260e0838301015167ffffffffffffffff8111612b6a57612a2a908284019085850101614cbc565b60e0850152610100612a3f8185850101614e2c565b90850152610120838301015167ffffffffffffffff8111612b6a57818301601f8286860101011215612b6a57808484010151612a7a816141b3565b92612a88604051948561412d565b818452602084019281860160208460071b838a8a0101010111612b66576020818888010101935b60208460071b838a8a010101018510612ae057505050505061012084015201610140908101519082015290386122cb565b6080858489010312612b625760405180608081011067ffffffffffffffff608083011117612b4d57608091818360209301604052875181528288015183820152612b2c60408901614d98565b6040820152612b3d60608901614d98565b6060820152815201940193612aaf565b5060248f634e487b7160e01b81526041600452fd5b8e80fd5b8d80fd5b8a80fd5b50929391905061ffff6001600160a01b0393612b8f85603554163314614eac565b1684526039602052612ba8604435604086205414614f1d565b6084358085526037602052612bc460ff60408720541615614f68565b8452603760205260408420600160ff198254161790556001815103612d7257612bf7612bf085926149a4565b5192614d62565b5090809491600014612d545750806020600492603a5460081c16604051938480926315ab88c960e31b82525afa8015612d49576060928491612d2a575b5016920191825190803b15612d26576024839260405194859384927f2e1a7d4d00000000000000000000000000000000000000000000000000000000845260048401525af18015612d1b57612ceb575b50828080809351855af1612c96614886565b5015612ca7578134611e6657505080f35b606460405162461bcd60e51b815260206004820152601960248201527f776974686472617720746f206e6174697665206661696c6564000000000000006044820152fd5b67ffffffffffffffff8194929411612d07576040529138612c84565b602482634e487b7160e01b81526041600452fd5b6040513d86823e3d90fd5b8280fd5b612d43915060203d602011610601576105f3818361412d565b38612c34565b6040513d85823e3d90fd5b908392506060906040612d6d9501511692015191614fb3565b611f52565b606460405162461bcd60e51b815260206004820152601660248201527f457870656374696e67206f6e65207472616e73666572000000000000000000006044820152fd5b602090604051612dc58161408b565b87815287838201528760408201528760608201528760808201528282880101520161224e565b813567ffffffffffffffff8111611b1957602091612e1083926024369189010161416c565b815201910190611e2d565b8380fd5b5080fd5b50612e2d36614061565b909291612e3b8215156143ac565b8315610a53576001600160a01b038080603d5416921691821494856130b2575b8590831515806130aa575b612fb6575b81612fae575b86612fa5575b604051916020978895600287860152336040860152606085015287608085015281151560a0850152151560c084015260c08352612eb3836140f5565b15612f7f57505b612f0882603554169186549560015494604051978896879586956312d729bd60e21b875261ffff94858560181c1660048901528460281c16602488015260e0604488015260e487019061474c565b93606486015260848501521660a48301523360c4830152039134905af1918215610608578092612f47575b505067ffffffffffffffff60405191168152f35b9091508282813d8311612f78575b612f5f818361412d565b810103126102265750612f7190614771565b3880612f33565b503d612f55565b612fa09061041e60405193849287840152604080840152606083019061474c565b612eba565b60019650612e77565b859150612e71565b905060405163313ce56760e01b8152602081600481875afa908115611a3a57908291879161306f575b5060ff8116906008821190838215613051576130019061048f61048a84614da6565b8092600014613028575061301c925061048a6104b091614da6565b03610978578590612e6b565b9192600811613039575b505061301c565b61304a925061048a61048f91614dd7565b3880613032565b6008841015613001575061306a61054161048a83614dd7565b613001565b9150506020813d82116130a2575b8161308a6020938361412d565b810103126121b15761309c8291614d98565b38612fdf565b3d915061307d565b508115612e66565b60025460ff169550612e5b565b5034610226576020600319360112610226576004358015158103612e1f57610647906130e96141ee565b61433a565b50346102265760406003193601126102265760243590811515820361022657602061311b8360043561462b565b604051908152f35b50602090816003193601126102265760043561313e8161445c565b8034106134855761314f90346143f7565b908115613441576001600160a01b0380603d54161580613435575b808061342e575b81613425575b60405191868884015233604084015286606084015286608084015281151560a0840152151560c083015260c082526131ae826140f5565b156133fd57905b80603a5460081c16936040516384acd1bb60e01b81528781600481895afa908115611a90578391899189916133e0575b50600460405180948193631a90a21960e01b8352165afa908115611a905787916133b1575b506132149161444f565b86865491604051968780927fbee9cdfc0000000000000000000000000000000000000000000000000000000082528661ffff96878160181c16600485015260281c1660248301528a60448301526080606483015281613276608482018a61474c565b03925af1948515611a3a5790879392918796613375575b50613296614968565b9187549161331e8184169280603a5460081c16604051906132b6826140bd565b8582528982015267ffffffffffffffff809b1660408201526132d7876149a4565b526132e1866149a4565b506132eb896145b3565b938160355416966001546040519b8c9a8b998a986319597e0760e11b8a5233978260281c169160181c1660048a016149db565b03925af192831561060857809361333b575b505060405191168152f35b909192508382813d831161336e575b613354818361412d565b81010312610226575061336690614771565b903880613330565b503d61334a565b84819394959297503d83116133aa575b61338f818361412d565b810103126121b157906133a3879392614771565b943861328d565b503d613385565b90508781813d83116133d9575b6133c8818361412d565b81010312611a84575161321461320a565b503d6133be565b6133f79150823d8411610601576105f3818361412d565b386131e5565b61341f61041e91604051928391878a840152604080840152606083019061474c565b906131b5565b60019150613177565b5084613171565b5060ff6002541661316a565b6064846040519062461bcd60e51b82526004820152601460248201527f4e6f207a65726f20617373657420616d6f756e740000000000000000000000006044820152fd5b6084846040519062461bcd60e51b82526004820152603660248201527f53706f6b653a3a6465706f736974436f6c6c61746572616c4e61746976653a4960448201527f6e73756666696369656e742076616c75652073656e74000000000000000000006064820152fd5b506134f936614061565b906135096111b38395949561445c565b8015610a53576001600160a01b0380603d541694169384149384613f95575b80151580613f8d575b613e5e575b848080613e56575b81613e4e575b6040519186602084015233604084015283606084015286608084015281151560a0840152151560c083015260c0825261357c826140f5565b15613e2557945b15613a9e57506135a2816001600160a01b03603d541630903390614786565b82549160015490604051916135b6836140d9565b60018352855b60208110613a8757506135cd614bd9565b506135f0846001600160a01b03603d54166001600160a01b03603b541690614a9e565b6001600160a01b03603b5416602063ffffffff60a461361561ffff8a60181c16614bf3565b8a6001600160a01b03603d54169560405196879586947ff856ddb60000000000000000000000000000000000000000000000000000000086528d60048701521660248501526001600160a01b038d60281c16604485015260648401526001600160a01b038c60281c1660848401525af1908115611a90578791613a4d575b50600460206001600160a01b036036541660405192838092634d4502c960e11b82525afa90811561289b5788916139ee575b506136f07fffffffffffffffff00000000000000000000000000000000000000000000000091614bf3565b917fffffffff000000000000000000000000000000000000000000000000000000006040519360e01b16602084015260c01b166024820152600c8152613735816140d9565b60405190613742826140d9565b600282526020820152613754846149a4565b5261375e836149a4565b506001600160a01b036035541694604051917f24320c9f0000000000000000000000000000000000000000000000000000000083526020836004818a5afa92831561289b5788936139cd575b506040805163c23ee3c360e01b815261ffff601885901c166004820152602481018690526044810183905299908a6064818b5afa968715611b255760449a8a9861398e575b5061387b9a509261ffff9197969594926138296001600160a01b03956114e36040519b8c926020840152604080840152606083019061474c565b6040519b8c997fc055120e000000000000000000000000000000000000000000000000000000008b52848460181c1660048c0152868460281c1660248c015261016060448c01526101648b019061474c565b9560648a01528a60848a015260a48901521660c48701523360e48701521661010485015260031984820301610124850152815180825260208201916020808360051b83010194019288915b83831061394757505050505060209391838092600f61014483015203925af190811561060857809161390a575b506020915067ffffffffffffffff60405191168152f35b90506020823d60201161393f575b816139256020938361412d565b810103126102265750613939602091614771565b386138f3565b3d9150613918565b9193959650919360208061397b83601f1986600196030187526040838b5160ff81511684520151918185820152019061474c565b97019301930190928996959492936138c6565b61ffff939198506001600160a01b039594926138296139be61387b9360403d60401161169757611682818361412d565b509a93955050929495506137ef565b6139e791935060203d602011610601576105f3818361412d565b91386137aa565b90506020813d602011613a45575b81613a096020938361412d565b810103126121ad576136f0613a3e7fffffffffffffffff00000000000000000000000000000000000000000000000092614a8f565b91506136c5565b3d91506139fc565b90506020813d602011613a7f575b81613a686020938361412d565b81010312611a8457613a7990614771565b38613693565b3d9150613a5b565b602090613a92614bd9565b828287010152016135bc565b929193613aad82303387614786565b82549360015490613abc614968565b93613ac5614949565b50600460405192613ad584614111565b878452613ae0614949565b50613afa836001600160a01b03603a5460081c1683614a9e565b6001600160a01b03603a5460081c169360206001600160a01b036036541660405194858092631a90a21960e01b82525afa928315611b2557908a949392918a93613de8575b50916020949391613bb76001600160a01b039794604051988997889687957fc5a5ebda0000000000000000000000000000000000000000000000000000000087526004870152602486015261ffff8160181c16604486015260281c1660648401528c608484015260c060a484015260c483019061474c565b03925af18015611ad9578590613dae575b600491506001600160a01b03603a5460081c1660206001600160a01b036036541660405194858092634d4502c960e11b82525afa928315611a90578793613d67575b509067ffffffffffffffff9161ffff60405194613c26866140bd565b1684526020840152166040820152613c3d846149a4565b52613c47836149a4565b506001600160a01b0360355416916040519663c23ee3c360e01b885260408880613c92868561ffff8d60181c166004850160409194939261ffff606083019616825260208201520152565b0381875afa918215611a3a5786979860209793613d41575b50613ce990604051998a97889687956319597e0760e11b8752339461ffff85169461ffff6001600160a01b038260281c169160181c1660048a016149db565b03925af1908115610608578091613d045750602091506115c2565b90506020823d602011613d39575b81613d1f6020938361412d565b810103126102265750613d33602091614771565b386119cd565b3d9150613d12565b613ce9919350613d5f9060403d60401161169757611682818361412d565b509290613caa565b9092506020813d602011613da6575b81613d836020938361412d565b81010312611a845790613d9e67ffffffffffffffff92614a8f565b929091613c0a565b3d9150613d76565b506020813d602011613de0575b81613dc86020938361412d565b81010312611acd57613ddb600491614771565b613bc8565b3d9150613dbb565b9192509293506020813d602011613e1d575b81613e076020938361412d565b81010312611b1957518993929091906020613b3f565b3d9150613dfa565b613e4861041e91604051928391866020840152604080840152606083019061474c565b94613583565b859150613544565b50600161353e565b60405163313ce56760e01b8152602081600481855afa908115611ad9579083918691613f52575b5060ff8116906008821190838215613f3457613ea79061048f61048a84614da6565b8092600014613f0b5750613ec2925061048a6104b091614da6565b1461353657606460405162461bcd60e51b815260206004820152601760248201527f546f6f206d616e7920646563696d616c20706c616365730000000000000000006044820152fd5b9192600811613f1c575b5050613ec2565b613f2d925061048a61048f91614dd7565b3880613f15565b6008841015613ea75750613f4d61054161048a83614dd7565b613ea7565b9150506020813d8211613f85575b81613f6d6020938361412d565b81010312611acd57613f7f8391614d98565b38613e85565b3d9150613f60565b508415613531565b60025460ff169450613528565b50346102265760206003193601126102265760ff60406020926004358152603784522054166040519015158152f35b503461022657604060031936011261022657610647613fee61401f565b613ff6614035565b9061427d565b5034610226576020600319360112610226576140166141ee565b60043560015580f35b600435906001600160a01b03821682036106f857565b602435906001600160a01b03821682036106f857565b604435906001600160a01b03821682036106f857565b60031960609101126106f8576004356001600160a01b03811681036106f857906024359060443590565b60a0810190811067ffffffffffffffff8211176140a757604052565b634e487b7160e01b600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176140a757604052565b6040810190811067ffffffffffffffff8211176140a757604052565b60e0810190811067ffffffffffffffff8211176140a757604052565b6020810190811067ffffffffffffffff8211176140a757604052565b90601f601f19910116810190811067ffffffffffffffff8211176140a757604052565b67ffffffffffffffff81116140a757601f01601f191660200190565b81601f820112156106f85780359061418382614150565b92614191604051948561412d565b828452602083830101116106f857816000926020809301838601378301015290565b67ffffffffffffffff81116140a75760051b60200190565b60031960609101126106f857600435906024359060443580151581036106f85790565b6001600160a01b03603e5416330361420257565b60246040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152fd5b1561423957565b606460405162461bcd60e51b815260206004820152600360248201527f57524900000000000000000000000000000000000000000000000000000000006044820152fd5b90600160ff19603a5461429360ff821615614232565b1617603a556001600160a01b03908173ffffffffffffffffffffffffffffffffffffffff1993168360355416176035551681603654161760365533906038541617603855565b916142f391926142ee60ff603a541615614232565b61427d565b7fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff00603a549260081b16911617603a55565b6001600160a01b03603c54161561435e5760ff60ff19600254169115151617600255565b50565b1561436857565b606460405162461bcd60e51b815260206004820152601760248201527f496e73756666696369656e742076616c75652073656e740000000000000000006044820152fd5b156143b357565b606460405162461bcd60e51b815260206004820152601e60248201527f4e6f6e2d7a65726f20636f7374466f7252657475726e44656c697665727900006044820152fd5b9190820391821161440457565b634e487b7160e01b600052601160045260246000fd5b908160209103126106f857516001600160a01b03811681036106f85790565b91908260409103126106f8576020825192015190565b9190820180921161440457565b603554600080546001546040805163c23ee3c360e01b815260189390931c61ffff16600484015260248301959095526044820152909392916001600160a01b039083908390831681806064810103915afa9182156145a9578592614589575b5080603a5460081c16835180916384acd1bb60e01b825281600460209485935afa90811561457f579082918891614562575b506004865180958193631a90a21960e01b8352165afa93841561455957508593614522575b505061451f92935061444f565b90565b9080929350813d8311614552575b61453a818361412d565b81010312612e1b5761451f9293505190839238614512565b503d614530565b513d87823e3d90fd5b6145799150823d8411610601576105f3818361412d565b386144ed565b85513d89823e3d90fd5b6145a1919250833d851161169757611682818361412d565b5090386144bb565b83513d87823e3d90fd5b6035546000546001546040805163c23ee3c360e01b815260189390931c61ffff16600484015260248301949094526044820152919082906001600160a01b031681806064810103915afa9081156103f15760009161460f575090565b614627915060403d811161169757611682818361412d565b5090565b603554600080546001546040805163c23ee3c360e01b815260189390931c61ffff16600484015260248301959095526044820152939092916001600160a01b03919083908690841681806064810103915afa94851561471c5784956146fc575b506146965750505090565b80603a95949293955460081c16835180916384acd1bb60e01b825281600460209485935afa90811561457f57908291889161456257506004865180958193631a90a21960e01b8352165afa9384156145595750859361452257505061451f92935061444f565b614714919550833d851161169757611682818361412d565b50933861468b565b50505051903d90823e3d90fd5b60005b83811061473c5750506000910152565b818101518382015260200161472c565b90601f19601f60209361476a81518092818752878088019101614729565b0116010190565b519067ffffffffffffffff821682036106f857565b90926147e393604051937f23b872dd0000000000000000000000000000000000000000000000000000000060208601526001600160a01b0380921660248601521660448401526064830152606482526147de8261408b565b6147f2565b565b519081151582036106f857565b6000806001600160a01b0361481c93169360208151910182865af1614815614886565b90836148b6565b8051908115159182614863575b50506148325750565b602490604051907f5274afe70000000000000000000000000000000000000000000000000000000082526004820152fd5b81925090602091810103126106f857602061487e91016147e5565b153880614829565b3d156148b1573d9061489782614150565b916148a5604051938461412d565b82523d6000602084013e565b606090565b906148f557508051156148cb57805190602001fd5b60046040517f1425ea42000000000000000000000000000000000000000000000000000000008152fd5b81511580614940575b614906575090565b6024906001600160a01b03604051917f9996b315000000000000000000000000000000000000000000000000000000008352166004820152fd5b50803b156148fe565b60405190614956826140bd565b60006040838281528260208201520152565b60405190614975826140d9565b600182528160005b6020908181101561499f57602091614993614949565b9082850101520161497d565b505050565b8051156149b15760200190565b634e487b7160e01b600052603260045260246000fd5b80518210156149b15760209160051b010190565b9591949896979390929861ffff80941687526001600160a01b0395866020911681890152614a166101009b8c60409d8e8c01528a019061474c565b91606093848a0152608089015287820360a08901528080855193848152019401926000905b838210614a5b57505050505060e09495969750961660c085015216910152565b845180518816875280840151878501528d015167ffffffffffffffff168d8701529485019493820193600190910190614a3b565b519061ffff821682036106f857565b6040517f095ea7b300000000000000000000000000000000000000000000000000000000602082018181526001600160a01b038516602484015260448084019690965294825294939092614af360648561412d565b6001600160a01b0390818416600080809588519082855af190614b14614886565b82614b9f575b5081614b94575b5015614b31575b50505050509050565b60405196602088015216602486015280604486015260448552608085019085821067ffffffffffffffff831117614b805750614b7593946147de91604052826147f2565b803880808080614b28565b80634e487b7160e01b602492526041600452fd5b90503b151538614b21565b80519192508115918215614bb7575b50509038614b1a565b8192509060209181010312611acd576020614bd291016147e5565b3880614bae565b60405190614be6826140d9565b6060602083600081520152565b61ffff1660028103614c055750600090565b60068103614c135750600190565b60178103614c215750600390565b601803614c2d57600290565b606460405162461bcd60e51b815260206004820152601160248201527f57726f6e67204343545020446f6d61696e0000000000000000000000000000006044820152fd5b15614c7857565b606460405162461bcd60e51b815260206004820152600d60248201527f726566756e64206661696c6564000000000000000000000000000000000000006044820152fd5b81601f820112156106f8578051614cd281614150565b92614ce0604051948561412d565b818452602082840101116106f85761451f9160208085019101614729565b9190916040818403126106f857805192602082015167ffffffffffffffff81116106f85761451f9201614cbc565b908160609103126106f85780516001600160a01b03811681036106f8579161451f6040614d5b602085016147e5565b93016147e5565b614d91614d806001600160a01b039260208082518301019101614cfe565b905060208082518301019101614d2c565b9190921692565b519060ff821682036106f857565b60ff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff89116019060ff821161440457565b60ff166008039060ff821161440457565b60ff16604d811161440457600a0a90565b8181029291811591840414171561440457565b8115614e16570490565b634e487b7160e01b600052601260045260246000fd5b519063ffffffff821682036106f857565b8015614ea657600190602081108216604e8210831617614e9e578190600a925b808211614e7557505081600019048111614404570290565b90928060001904811161440457818416614e95575b800292811c90614e5d565b80920291614e8a565b9050600a0a90565b50600190565b15614eb357565b608460405162461bcd60e51b815260206004820152602260248201527f4d73672e73656e646572206973206e6f7420576f726d686f6c652052656c617960448201527f65720000000000000000000000000000000000000000000000000000000000006064820152fd5b15614f2457565b606460405162461bcd60e51b815260206004820152601560248201527f4e6f7420726567697374657265642073656e64657200000000000000000000006044820152fd5b15614f6f57565b606460405162461bcd60e51b815260206004820152601960248201527f4d65737361676520616c72656164792070726f636573736564000000000000006044820152fd5b6040517fa9059cbb0000000000000000000000000000000000000000000000000000000060208201526001600160a01b039290921660248301526044808301939093529181526147e3916147de60648361412d56fea2646970667358221220c01bb022dcabde12b63571b0b163c202062a26bed1b2436335bad59d81d8542364736f6c63430008140033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000000000000000000000000000000000000000000200000000000000000000000098f3c9e6e3face36baad05fe09d375ef1464288b0000000000000000000000003ee18b2214aff97000d974cf647e7c347e8fa58500000000000000000000000027428dd2d3dd32a4d7f7c497eaaa23130d89491100000000000000000000000000000000000000000000000000000000000000170000000000000000000000001e3f1f1ca8c62aabcb3b78d87223e988dfa3780e0000000000000000000000000a992d191deec32afe36203ad87d7d289a738f81000000000000000000000000bd3fa81b58ba92a82136038b25adec7066af3155000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48

-----Decoded View---------------
Arg [0] : chainId (uint16): 2
Arg [1] : wormhole (address): 0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B
Arg [2] : tokenBridge (address): 0x3ee18B2214AFF97000D974cf647E7C347E8fa585
Arg [3] : relayer (address): 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911
Arg [4] : hubChainId (uint16): 23
Arg [5] : hubContractAddress (address): 0x1e3f1f1cA8C62aABCB3B78D87223E988Dfa3780E
Arg [6] : _circleMessageTransmitter (address): 0x0a992d191DEeC32aFe36203Ad87D7d289a738F81
Arg [7] : _circleTokenMessenger (address): 0xBd3fa81B58Ba92a82136038B25aDec7066af3155
Arg [8] : _USDC (address): 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

-----Encoded View---------------
9 Constructor Arguments found :
Arg [0] : 0000000000000000000000000000000000000000000000000000000000000002
Arg [1] : 00000000000000000000000098f3c9e6e3face36baad05fe09d375ef1464288b
Arg [2] : 0000000000000000000000003ee18b2214aff97000d974cf647e7c347e8fa585
Arg [3] : 00000000000000000000000027428dd2d3dd32a4d7f7c497eaaa23130d894911
Arg [4] : 0000000000000000000000000000000000000000000000000000000000000017
Arg [5] : 0000000000000000000000001e3f1f1ca8c62aabcb3b78d87223e988dfa3780e
Arg [6] : 0000000000000000000000000a992d191deec32afe36203ad87d7d289a738f81
Arg [7] : 000000000000000000000000bd3fa81b58ba92a82136038b25adec7066af3155
Arg [8] : 000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]
[ 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.