ETH Price: $3,394.24 (+1.97%)

Contract

0x461a8878060Aa45Fa685732bfF654Ca1FDeF2830
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Wrap And Transfe...187756512023-12-13 7:05:35381 days ago1702451135IN
0x461a8878...1FDeF2830
0.1 ETH0.0056785239.57267569
Transfer Tokens ...186086282023-11-19 21:47:35405 days ago1700430455IN
0x461a8878...1FDeF2830
0 ETH0.0064100937.53113696
Transfer Tokens ...183864992023-10-19 19:34:59436 days ago1697744099IN
0x461a8878...1FDeF2830
0 ETH0.0022198213
Wrap And Transfe...181903012023-09-22 8:49:11463 days ago1695372551IN
0x461a8878...1FDeF2830
0.0098 ETH0.001299719.06110538
Transfer Tokens ...178225602023-08-01 20:32:23515 days ago1690921943IN
0x461a8878...1FDeF2830
0 ETH0.0044518427.05201422
Wrap And Transfe...177550322023-07-23 9:50:11524 days ago1690105811IN
0x461a8878...1FDeF2830
0.01 ETH0.0019455813.56382173
Transfer Tokens ...177205502023-07-18 13:56:47529 days ago1689688607IN
0x461a8878...1FDeF2830
0 ETH0.0050845330.88947819
Transfer Tokens ...176594822023-07-09 23:42:11538 days ago1688946131IN
0x461a8878...1FDeF2830
0 ETH0.0028989817.61489977
Transfer Tokens ...176591512023-07-09 22:35:35538 days ago1688942135IN
0x461a8878...1FDeF2830
0 ETH0.0021873312.810364
Wrap And Transfe...176064072023-07-02 12:45:11545 days ago1688301911IN
0x461a8878...1FDeF2830
0.02 ETH0.0020223714.09916985
Transfer Tokens ...175962362023-07-01 2:26:47546 days ago1688178407IN
0x461a8878...1FDeF2830
0 ETH0.0041313725.10466045
Wrap And Transfe...175958152023-07-01 1:01:35546 days ago1688173295IN
0x461a8878...1FDeF2830
0.0008 ETH0.002577717.97073588
Complete Transfe...175771062023-06-28 10:05:35549 days ago1687946735IN
0x461a8878...1FDeF2830
0 ETH0.04064039108.30448234
Wrap And Transfe...175448962023-06-23 21:18:47554 days ago1687555127IN
0x461a8878...1FDeF2830
0.015 ETH0.0040075127.93878933
Wrap And Transfe...175318562023-06-22 1:19:35555 days ago1687396775IN
0x461a8878...1FDeF2830
0.1 ETH0.0025714417.9270831
Wrap And Transfe...175299532023-06-21 18:54:47556 days ago1687373687IN
0x461a8878...1FDeF2830
0.01 ETH0.0037876226.4058321
Wrap And Transfe...175266732023-06-21 7:52:59556 days ago1687333979IN
0x461a8878...1FDeF2830
0.01 ETH0.0019839813.83158656
Wrap And Transfe...175184672023-06-20 4:14:47557 days ago1687234487IN
0x461a8878...1FDeF2830
0.002 ETH0.0019468213.57249261
Wrap And Transfe...175173272023-06-20 0:23:59557 days ago1687220639IN
0x461a8878...1FDeF2830
0.035 ETH0.0017218312
Transfer Tokens ...175162892023-06-19 20:54:47558 days ago1687208087IN
0x461a8878...1FDeF2830
0 ETH0.0025601914.9908513
Transfer Tokens ...174734582023-06-13 20:30:59564 days ago1686688259IN
0x461a8878...1FDeF2830
0 ETH0.0023930214.01500612
Wrap And Transfe...174662312023-06-12 20:06:35565 days ago1686600395IN
0x461a8878...1FDeF2830
0.007 ETH0.0028981720.20496118
Wrap And Transfe...174628952023-06-12 8:49:11565 days ago1686559751IN
0x461a8878...1FDeF2830
0.1 ETH0.0021910815.2753479
Wrap And Transfe...174628562023-06-12 8:40:59565 days ago1686559259IN
0x461a8878...1FDeF2830
0.009 ETH0.0021036714.66598133
Transfer Tokens ...174614732023-06-12 4:00:59565 days ago1686542459IN
0x461a8878...1FDeF2830
0 ETH0.0028957816.96052306
View all transactions

Latest 25 internal transactions (View All)

Advanced mode:
Parent Transaction Hash Block
From
To
187756512023-12-13 7:05:35381 days ago1702451135
0x461a8878...1FDeF2830
0.1 ETH
181903012023-09-22 8:49:11463 days ago1695372551
0x461a8878...1FDeF2830
0.0098 ETH
177550322023-07-23 9:50:11524 days ago1690105811
0x461a8878...1FDeF2830
0.01 ETH
176064072023-07-02 12:45:11545 days ago1688301911
0x461a8878...1FDeF2830
0.02 ETH
175958152023-07-01 1:01:35546 days ago1688173295
0x461a8878...1FDeF2830
0.0008 ETH
175448962023-06-23 21:18:47554 days ago1687555127
0x461a8878...1FDeF2830
0.015 ETH
175318562023-06-22 1:19:35555 days ago1687396775
0x461a8878...1FDeF2830
0.1 ETH
175299532023-06-21 18:54:47556 days ago1687373687
0x461a8878...1FDeF2830
0.01 ETH
175266732023-06-21 7:52:59556 days ago1687333979
0x461a8878...1FDeF2830
0.01 ETH
175184672023-06-20 4:14:47557 days ago1687234487
0x461a8878...1FDeF2830
0.002 ETH
175173272023-06-20 0:23:59557 days ago1687220639
0x461a8878...1FDeF2830
0.035 ETH
174662312023-06-12 20:06:35565 days ago1686600395
0x461a8878...1FDeF2830
0.007 ETH
174628952023-06-12 8:49:11565 days ago1686559751
0x461a8878...1FDeF2830
0.1 ETH
174628562023-06-12 8:40:59565 days ago1686559259
0x461a8878...1FDeF2830
0.009 ETH
174588742023-06-11 19:15:23566 days ago1686510923
0x461a8878...1FDeF2830
0.001 ETH
174586492023-06-11 18:29:47566 days ago1686508187
0x461a8878...1FDeF2830
0.7 ETH
174353682023-06-08 11:42:47569 days ago1686224567
0x461a8878...1FDeF2830
0.025 ETH
174200342023-06-06 7:48:35571 days ago1686037715
0x461a8878...1FDeF2830
0.0055 ETH
174134302023-06-05 9:27:47572 days ago1685957267
0x461a8878...1FDeF2830
0.0025 ETH
174086602023-06-04 17:16:59573 days ago1685899019
0x461a8878...1FDeF2830
0.2 ETH
174075182023-06-04 13:24:35573 days ago1685885075
0x461a8878...1FDeF2830
0.011652 ETH
174025952023-06-03 20:44:23574 days ago1685825063
0x461a8878...1FDeF2830
0.02 ETH
174011012023-06-03 15:40:11574 days ago1685806811
0x461a8878...1FDeF2830
0.05 ETH
173931902023-06-02 12:57:11575 days ago1685710631
0x461a8878...1FDeF2830
0.07 ETH
173865592023-06-01 14:29:35576 days ago1685629775
0x461a8878...1FDeF2830
0.005 ETH
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
TokenBridgeRelayer

Compiler Version
v0.8.17+commit.8df45f5f

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
File 1 of 16 : TokenBridgeRelayer.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.17;

import {IWormhole} from "../interfaces/IWormhole.sol";

import "../libraries/BytesLib.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "./TokenBridgeRelayerGovernance.sol";
import "./TokenBridgeRelayerMessages.sol";

/**
 * @title Wormhole Token Bridge Relayer
 * @notice This contract composes on Wormhole's Token Bridge contracts to faciliate
 * one-click transfers of Token Bridge supported assets cross chain.
 */
contract TokenBridgeRelayer is TokenBridgeRelayerGovernance, TokenBridgeRelayerMessages, ReentrancyGuard {
    using BytesLib for bytes;

    constructor(
        uint16 chainId,
        address wormhole,
        address tokenBridge_,
        address wethAddress,
        bool unwrapWeth_
    ) {
        require(chainId > 0, "invalid chainId");
        require(wormhole != address(0), "invalid wormhole address");
        require(tokenBridge_ != address(0), "invalid token bridge address");
        require(wethAddress != address(0), "invalid weth address");

        // set initial state
        setOwner(msg.sender);
        setChainId(chainId);
        setWormhole(wormhole);
        setTokenBridge(tokenBridge_);
        setWethAddress(wethAddress);
        setUnwrapWethFlag(unwrapWeth_);

        // set the initial swapRate/relayer precisions to 1e8
        setSwapRatePrecision(1e8);
        setRelayerFeePrecision(1e8);
    }

    /**
     * @notice Emitted when a transfer is completed by the Wormhole token bridge
     * @param emitterChainId Wormhole chain ID of emitter contract on the source chain
     * @param emitterAddress Address (bytes32 zero-left-padded) of emitter on the source chain
     * @param sequence Sequence of the Wormhole message
     */
    event TransferRedeemed(
        uint16 indexed emitterChainId,
        bytes32 indexed emitterAddress,
        uint64 indexed sequence
    );

    /**
     * @notice Emitted when a swap is executed with an off-chain relayer
     * @param recipient Address of the recipient of the native assets
     * @param relayer Address of the relayer that performed the swap
     * @param token Address of the token being swapped
     * @param tokenAmount Amount of token being swapped
     * @param nativeAmount Amount of native assets swapped for tokens
     */
    event SwapExecuted(
        address indexed recipient,
        address indexed relayer,
        address indexed token,
        uint256 tokenAmount,
        uint256 nativeAmount
    );

    /**
     * @notice Calls Wormhole's Token Bridge contract to emit a contract-controlled
     * transfer. The transfer message includes an arbitrary payload with instructions
     * for how to handle relayer payments on the target contract and the quantity of
     * tokens to convert into native assets for the user.
     * @param token ERC20 token address to transfer cross chain.
     * @param amount Quantity of tokens to be transferred.
     * @param toNativeTokenAmount Amount of tokens to swap into native assets on
     * the target chain.
     * @param targetChain Wormhole chain ID of the target blockchain.
     * @param targetRecipient User's wallet address on the target blockchain in bytes32 format
     * (zero-left-padded).
     * @param batchId ID for Wormhole message batching
     * @return messageSequence Wormhole sequence for emitted TransferTokensWithRelay message.
     */
    function transferTokensWithRelay(
        address token,
        uint256 amount,
        uint256 toNativeTokenAmount,
        uint16 targetChain,
        bytes32 targetRecipient,
        uint32 batchId
    ) public payable nonReentrant returns (uint64 messageSequence) {
        // Cache wormhole fee and confirm that the user has passed enough
        // value to cover the wormhole protocol fee.
        uint256 wormholeFee = wormhole().messageFee();
        require(msg.value == wormholeFee, "insufficient value");

        // Cache token decimals, and remove dust from the amount argument. This
        // ensures that the dust is never transferred to this contract.
        uint8 tokenDecimals = getDecimals(token);
        amount = denormalizeAmount(
            normalizeAmount(amount, tokenDecimals),
            tokenDecimals
        );

        // Transfer tokens from user to the this contract, and
        // override amount with actual amount received.
        amount = custodyTokens(token, amount);

        // call the internal _transferTokensWithRelay function
        messageSequence = _transferTokensWithRelay(
            InternalTransferParams({
                token: token,
                amount: amount,
                tokenDecimals: tokenDecimals,
                toNativeTokenAmount: toNativeTokenAmount,
                targetChain: targetChain,
                targetRecipient: targetRecipient
            }),
            batchId,
            wormholeFee
        );
    }

    /**
     * @notice Wraps Ether and calls Wormhole's Token Bridge contract to emit
     * a contract-controlled transfer. The transfer message includes an arbitrary
     * payload with instructions for how to handle relayer payments on the target
     * contract and the quantity of tokens to convert into native assets for the user.
     * @param toNativeTokenAmount Amount of tokens to swap into native assets on
     * the target chain.
     * @param targetChain Wormhole chain ID of the target blockchain.
     * @param targetRecipient User's wallet address on the target blockchain in bytes32 format
     * (zero-left-padded).
     * @param batchId ID for Wormhole message batching
     * @return messageSequence Wormhole sequence for emitted TransferTokensWithRelay message.
     */
    function wrapAndTransferEthWithRelay(
        uint256 toNativeTokenAmount,
        uint16 targetChain,
        bytes32 targetRecipient,
        uint32 batchId
    ) public payable returns (uint64 messageSequence) {
        require(unwrapWeth(), "WETH functionality not supported");

        // Cache wormhole fee and confirm that the user has passed enough
        // value to cover the wormhole protocol fee.
        uint256 wormholeFee = wormhole().messageFee();
        require(msg.value > wormholeFee, "insufficient value");

        // remove the wormhole protocol fee from the amount
        uint256 amount = msg.value - wormholeFee;

        // refund dust
        uint256 dust = amount - denormalizeAmount(normalizeAmount(amount, 18), 18);
        if (dust > 0) {
            payable(msg.sender).transfer(dust);
        }

        // remove dust from amount and cache WETH
        uint256 amountLessDust = amount - dust;
        IWETH weth = WETH();

        // deposit into the WETH contract
        weth.deposit{
            value : amountLessDust
        }();

        // call the internal _transferTokensWithRelay function
        messageSequence = _transferTokensWithRelay(
            InternalTransferParams({
                token: address(weth),
                tokenDecimals: 18,
                amount: amountLessDust,
                toNativeTokenAmount: toNativeTokenAmount,
                targetChain: targetChain,
                targetRecipient: targetRecipient
            }),
            batchId,
            wormholeFee
        );
    }

    function _transferTokensWithRelay(
        InternalTransferParams memory params,
        uint32 batchId,
        uint256 wormholeFee
    ) internal returns (uint64 messageSequence) {
        // sanity check function arguments
        require(isAcceptedToken(params.token), "token not accepted");
        require(
            params.targetRecipient != bytes32(0),
            "targetRecipient cannot be bytes32(0)"
        );

        /**
         * Cache the normalized amount and verify that it's nonzero.
         * The token bridge peforms the same operation before encoding
         * the amount in the `TransferWithPayload` message.
         */
        uint256 normalizedAmount = normalizeAmount(
            params.amount,
            params.tokenDecimals
        );
        require(normalizedAmount > 0, "normalized amount must be > 0");

        // normalized toNativeTokenAmount should be nonzero
        uint256 normalizedToNativeTokenAmount = normalizeAmount(
            params.toNativeTokenAmount,
            params.tokenDecimals
        );
        require(
            params.toNativeTokenAmount == 0 || normalizedToNativeTokenAmount > 0,
            "invalid toNativeTokenAmount"
        );

        // Cache the target contract address and verify that there
        // is a registered contract for the specified targetChain.
        bytes32 targetContract = getRegisteredContract(params.targetChain);
        require(targetContract != bytes32(0), "target not registered");

        // Confirm that the user has sent enough tokens to cover the native swap
        // on the target chain and to pay the relayer fee.
        uint256 normalizedRelayerFee = normalizeAmount(
            calculateRelayerFee(
                params.targetChain,
                params.token,
                params.tokenDecimals
            ),
            params.tokenDecimals
        );
        require(
            normalizedAmount > normalizedRelayerFee + normalizedToNativeTokenAmount,
            "insufficient amount"
        );

        /**
         * Encode instructions (TransferWithRelay) to send with the token transfer.
         * The `targetRecipient` address is in bytes32 format (zero-left-padded) to
         * support non-evm smart contracts that have addresses that are longer
         * than 20 bytes.
         *
         * We normalize the relayerFee and toNativeTokenAmount to support
         * non-evm smart contracts that can only handle uint64.max values.
         */
        bytes memory messagePayload = encodeTransferWithRelay(
            TransferWithRelay({
                payloadId: 1,
                targetRelayerFee: normalizedRelayerFee,
                toNativeTokenAmount: normalizedToNativeTokenAmount,
                targetRecipient: params.targetRecipient
            })
        );

        // cache TokenBridge instance
        ITokenBridge bridge = tokenBridge();

        // approve the token bridge to spend the specified tokens
        SafeERC20.safeApprove(
            IERC20(params.token),
            address(bridge),
            params.amount
        );

        /**
         * Call `transferTokensWithPayload` method on the token bridge and pay
         * the Wormhole network fee. The token bridge will emit a Wormhole
         * message with an encoded `TransferWithPayload` struct (see the
         * ITokenBridge.sol interface file in this repo).
         */
        messageSequence = bridge.transferTokensWithPayload{value: wormholeFee}(
            params.token,
            params.amount,
            params.targetChain,
            targetContract,
            batchId,
            messagePayload
        );
    }

    /**
     * @notice Calls Wormhole's Token Bridge contract to complete token transfers. Takes
     * custody of the wrapped (or released) tokens and sends the tokens to the target recipient.
     * It pays the relayer in the minted token denomination. If requested by the user,
     * it will perform a swap with the off-chain relayer to provide the user with native assets.
     * If the `token` being transferred is WETH, the contract will unwrap native assets and send
     * the transferred amount to the recipient and pay the relayer in native assets.
     * @dev reverts if:
     * - the transferred token is not accepted by this contract
     * - the transffered token is not attested on this blockchain's Token Bridge contract
     * - the emitter of the transfer message is not registered with this contract
     * - the relayer fails to provide enough native assets to faciliate a native swap
     * - the recipient attempts to swap native assets when performing a self redemption
     * @param encodedTransferMessage Attested `TransferWithPayload` wormhole message.
     */
    function completeTransferWithRelay(bytes calldata encodedTransferMessage) public payable {
        // complete the transfer by calling the token bridge
        (bytes memory payload, uint256 amount, address token) =
             _completeTransfer(encodedTransferMessage);

        // parse the payload into the `TransferWithRelay` struct
        TransferWithRelay memory transferWithRelay = decodeTransferWithRelay(
            payload
        );

        // cache the recipient address and unwrap weth flag
        address recipient = bytes32ToAddress(transferWithRelay.targetRecipient);
        bool unwrapWeth = unwrapWeth();

        // handle self redemptions
        if (msg.sender == recipient) {
            _completeSelfRedemption(
                token,
                recipient,
                amount,
                unwrapWeth
            );

            // bail out
            return;
        }

        // cache token decimals
        uint8 tokenDecimals = getDecimals(token);

        // denormalize the encoded relayerFee
        transferWithRelay.targetRelayerFee = denormalizeAmount(
            transferWithRelay.targetRelayerFee,
            tokenDecimals
        );

        // unwrap and transfer ETH
        if (token == address(WETH())) {
            _completeWethTransfer(
                amount,
                recipient,
                transferWithRelay.targetRelayerFee,
                unwrapWeth
            );

            // bail out
            return;
        }

        // handle native asset payments and refunds
        if (transferWithRelay.toNativeTokenAmount > 0) {
            // denormalize the toNativeTokenAmount
            transferWithRelay.toNativeTokenAmount = denormalizeAmount(
                transferWithRelay.toNativeTokenAmount,
                tokenDecimals
            );

            /**
             * Compute the maximum amount of tokens that the user is allowed
             * to swap for native assets.
             *
             * Override the toNativeTokenAmount in transferWithRelay if the
             * toNativeTokenAmount is greater than the maxToNativeAllowed.
             *
             * Compute the amount of native assets to send the recipient.
             */
            uint256 nativeAmountForRecipient;
            uint256 maxToNativeAllowed = calculateMaxSwapAmountIn(token);
            if (transferWithRelay.toNativeTokenAmount > maxToNativeAllowed) {
                transferWithRelay.toNativeTokenAmount = maxToNativeAllowed;
            }
            // compute amount of native asset to pay the recipient
            nativeAmountForRecipient = calculateNativeSwapAmountOut(
                token,
                transferWithRelay.toNativeTokenAmount
            );

            /**
             * The nativeAmountForRecipient can be zero if the user specifed
             * a toNativeTokenAmount that is too little to convert to native
             * asset. We need to override the toNativeTokenAmount to be zero
             * if that is the case, that way the user receives the full amount
             * of transferred tokens.
             */
            if (nativeAmountForRecipient > 0) {
                // check to see if the relayer sent enough value
                require(
                    msg.value >= nativeAmountForRecipient,
                    "insufficient native asset amount"
                );

                // refund excess native asset to relayer if applicable
                uint256 relayerRefund = msg.value - nativeAmountForRecipient;
                if (relayerRefund > 0) {
                    payable(msg.sender).transfer(relayerRefund);
                }

                // send requested native asset to target recipient
                payable(recipient).transfer(nativeAmountForRecipient);

                // emit swap event
                emit SwapExecuted(
                    recipient,
                    msg.sender,
                    token,
                    transferWithRelay.toNativeTokenAmount,
                    nativeAmountForRecipient
                );
            } else {
                // override the toNativeTokenAmount in transferWithRelay
                transferWithRelay.toNativeTokenAmount = 0;

                // refund the relayer any native asset sent to this contract
                if (msg.value > 0) {
                    payable(msg.sender).transfer(msg.value);
                }
            }
        }

        // add the token swap amount to the relayer fee
        uint256 amountForRelayer =
            transferWithRelay.targetRelayerFee + transferWithRelay.toNativeTokenAmount;

        // pay the relayer if amountForRelayer > 0
        if (amountForRelayer > 0) {
            SafeERC20.safeTransfer(
                IERC20(token),
                msg.sender,
                amountForRelayer
            );
        }

        // pay the target recipient the remaining tokens
        SafeERC20.safeTransfer(
            IERC20(token),
            recipient,
            amount - amountForRelayer
        );
    }

    function _completeTransfer(
        bytes memory encodedTransferMessage
    ) internal returns (bytes memory, uint256, address) {
        /**
         * parse the encoded Wormhole message
         *
         * SECURITY: This message not been verified by the Wormhole core layer yet.
         * The encoded payload can only be trusted once the message has been verified
         * by the Wormhole core contract. In this case, the message will be verified
         * by a call to the token bridge contract in subsequent actions.
         */
        IWormhole.VM memory parsedMessage = wormhole().parseVM(
            encodedTransferMessage
        );

        /**
         * The amount encoded in the payload could be incorrect,
         * since fee-on-transfer tokens are supported by the token bridge.
         *
         * NOTE: The token bridge truncates the encoded amount for any token
         * with decimals greater than 8. This is to support blockchains that
         * cannot handle transfer amounts exceeding max(uint64).
         */
        address localTokenAddress = fetchLocalAddressFromTransferMessage(
            parsedMessage.payload
        );
        require(isAcceptedToken(localTokenAddress), "token not registered");

        // check balance before completing the transfer
        uint256 balanceBefore = getBalance(localTokenAddress);

        // cache the token bridge instance
        ITokenBridge bridge = tokenBridge();

        /**
         * Call `completeTransferWithPayload` on the token bridge. This
         * method acts as a reentrancy protection since it does not allow
         * transfers to be redeemed more than once.
         */
        bytes memory transferPayload = bridge.completeTransferWithPayload(
            encodedTransferMessage
        );

        // compute and save the balance difference after completing the transfer
        uint256 amountReceived = getBalance(localTokenAddress) - balanceBefore;

        // parse the wormhole message payload into the `TransferWithPayload` struct
        ITokenBridge.TransferWithPayload memory transfer =
            bridge.parseTransferWithPayload(transferPayload);

        // confirm that the message sender is a registered TokenBridgeRelayer contract
        require(
            transfer.fromAddress == getRegisteredContract(parsedMessage.emitterChainId),
            "contract not registered"
        );

        // emit event with information about the TransferWithPayload message
        emit TransferRedeemed(
            parsedMessage.emitterChainId,
            parsedMessage.emitterAddress,
            parsedMessage.sequence
        );

        return (
            transfer.payload,
            amountReceived,
            localTokenAddress
        );
    }

    function _completeSelfRedemption(
        address token,
        address recipient,
        uint256 amount,
        bool unwrapWeth
    ) internal {
        // revert if the caller sends ether to this contract
        require(msg.value == 0, "recipient cannot swap native assets");

        // cache WETH instance
        IWETH weth = WETH();

        // transfer the full amount to the recipient
        if (token == address(weth) && unwrapWeth) {
            // withdraw weth and send to the recipient
            weth.withdraw(amount);
            payable(recipient).transfer(amount);
        } else {
            SafeERC20.safeTransfer(
                IERC20(token),
                recipient,
                amount
            );
        }
    }

    function _completeWethTransfer(
        uint256 amount,
        address recipient,
        uint256 relayerFee,
        bool unwrapWeth
    ) internal {
        // revert if the relayer sends ether to this contract
        require(msg.value == 0, "value must be zero");

        /**
         * Check if the weth is unwrappable. Some wrapped native assets
         * are not unwrappable (e.g. CELO) and must be transferred via
         * the ERC20 interface.
         */
        if (unwrapWeth) {
            // withdraw eth
            WETH().withdraw(amount);

            // transfer eth to recipient
            payable(recipient).transfer(amount - relayerFee);

            // transfer relayer fee to the caller
            if (relayerFee > 0) {
                payable(msg.sender).transfer(relayerFee);
            }
        } else {
            // cache WETH instance
            IWETH weth = WETH();

            // transfer the native asset to the caller
            SafeERC20.safeTransfer(
                IERC20(address(weth)),
                recipient,
                amount - relayerFee
            );

            // transfer relayer fee to the caller
            if (relayerFee > 0) {
                SafeERC20.safeTransfer(
                    IERC20(address(weth)),
                    msg.sender,
                    relayerFee
                );
            }
        }
    }

    /**
     * @notice Parses the encoded address and chainId from a `TransferWithPayload`
     * message. Finds the address of the wrapped token contract if the token is not
     * native to this chain.
     * @param payload Encoded `TransferWithPayload` message
     * @return localAddress Address of the encoded (bytes32 format) token address on
     * this chain.
     */
    function fetchLocalAddressFromTransferMessage(
        bytes memory payload
    ) public view returns (address localAddress) {
        // parse the source token address and chainId
        bytes32 sourceAddress = payload.toBytes32(33);
        uint16 tokenChain = payload.toUint16(65);

        // Fetch the wrapped address from the token bridge if the token
        // is not from this chain.
        if (tokenChain != chainId()) {
            // identify wormhole token bridge wrapper
            localAddress = tokenBridge().wrappedAsset(tokenChain, sourceAddress);
            require(localAddress != address(0), "token not attested");
        } else {
            // return the encoded address if the token is native to this chain
            localAddress = bytes32ToAddress(sourceAddress);
        }
    }

    /**
     * @notice Calculates the max amount of tokens the user can convert to
     * native assets on this chain.
     * @dev The max amount of native assets the contract will swap with the user
     * is governed by the `maxNativeSwapAmount` state variable.
     * @param token Address of token being transferred.
     * @return maxAllowed The maximum number of tokens the user is allowed to
     * swap for native assets.
     */
    function calculateMaxSwapAmountIn(
        address token
    ) public view returns (uint256 maxAllowed) {
        // fetch the decimals for the token and native token
        uint8 tokenDecimals = getDecimals(token);
        uint8 nativeDecimals = getDecimals(address(WETH()));

        if (tokenDecimals > nativeDecimals) {
            maxAllowed =
                maxNativeSwapAmount(token) * nativeSwapRate(token) *
                10 ** (tokenDecimals - nativeDecimals) / swapRatePrecision();
        } else {
            maxAllowed =
                (maxNativeSwapAmount(token) * nativeSwapRate(token)) /
                (10 ** (nativeDecimals - tokenDecimals) * swapRatePrecision());
        }
    }

    /**
     * @notice Calculates the amount of native assets that a user will receive
     * when swapping transferred tokens for native assets.
     * @param token Address of token being transferred.
     * @param toNativeAmount Quantity of tokens to be converted to native assets.
     * @return nativeAmount The exchange rate between native assets and the `toNativeAmount`
     * of transferred tokens.
     */
    function calculateNativeSwapAmountOut(
        address token,
        uint256 toNativeAmount
    ) public view returns (uint256 nativeAmount) {
        // fetch the decimals for the token and native token
        uint8 tokenDecimals = getDecimals(token);
        uint8 nativeDecimals = getDecimals(address(WETH()));

        if (tokenDecimals > nativeDecimals) {
            nativeAmount =
                swapRatePrecision() * toNativeAmount /
                (nativeSwapRate(token) * 10 ** (tokenDecimals - nativeDecimals));
        } else {
            nativeAmount =
                swapRatePrecision() * toNativeAmount *
                10 ** (nativeDecimals - tokenDecimals) /
                nativeSwapRate(token);
        }
    }

    /**
     * @notice Converts the USD denominated relayer fee into the specified token
     * denomination.
     * @param targetChainId Wormhole chain ID of the target blockchain.
     * @param token Address of token being transferred.
     * @param decimals Token decimals of token being transferred.
     * @return feeInTokenDenomination Relayer fee denominated in tokens.
     */
    function calculateRelayerFee(
        uint16 targetChainId,
        address token,
        uint8 decimals
    ) public view returns (uint256 feeInTokenDenomination) {
        // cache swap rate
        uint256 tokenSwapRate = swapRate(token);
        require(tokenSwapRate != 0, "swap rate not set");
        feeInTokenDenomination =
            10 ** decimals * relayerFee(targetChainId) * swapRatePrecision() /
            (tokenSwapRate * relayerFeePrecision());
    }

    function custodyTokens(
        address token,
        uint256 amount
    ) internal returns (uint256) {
        // query own token balance before transfer
        uint256 balanceBefore = getBalance(token);

        // deposit tokens
        SafeERC20.safeTransferFrom(
            IERC20(token),
            msg.sender,
            address(this),
            amount
        );

        // return the balance difference
        return getBalance(token) - balanceBefore;
    }

    function bytes32ToAddress(bytes32 address_) internal pure returns (address) {
        require(bytes12(address_) == 0, "invalid EVM address");
        return address(uint160(uint256(address_)));
    }

    // necessary for receiving native assets
    receive() external payable {}
}

File 2 of 16 : ReentrancyGuard.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

File 3 of 16 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @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 amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` 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 amount
    ) external returns (bool);
}

File 4 of 16 : draft-IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @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.
 */
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].
     */
    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 5 of 16 : SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../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;

    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @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, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

File 6 of 16 : Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @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://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @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, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * 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.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @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`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

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

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

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

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) 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(errorMessage);
        }
    }
}

File 7 of 16 : ITokenBridge.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 8 of 16 : IWETH.sol
// contracts/Bridge.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;

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

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

File 9 of 16 : 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;
    }

    event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel);

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

    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 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 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);
}

File 10 of 16 : BytesLib.sol
// SPDX-License-Identifier: Unlicense
/*
 * @title Solidity Bytes Arrays Utils
 * @author Gonçalo Sá <[email protected]>
 *
 * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
 *      The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.
 */
pragma solidity >=0.8.0 <0.9.0;


library BytesLib {
    function concat(
        bytes memory _preBytes,
        bytes memory _postBytes
    )
        internal
        pure
        returns (bytes memory)
    {
        bytes memory tempBytes;

        assembly {
            // Get a location of some free memory and store it in tempBytes as
            // Solidity does for memory variables.
            tempBytes := mload(0x40)

            // Store the length of the first bytes array at the beginning of
            // the memory for tempBytes.
            let length := mload(_preBytes)
            mstore(tempBytes, length)

            // Maintain a memory counter for the current write location in the
            // temp bytes array by adding the 32 bytes for the array length to
            // the starting location.
            let mc := add(tempBytes, 0x20)
            // Stop copying when the memory counter reaches the length of the
            // first bytes array.
            let end := add(mc, length)

            for {
                // Initialize a copy counter to the start of the _preBytes data,
                // 32 bytes into its memory.
                let cc := add(_preBytes, 0x20)
            } lt(mc, end) {
                // Increase both counters by 32 bytes each iteration.
                mc := add(mc, 0x20)
                cc := add(cc, 0x20)
            } {
                // Write the _preBytes data into the tempBytes memory 32 bytes
                // at a time.
                mstore(mc, mload(cc))
            }

            // Add the length of _postBytes to the current length of tempBytes
            // and store it as the new length in the first 32 bytes of the
            // tempBytes memory.
            length := mload(_postBytes)
            mstore(tempBytes, add(length, mload(tempBytes)))

            // Move the memory counter back from a multiple of 0x20 to the
            // actual end of the _preBytes data.
            mc := end
            // Stop copying when the memory counter reaches the new combined
            // length of the arrays.
            end := add(mc, length)

            for {
                let cc := add(_postBytes, 0x20)
            } lt(mc, end) {
                mc := add(mc, 0x20)
                cc := add(cc, 0x20)
            } {
                mstore(mc, mload(cc))
            }

            // Update the free-memory pointer by padding our last write location
            // to 32 bytes: add 31 bytes to the end of tempBytes to move to the
            // next 32 byte block, then round down to the nearest multiple of
            // 32. If the sum of the length of the two arrays is zero then add
            // one before rounding down to leave a blank 32 bytes (the length block with 0).
            mstore(0x40, and(
              add(add(end, iszero(add(length, mload(_preBytes)))), 31),
              not(31) // Round down to the nearest 32 bytes.
            ))
        }

        return tempBytes;
    }

    function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {
        assembly {
            // Read the first 32 bytes of _preBytes storage, which is the length
            // of the array. (We don't need to use the offset into the slot
            // because arrays use the entire slot.)
            let fslot := sload(_preBytes.slot)
            // Arrays of 31 bytes or less have an even value in their slot,
            // while longer arrays have an odd value. The actual length is
            // the slot divided by two for odd values, and the lowest order
            // byte divided by two for even values.
            // If the slot is even, bitwise and the slot with 255 and divide by
            // two to get the length. If the slot is odd, bitwise and the slot
            // with -1 and divide by two.
            let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
            let mlength := mload(_postBytes)
            let newlength := add(slength, mlength)
            // slength can contain both the length and contents of the array
            // if length < 32 bytes so let's prepare for that
            // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
            switch add(lt(slength, 32), lt(newlength, 32))
            case 2 {
                // Since the new array still fits in the slot, we just need to
                // update the contents of the slot.
                // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length
                sstore(
                    _preBytes.slot,
                    // all the modifications to the slot are inside this
                    // next block
                    add(
                        // we can just add to the slot contents because the
                        // bytes we want to change are the LSBs
                        fslot,
                        add(
                            mul(
                                div(
                                    // load the bytes from memory
                                    mload(add(_postBytes, 0x20)),
                                    // zero all bytes to the right
                                    exp(0x100, sub(32, mlength))
                                ),
                                // and now shift left the number of bytes to
                                // leave space for the length in the slot
                                exp(0x100, sub(32, newlength))
                            ),
                            // increase length by the double of the memory
                            // bytes length
                            mul(mlength, 2)
                        )
                    )
                )
            }
            case 1 {
                // The stored value fits in the slot, but the combined value
                // will exceed it.
                // get the keccak hash to get the contents of the array
                mstore(0x0, _preBytes.slot)
                let sc := add(keccak256(0x0, 0x20), div(slength, 32))

                // save new length
                sstore(_preBytes.slot, add(mul(newlength, 2), 1))

                // The contents of the _postBytes array start 32 bytes into
                // the structure. Our first read should obtain the `submod`
                // bytes that can fit into the unused space in the last word
                // of the stored array. To get this, we read 32 bytes starting
                // from `submod`, so the data we read overlaps with the array
                // contents by `submod` bytes. Masking the lowest-order
                // `submod` bytes allows us to add that value directly to the
                // stored value.

                let submod := sub(32, slength)
                let mc := add(_postBytes, submod)
                let end := add(_postBytes, mlength)
                let mask := sub(exp(0x100, submod), 1)

                sstore(
                    sc,
                    add(
                        and(
                            fslot,
                            0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00
                        ),
                        and(mload(mc), mask)
                    )
                )

                for {
                    mc := add(mc, 0x20)
                    sc := add(sc, 1)
                } lt(mc, end) {
                    sc := add(sc, 1)
                    mc := add(mc, 0x20)
                } {
                    sstore(sc, mload(mc))
                }

                mask := exp(0x100, sub(mc, end))

                sstore(sc, mul(div(mload(mc), mask), mask))
            }
            default {
                // get the keccak hash to get the contents of the array
                mstore(0x0, _preBytes.slot)
                // Start copying to the last used word of the stored array.
                let sc := add(keccak256(0x0, 0x20), div(slength, 32))

                // save new length
                sstore(_preBytes.slot, add(mul(newlength, 2), 1))

                // Copy over the first `submod` bytes of the new data as in
                // case 1 above.
                let slengthmod := mod(slength, 32)
                let mlengthmod := mod(mlength, 32)
                let submod := sub(32, slengthmod)
                let mc := add(_postBytes, submod)
                let end := add(_postBytes, mlength)
                let mask := sub(exp(0x100, submod), 1)

                sstore(sc, add(sload(sc), and(mload(mc), mask)))

                for {
                    sc := add(sc, 1)
                    mc := add(mc, 0x20)
                } lt(mc, end) {
                    sc := add(sc, 1)
                    mc := add(mc, 0x20)
                } {
                    sstore(sc, mload(mc))
                }

                mask := exp(0x100, sub(mc, end))

                sstore(sc, mul(div(mload(mc), mask), mask))
            }
        }
    }

    function slice(
        bytes memory _bytes,
        uint256 _start,
        uint256 _length
    )
        internal
        pure
        returns (bytes memory)
    {
        require(_length + 31 >= _length, "slice_overflow");
        require(_bytes.length >= _start + _length, "slice_outOfBounds");

        bytes memory tempBytes;

        assembly {
            switch iszero(_length)
            case 0 {
                // Get a location of some free memory and store it in tempBytes as
                // Solidity does for memory variables.
                tempBytes := mload(0x40)

                // The first word of the slice result is potentially a partial
                // word read from the original array. To read it, we calculate
                // the length of that partial word and start copying that many
                // bytes into the array. The first word we copy will start with
                // data we don't care about, but the last `lengthmod` bytes will
                // land at the beginning of the contents of the new array. When
                // we're done copying, we overwrite the full first word with
                // the actual length of the slice.
                let lengthmod := and(_length, 31)

                // The multiplication in the next line is necessary
                // because when slicing multiples of 32 bytes (lengthmod == 0)
                // the following copy loop was copying the origin's length
                // and then ending prematurely not copying everything it should.
                let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
                let end := add(mc, _length)

                for {
                    // The multiplication in the next line has the same exact purpose
                    // as the one above.
                    let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
                } lt(mc, end) {
                    mc := add(mc, 0x20)
                    cc := add(cc, 0x20)
                } {
                    mstore(mc, mload(cc))
                }

                mstore(tempBytes, _length)

                //update free-memory pointer
                //allocating the array padded to 32 bytes like the compiler does now
                mstore(0x40, and(add(mc, 31), not(31)))
            }
            //if we want a zero-length slice let's just return a zero-length array
            default {
                tempBytes := mload(0x40)
                //zero out the 32 bytes slice we are about to return
                //we need to do it because Solidity does not garbage collect
                mstore(tempBytes, 0)

                mstore(0x40, add(tempBytes, 0x20))
            }
        }

        return tempBytes;
    }

    function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
        require(_bytes.length >= _start + 20, "toAddress_outOfBounds");
        address tempAddress;

        assembly {
            tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
        }

        return tempAddress;
    }

    function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
        require(_bytes.length >= _start + 1 , "toUint8_outOfBounds");
        uint8 tempUint;

        assembly {
            tempUint := mload(add(add(_bytes, 0x1), _start))
        }

        return tempUint;
    }

    function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) {
        require(_bytes.length >= _start + 2, "toUint16_outOfBounds");
        uint16 tempUint;

        assembly {
            tempUint := mload(add(add(_bytes, 0x2), _start))
        }

        return tempUint;
    }

    function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) {
        require(_bytes.length >= _start + 4, "toUint32_outOfBounds");
        uint32 tempUint;

        assembly {
            tempUint := mload(add(add(_bytes, 0x4), _start))
        }

        return tempUint;
    }

    function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) {
        require(_bytes.length >= _start + 8, "toUint64_outOfBounds");
        uint64 tempUint;

        assembly {
            tempUint := mload(add(add(_bytes, 0x8), _start))
        }

        return tempUint;
    }

    function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) {
        require(_bytes.length >= _start + 12, "toUint96_outOfBounds");
        uint96 tempUint;

        assembly {
            tempUint := mload(add(add(_bytes, 0xc), _start))
        }

        return tempUint;
    }

    function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) {
        require(_bytes.length >= _start + 16, "toUint128_outOfBounds");
        uint128 tempUint;

        assembly {
            tempUint := mload(add(add(_bytes, 0x10), _start))
        }

        return tempUint;
    }

    function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) {
        require(_bytes.length >= _start + 32, "toUint256_outOfBounds");
        uint256 tempUint;

        assembly {
            tempUint := mload(add(add(_bytes, 0x20), _start))
        }

        return tempUint;
    }

    function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) {
        require(_bytes.length >= _start + 32, "toBytes32_outOfBounds");
        bytes32 tempBytes32;

        assembly {
            tempBytes32 := mload(add(add(_bytes, 0x20), _start))
        }

        return tempBytes32;
    }

    function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {
        bool success = true;

        assembly {
            let length := mload(_preBytes)

            // if lengths don't match the arrays are not equal
            switch eq(length, mload(_postBytes))
            case 1 {
                // cb is a circuit breaker in the for loop since there's
                //  no said feature for inline assembly loops
                // cb = 1 - don't breaker
                // cb = 0 - break
                let cb := 1

                let mc := add(_preBytes, 0x20)
                let end := add(mc, length)

                for {
                    let cc := add(_postBytes, 0x20)
                // the next line is the loop condition:
                // while(uint256(mc < end) + cb == 2)
                } eq(add(lt(mc, end), cb), 2) {
                    mc := add(mc, 0x20)
                    cc := add(cc, 0x20)
                } {
                    // if any of these checks fails then arrays are not equal
                    if iszero(eq(mload(mc), mload(cc))) {
                        // unsuccess:
                        success := 0
                        cb := 0
                    }
                }
            }
            default {
                // unsuccess:
                success := 0
            }
        }

        return success;
    }

    function equalStorage(
        bytes storage _preBytes,
        bytes memory _postBytes
    )
        internal
        view
        returns (bool)
    {
        bool success = true;

        assembly {
            // we know _preBytes_offset is 0
            let fslot := sload(_preBytes.slot)
            // Decode the length of the stored array like in concatStorage().
            let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
            let mlength := mload(_postBytes)

            // if lengths don't match the arrays are not equal
            switch eq(slength, mlength)
            case 1 {
                // slength can contain both the length and contents of the array
                // if length < 32 bytes so let's prepare for that
                // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
                if iszero(iszero(slength)) {
                    switch lt(slength, 32)
                    case 1 {
                        // blank the last byte which is the length
                        fslot := mul(div(fslot, 0x100), 0x100)

                        if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
                            // unsuccess:
                            success := 0
                        }
                    }
                    default {
                        // cb is a circuit breaker in the for loop since there's
                        //  no said feature for inline assembly loops
                        // cb = 1 - don't breaker
                        // cb = 0 - break
                        let cb := 1

                        // get the keccak hash to get the contents of the array
                        mstore(0x0, _preBytes.slot)
                        let sc := keccak256(0x0, 0x20)

                        let mc := add(_postBytes, 0x20)
                        let end := add(mc, mlength)

                        // the next line is the loop condition:
                        // while(uint256(mc < end) + cb == 2)
                        for {} eq(add(lt(mc, end), cb), 2) {
                            sc := add(sc, 1)
                            mc := add(mc, 0x20)
                        } {
                            if iszero(eq(sload(sc), mload(mc))) {
                                // unsuccess:
                                success := 0
                                cb := 0
                            }
                        }
                    }
                }
            }
            default {
                // unsuccess:
                success := 0
            }
        }

        return success;
    }
}

File 11 of 16 : TokenBridgeRelayerGetters.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.17;

import {IWormhole} from "../interfaces/IWormhole.sol";
import {ITokenBridge} from "../interfaces/ITokenBridge.sol";
import {IWETH} from "../interfaces/IWETH.sol";

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

import "./TokenBridgeRelayerSetters.sol";

abstract contract TokenBridgeRelayerGetters is TokenBridgeRelayerSetters {
    function owner() public view returns (address) {
        return _state.owner;
    }

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

    function wormhole() public view returns (IWormhole) {
        return IWormhole(_state.wormhole);
    }

    function tokenBridge() public view returns (ITokenBridge) {
        return ITokenBridge(payable(_state.tokenBridge));
    }

    function WETH() public view returns (IWETH) {
        return IWETH(_state.wethAddress);
    }

    function unwrapWeth() public view returns (bool) {
        return _state.unwrapWeth;
    }

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

    function getRegisteredContract(uint16 emitterChainId) public view returns (bytes32) {
        return _state.registeredContracts[emitterChainId];
    }

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

    function isAcceptedToken(address token) public view returns (bool) {
        return _state.acceptedTokens[token];
    }

    function getAcceptedTokensList() public view returns (address[] memory) {
        return _state.acceptedTokensList;
    }

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

    function relayerFee(uint16 chainId_) public view returns (uint256) {
        return _state.relayerFees[chainId_];
    }

    function maxNativeSwapAmount(address token) public view returns (uint256) {
        return _state.maxNativeSwapAmount[token];
    }

    function swapRate(address token) public view returns (uint256) {
        return _state.swapRates[token];
    }

    function nativeSwapRate(address token) public view returns (uint256) {
        uint256 nativeSwapRate_ = swapRate(_state.wethAddress);
        uint256 tokenSwapRate = swapRate(token);

        require(
            nativeSwapRate_ > 0 && tokenSwapRate > 0,
            "swap rate not set"
        );

        return swapRatePrecision() * nativeSwapRate_ / tokenSwapRate;
    }

    function normalizeAmount(
        uint256 amount,
        uint8 decimals
    ) public pure returns (uint256) {
        if (decimals > 8) {
            amount /= 10 ** (decimals - 8);
        }
        return amount;
    }

    function denormalizeAmount(
        uint256 amount,
        uint8 decimals
    ) public pure returns (uint256) {
        if (decimals > 8) {
            amount *= 10 ** (decimals - 8);
        }
        return amount;
    }

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

    function getBalance(address token) internal view returns (uint256 balance) {
        // fetch the specified token balance for this contract
        (, bytes memory queriedBalance) =
            token.staticcall(
                abi.encodeWithSelector(IERC20.balanceOf.selector, address(this))
            );
        balance = abi.decode(queriedBalance, (uint256));
    }
}

File 12 of 16 : TokenBridgeRelayerGovernance.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.17;

import {IWormhole} from "../interfaces/IWormhole.sol";

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

import "./TokenBridgeRelayerGetters.sol";

abstract contract TokenBridgeRelayerGovernance is TokenBridgeRelayerGetters {
    event OwnershipTransfered(address indexed oldOwner, address indexed newOwner);
    event SwapRateUpdated(address indexed token, uint256 indexed swapRate);

    /**
     * @notice Starts the ownership transfer process of the contracts. It saves
     * an address in the pending owner state variable.
     * @param chainId_ Wormhole chain ID.
     * @param newOwner Address of the pending owner.
     */
    function submitOwnershipTransferRequest(
        uint16 chainId_,
        address newOwner
    ) public onlyOwner onlyCurrentChain(chainId_) {
        require(newOwner != address(0), "newOwner cannot equal address(0)");

        setPendingOwner(newOwner);
    }

    /**
     * @notice Cancels the ownership transfer process.
     * @dev Sets the pending owner state variable to the zero address.
     */
    function cancelOwnershipTransferRequest(
        uint16 chainId_
    ) public onlyOwner onlyCurrentChain(chainId_) {
        setPendingOwner(address(0));
    }

    /**
     * @notice Finalizes the ownership transfer to the pending owner.
     * @dev It checks that the caller is the pendingOwner to validate the wallet
     * address. It updates the owner state variable with the pendingOwner state
     * variable.
     */
    function confirmOwnershipTransferRequest() public {
        // cache the new owner address
        address newOwner = pendingOwner();

        require(msg.sender == newOwner, "caller must be pendingOwner");

        // cache currentOwner for Event
        address currentOwner = owner();

        // update the owner in the contract state and reset the pending owner
        setOwner(newOwner);
        setPendingOwner(address(0));

        emit OwnershipTransfered(currentOwner, newOwner);
    }

    /**
     * @notice Updates the unwrapWeth state variable.
     * @dev This variable should only be set to true for chains that
     * support a WETH contract. Some chains (e.g. Celo, Karura, Acala)
     * do not support a WETH contract, and the address is set as a placeholder
     * for the native asset address for swapRate lookups.
     * @param chainId_ Wormhole chain ID.
     * @param unwrapWeth_ Boolean that determines if WETH is unwrapped
     * when transferred back to its native blockchain.
     */
    function updateUnwrapWethFlag(
        uint16 chainId_,
        bool unwrapWeth_
    ) public onlyOwner onlyCurrentChain(chainId_) {
        setUnwrapWethFlag(unwrapWeth_);
    }

    /**
     * @notice Registers foreign Token Bridge Relayer contracts.
     * @param chainId_ Wormhole chain ID of the foreign contract.
     * @param contractAddress Address of the foreign contract in bytes32 format
     * (zero-left-padded address).
     */
    function registerContract(
        uint16 chainId_,
        bytes32 contractAddress
    ) public onlyOwner {
        // sanity check both input arguments
        require(
            contractAddress != bytes32(0),
            "contractAddress cannot equal bytes32(0)"
        );
        require(
            chainId_ != 0 && chainId_ != chainId(),
            "chainId_ cannot equal 0 or this chainId"
        );

        // update the registeredContracts state variable
        _registerContract(chainId_, contractAddress);
    }

    /**
     * @notice Register tokens accepted by this contract.
     * @param chainId_ Wormhole chain ID.
     * @param token Address of the token.
     */
    function registerToken(
        uint16 chainId_,
        address token
    ) public onlyOwner onlyCurrentChain(chainId_) {
        require(token != address(0), "invalid token");

        addAcceptedToken(token);
    }

    /**
     * @notice Deregister tokens accepted by this contract.
     * @dev The `removeAcceptedToken` function will revert
     * if the token is not registered.
     * @param chainId_ Wormhole chain ID.
     * @param token Address of the token.
     */
    function deregisterToken(
        uint16 chainId_,
        address token
    ) public onlyOwner onlyCurrentChain(chainId_) {
        require(token != address(0), "invalid token");

        removeAcceptedToken(token);
    }

    /**
     * @notice Updates the fee for relaying transfers to foreign contracts.
     * @param chainId_ Wormhole chain ID.
     * @param amount Amount of USD to pay the relayer upon redemption.
     * @dev The relayerFee is scaled by the relayerFeePrecision. For example,
     * if the relayerFee is $15 and the relayerFeePrecision is 1000000, the
     * relayerFee should be set to 15000000.
     */
    function updateRelayerFee(
        uint16 chainId_,
        uint256 amount
    ) public onlyOwner {
        require(chainId_ != chainId(), "invalid chain");
        require(
            getRegisteredContract(chainId_) != bytes32(0),
            "contract doesn't exist"
        );

        setRelayerFee(chainId_, amount);
    }

    /**
     * @notice Updates the precision of the relayer fee.
     * @param chainId_ Wormhole chain ID.
     * @param relayerFeePrecision_ Precision of relayer fee.
     */
    function updateRelayerFeePrecision(
        uint16 chainId_,
        uint256 relayerFeePrecision_
    ) public onlyOwner onlyCurrentChain(chainId_) {
        require(relayerFeePrecision_ > 0, "precision must be > 0");

        setRelayerFeePrecision(relayerFeePrecision_);
    }

    /**
     * @notice Updates the swap rate for specified token in USD.
     * @param chainId_ Wormhole chain ID.
     * @param token Address of the token to update the conversion rate for.
     * @param swapRate The token -> USD conversion rate.
     * @dev The swapRate is the conversion rate using asset prices denominated in
     * USD multiplied by the swapRatePrecision. For example, if the conversion
     * rate is $15 and the swapRatePrecision is 1000000, the swapRate should be set
     * to 15000000.
     */
    function updateSwapRate(
        uint16 chainId_,
        address token,
        uint256 swapRate
    ) public onlyOwner onlyCurrentChain(chainId_) {
        require(isAcceptedToken(token), "token not accepted");
        require(swapRate > 0, "swap rate must be nonzero");

        setSwapRate(token, swapRate);

        emit SwapRateUpdated(token, swapRate);
    }

    /**
     * @notice Updates the precision of the swap rate.
     * @param chainId_ Wormhole chain ID.
     * @param swapRatePrecision_ Precision of swap rate.
     */
    function updateSwapRatePrecision(
        uint16 chainId_,
        uint256 swapRatePrecision_
    ) public onlyOwner onlyCurrentChain(chainId_) {
        require(swapRatePrecision_ > 0, "precision must be > 0");

        setSwapRatePrecision(swapRatePrecision_);
    }

    /**
     * @notice Updates the max amount of native assets the contract will pay
     * to the target recipient.
     * @param chainId_ Wormhole chain ID.
     * @param token Address of the token to update the max native swap amount for.
     * @param maxAmount Max amount of native assets.
     */
    function updateMaxNativeSwapAmount(
        uint16 chainId_,
        address token,
        uint256 maxAmount
    ) public onlyOwner onlyCurrentChain(chainId_) {
        require(isAcceptedToken(token), "token not accepted");

        setMaxNativeSwapAmount(token, maxAmount);
    }

    modifier onlyOwner() {
        require(owner() == msg.sender, "caller not the owner");
        _;
    }

    modifier onlyCurrentChain(uint16 chainId_) {
        require(chainId() == chainId_, "wrong chain");
        _;
    }
}

File 13 of 16 : TokenBridgeRelayerMessages.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.17;

import "../libraries/BytesLib.sol";

import "./TokenBridgeRelayerStructs.sol";

abstract contract TokenBridgeRelayerMessages is TokenBridgeRelayerStructs {
    using BytesLib for bytes;

    /**
     * @notice Encodes the TransferWithRelay struct into bytes.
     * @param transfer TransferWithRelay struct.
     * @return encoded TransferWithRelay struct encoded into bytes.
     */
    function encodeTransferWithRelay(
        TransferWithRelay memory transfer
    ) public pure returns (bytes memory encoded) {
       require(transfer.payloadId == 1, "invalid payloadId");
        encoded = abi.encodePacked(
            transfer.payloadId,
            transfer.targetRelayerFee,
            transfer.toNativeTokenAmount,
            transfer.targetRecipient
        );
    }

    /**
     * @notice Decodes an encoded `TransferWithRelay` struct.
     * @dev reverts if:
     * - the first byte (payloadId) does not equal 1
     * - the length of the payload has an unexpected length
     * @param encoded Encoded `TransferWithRelay` struct.
     * @return transfer `TransferTokenRelay` struct.
     */
    function decodeTransferWithRelay(
        bytes memory encoded
    ) public pure returns (TransferWithRelay memory transfer) {
        uint256 index = 0;

        // parse the payloadId
        transfer.payloadId = encoded.toUint8(index);
        index += 1;

        require(transfer.payloadId == 1, "invalid payloadId");

        // target relayer fee
        transfer.targetRelayerFee = encoded.toUint256(index);
        index += 32;

        // amount of tokens to convert to native assets
        transfer.toNativeTokenAmount = encoded.toUint256(index);
        index += 32;

        // recipient of the transfered tokens and native assets
        transfer.targetRecipient = encoded.toBytes32(index);
        index += 32;

        require(index == encoded.length, "invalid message length");
    }
}

File 14 of 16 : TokenBridgeRelayerSetters.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.17;

import "./TokenBridgeRelayerState.sol";

abstract contract TokenBridgeRelayerSetters is TokenBridgeRelayerState {
    function setOwner(address owner_) internal {
        _state.owner = owner_;
    }

    function setPendingOwner(address pendingOwner_) internal {
        _state.pendingOwner = pendingOwner_;
    }

    function setWormhole(address wormhole_) internal {
        _state.wormhole = payable(wormhole_);
    }

    function setTokenBridge(address tokenBridge_) internal {
        _state.tokenBridge = payable(tokenBridge_);
    }

    function setUnwrapWethFlag(bool unwrapWeth_) internal {
        _state.unwrapWeth = unwrapWeth_;
    }

    function setWethAddress(address weth_) internal {
        _state.wethAddress = weth_;
    }

    function setChainId(uint16 chainId_) internal {
        _state.chainId = chainId_;
    }

    function _registerContract(uint16 chainId_, bytes32 contract_) internal {
        _state.registeredContracts[chainId_] = contract_;
    }

    function setSwapRatePrecision(uint256 precision) internal {
        _state.swapRatePrecision = precision;
    }

    function setRelayerFeePrecision(uint256 precision) internal {
        _state.relayerFeePrecision = precision;
    }

    function addAcceptedToken(address token) internal {
        require(
            _state.acceptedTokens[token] == false,
            "token already registered"
        );
        _state.acceptedTokens[token] = true;
        _state.acceptedTokensList.push(token);
    }

    function removeAcceptedToken(address token) internal {
        require(
            _state.acceptedTokens[token],
            "token not registered"
        );

        // Remove the token from the acceptedTokens mapping, and
        // clear the token's swapRate and maxNativeSwapAmount.
        _state.acceptedTokens[token] = false;
        _state.swapRates[token] = 0;
        _state.maxNativeSwapAmount[token] = 0;

        // cache array length
        uint256 length_ = _state.acceptedTokensList.length;

        // Replace `token` in the acceptedTokensList with the last
        // element in the acceptedTokensList array.
        uint256 i = 0;
        for (; i < length_;) {
            if (_state.acceptedTokensList[i] == token) {
                break;
            }
            unchecked { i += 1; }
        }

        if (i != length_) {
            if (length_ > 1) {
                _state.acceptedTokensList[i] = _state.acceptedTokensList[length_ - 1];
            }
            _state.acceptedTokensList.pop();
        }
    }

    function setRelayerFee(uint16 chainId_, uint256 fee) internal {
        _state.relayerFees[chainId_] = fee;
    }

    function setSwapRate(address token, uint256 swapRate) internal {
        _state.swapRates[token] = swapRate;
    }

    function setMaxNativeSwapAmount(address token, uint256 maximum) internal {
        _state.maxNativeSwapAmount[token] = maximum;
    }
}

File 15 of 16 : TokenBridgeRelayerState.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.17;

import {IWormhole} from "../interfaces/IWormhole.sol";

abstract contract TokenBridgeRelayerStorage {
    struct State {
        // Wormhole chain ID of this contract
        uint16 chainId;

        // boolean to determine if weth is unwrappable
        bool unwrapWeth;

        // address of WETH on this chain
        address wethAddress;

        // owner of this contract
        address owner;

        // intermediate state when transfering contract ownership
        address pendingOwner;

        // address of the Wormhole contract on this chain
        address wormhole;

        // address of the Wormhole TokenBridge contract on this chain
        address tokenBridge;

        // precision of the nativeSwapRates, this value should NEVER be set to zero
        uint256 swapRatePrecision;

        // precision of the relayerFee, this value should NEVER be set to zero
        uint256 relayerFeePrecision;

        // Wormhole chain ID to known relayer contract address mapping
        mapping(uint16 => bytes32) registeredContracts;

        // token swap rate in USD terms
        mapping(address => uint256) swapRates;

        /**
         * Mapping of source token address to maximum native asset swap amount
         * allowed.
         */
        mapping(address => uint256) maxNativeSwapAmount;

        // mapping of chainId to relayerFee in USD
        mapping(uint16 => uint256) relayerFees;

        // accepted token to bool mapping
        mapping(address => bool) acceptedTokens;

        // list of accepted token addresses
        address[] acceptedTokensList;
    }
}

abstract contract TokenBridgeRelayerState {
    TokenBridgeRelayerStorage.State _state;
}

File 16 of 16 : TokenBridgeRelayerStructs.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.17;

abstract contract TokenBridgeRelayerStructs {
    struct TransferWithRelay {
        uint8 payloadId; // == 1
        uint256 targetRelayerFee;
        uint256 toNativeTokenAmount;
        bytes32 targetRecipient;
    }

    struct InternalTransferParams {
        address token;
        uint8 tokenDecimals;
        uint256 amount;
        uint256 toNativeTokenAmount;
        uint16 targetChain;
        bytes32 targetRecipient;
    }
}

Settings
{
  "remappings": [
    "@openzeppelin/=node_modules/@openzeppelin/",
    "@solidity-parser/=node_modules/@solidity-parser/",
    "ds-test/=lib/forge-std/lib/ds-test/src/",
    "forge-std/=lib/forge-std/src/",
    "wormhole-solidity/=modules/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "london",
  "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":"wethAddress","type":"address"},{"internalType":"bool","name":"unwrapWeth_","type":"bool"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransfered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":true,"internalType":"address","name":"relayer","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"nativeAmount","type":"uint256"}],"name":"SwapExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"swapRate","type":"uint256"}],"name":"SwapRateUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint16","name":"emitterChainId","type":"uint16"},{"indexed":true,"internalType":"bytes32","name":"emitterAddress","type":"bytes32"},{"indexed":true,"internalType":"uint64","name":"sequence","type":"uint64"}],"name":"TransferRedeemed","type":"event"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"contract IWETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"calculateMaxSwapAmountIn","outputs":[{"internalType":"uint256","name":"maxAllowed","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"toNativeAmount","type":"uint256"}],"name":"calculateNativeSwapAmountOut","outputs":[{"internalType":"uint256","name":"nativeAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"targetChainId","type":"uint16"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"calculateRelayerFee","outputs":[{"internalType":"uint256","name":"feeInTokenDenomination","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"chainId_","type":"uint16"}],"name":"cancelOwnershipTransferRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"chainId","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"encodedTransferMessage","type":"bytes"}],"name":"completeTransferWithRelay","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"confirmOwnershipTransferRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"encoded","type":"bytes"}],"name":"decodeTransferWithRelay","outputs":[{"components":[{"internalType":"uint8","name":"payloadId","type":"uint8"},{"internalType":"uint256","name":"targetRelayerFee","type":"uint256"},{"internalType":"uint256","name":"toNativeTokenAmount","type":"uint256"},{"internalType":"bytes32","name":"targetRecipient","type":"bytes32"}],"internalType":"struct TokenBridgeRelayerStructs.TransferWithRelay","name":"transfer","type":"tuple"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"denormalizeAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint16","name":"chainId_","type":"uint16"},{"internalType":"address","name":"token","type":"address"}],"name":"deregisterToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"payloadId","type":"uint8"},{"internalType":"uint256","name":"targetRelayerFee","type":"uint256"},{"internalType":"uint256","name":"toNativeTokenAmount","type":"uint256"},{"internalType":"bytes32","name":"targetRecipient","type":"bytes32"}],"internalType":"struct TokenBridgeRelayerStructs.TransferWithRelay","name":"transfer","type":"tuple"}],"name":"encodeTransferWithRelay","outputs":[{"internalType":"bytes","name":"encoded","type":"bytes"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"fetchLocalAddressFromTransferMessage","outputs":[{"internalType":"address","name":"localAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAcceptedTokensList","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"emitterChainId","type":"uint16"}],"name":"getRegisteredContract","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"isAcceptedToken","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"maxNativeSwapAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"nativeSwapRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"normalizeAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"chainId_","type":"uint16"},{"internalType":"bytes32","name":"contractAddress","type":"bytes32"}],"name":"registerContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"chainId_","type":"uint16"},{"internalType":"address","name":"token","type":"address"}],"name":"registerToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"chainId_","type":"uint16"}],"name":"relayerFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"relayerFeePrecision","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"chainId_","type":"uint16"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"submitOwnershipTransferRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"swapRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"swapRatePrecision","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenBridge","outputs":[{"internalType":"contract ITokenBridge","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"toNativeTokenAmount","type":"uint256"},{"internalType":"uint16","name":"targetChain","type":"uint16"},{"internalType":"bytes32","name":"targetRecipient","type":"bytes32"},{"internalType":"uint32","name":"batchId","type":"uint32"}],"name":"transferTokensWithRelay","outputs":[{"internalType":"uint64","name":"messageSequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"unwrapWeth","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"chainId_","type":"uint16"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"maxAmount","type":"uint256"}],"name":"updateMaxNativeSwapAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"chainId_","type":"uint16"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"updateRelayerFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"chainId_","type":"uint16"},{"internalType":"uint256","name":"relayerFeePrecision_","type":"uint256"}],"name":"updateRelayerFeePrecision","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"chainId_","type":"uint16"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"swapRate","type":"uint256"}],"name":"updateSwapRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"chainId_","type":"uint16"},{"internalType":"uint256","name":"swapRatePrecision_","type":"uint256"}],"name":"updateSwapRatePrecision","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"chainId_","type":"uint16"},{"internalType":"bool","name":"unwrapWeth_","type":"bool"}],"name":"updateUnwrapWethFlag","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"wormhole","outputs":[{"internalType":"contract IWormhole","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"toNativeTokenAmount","type":"uint256"},{"internalType":"uint16","name":"targetChain","type":"uint16"},{"internalType":"bytes32","name":"targetRecipient","type":"bytes32"},{"internalType":"uint32","name":"batchId","type":"uint32"}],"name":"wrapAndTransferEthWithRelay","outputs":[{"internalType":"uint64","name":"messageSequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]

60806040523480156200001157600080fd5b50604051620040b9380380620040b9833981016040819052620000349162000260565b6001600d5561ffff8516620000825760405162461bcd60e51b815260206004820152600f60248201526e1a5b9d985b1a590818da185a5b9259608a1b60448201526064015b60405180910390fd5b6001600160a01b038416620000da5760405162461bcd60e51b815260206004820152601860248201527f696e76616c696420776f726d686f6c6520616464726573730000000000000000604482015260640162000079565b6001600160a01b038316620001325760405162461bcd60e51b815260206004820152601c60248201527f696e76616c696420746f6b656e20627269646765206164647265737300000000604482015260640162000079565b6001600160a01b0382166200018a5760405162461bcd60e51b815260206004820152601460248201527f696e76616c696420776574682061646472657373000000000000000000000000604482015260640162000079565b600180546001600160a01b031916331790556000805461ffff191661ffff8716179055600380546001600160a01b0319166001600160a01b038616179055600480546001600160a01b0319166001600160a01b038516179055600080546301000000600160b81b03191663010000006001600160a01b038516021790556000805462ff0000191662010000831515021790556200022a6305f5e100600555565b620002386305f5e100600655565b5050505050620002e0565b80516001600160a01b03811681146200025b57600080fd5b919050565b600080600080600060a086880312156200027957600080fd5b855161ffff811681146200028c57600080fd5b94506200029c6020870162000243565b9350620002ac6040870162000243565b9250620002bc6060870162000243565b915060808601518015158114620002d257600080fd5b809150509295509295909350565b613dc980620002f06000396000f3fe6080604052600436106102345760003560e01c80637c9dec7a1161012e578063a2f32c8f116100ab578063cd601c781161006f578063cd601c781461071a578063df160d761461073a578063e30c39781461075c578063f23f82ef1461077a578063fff982a81461079a57600080fd5b8063a2f32c8f14610677578063ad5c464814610697578063b5419523146106bc578063c6328a46146106dc578063c9c22f9f146106fa57600080fd5b806390a1afaa116100f257806390a1afaa146105c957806394cc743d146105e95780639a8a0592146106095780639cf278cd1461062c5780639fe089ad1461064a57600080fd5b80637c9dec7a146104f75780638335572f1461054d57806384acd1bb1461056d5780638da5cb5b1461058b5780638e151dd1146105a957600080fd5b80632def3e16116101bc5780633d62cca0116101805780633d62cca014610440578063415828bf1461047157806351e2d7b5146104865780635b9cf0e1146104a657806366775a6b146104c657600080fd5b80632def3e16146103875780632efe4f42146103a75780632f25e25f146103dd57806339ba6611146103f05780633b6e750f1461041057600080fd5b806327105ab91161020357806327105ab9146102c657806328ded8e3146102e657806329ac8361146103065780632ca8008c146103195780632d77e8fe1461034f57600080fd5b8063016aa20514610240578063038c0b66146102625780631019d654146102775780631acdab4b146102a757600080fd5b3661023b57005b600080fd5b34801561024c57600080fd5b5061026061025b3660046131a1565b6107ba565b005b34801561026e57600080fd5b50610260610884565b61028a6102853660046131ec565b610971565b6040516001600160401b0390911681526020015b60405180910390f35b3480156102b357600080fd5b506005545b60405190815260200161029e565b3480156102d257600080fd5b506102b86102e1366004613262565b610ac3565b3480156102f257600080fd5b506102b8610301366004613262565b610afd565b61028a610314366004613287565b610b2b565b34801561032557600080fd5b506102b86103343660046132d1565b6001600160a01b031660009081526008602052604090205490565b34801561035b57600080fd5b5061036f61036a3660046133c9565b610d7b565b6040516001600160a01b03909116815260200161029e565b34801561039357600080fd5b506102606103a2366004613448565b610e9b565b3480156103b357600080fd5b506102b86103c23660046132d1565b6001600160a01b031660009081526009602052604090205490565b6102606103eb366004613474565b610f59565b3480156103fc57600080fd5b506102b861040b3660046134e5565b611274565b34801561041c57600080fd5b5061043061042b3660046132d1565b611327565b604051901515815260200161029e565b34801561044c57600080fd5b506102b861045b366004613530565b61ffff1660009081526007602052604090205490565b34801561047d57600080fd5b506006546102b8565b34801561049257600080fd5b506102606104a1366004613448565b611345565b3480156104b257600080fd5b506102606104c1366004613448565b61146f565b3480156104d257600080fd5b506102b86104e1366004613530565b61ffff166000908152600a602052604090205490565b34801561050357600080fd5b506105176105123660046133c9565b611566565b60405161029e9190815160ff16815260208083015190820152604080830151908201526060918201519181019190915260800190565b34801561055957600080fd5b5061026061056836600461355b565b611697565b34801561057957600080fd5b506003546001600160a01b031661036f565b34801561059757600080fd5b506001546001600160a01b031661036f565b3480156105b557600080fd5b506102b86105c4366004613589565b61171e565b3480156105d557600080fd5b506102606105e4366004613448565b6117fe565b3480156105f557600080fd5b506102606106043660046131a1565b6118bc565b34801561061557600080fd5b5060005460405161ffff909116815260200161029e565b34801561063857600080fd5b5060005462010000900460ff16610430565b34801561065657600080fd5b5061066a6106653660046135a7565b611988565b60405161029e9190613665565b34801561068357600080fd5b50610260610692366004613530565b611a33565b3480156106a357600080fd5b50600054630100000090046001600160a01b031661036f565b3480156106c857600080fd5b506102b86106d73660046132d1565b611aaa565b3480156106e857600080fd5b506004546001600160a01b031661036f565b34801561070657600080fd5b506102606107153660046131a1565b611b44565b34801561072657600080fd5b506102b86107353660046132d1565b611c00565b34801561074657600080fd5b5061074f611ce5565b60405161029e9190613678565b34801561076857600080fd5b506002546001600160a01b031661036f565b34801561078657600080fd5b506102606107953660046136c5565b611d4a565b3480156107a657600080fd5b506102606107b53660046136c5565b611e75565b336107cd6001546001600160a01b031690565b6001600160a01b0316146107fc5760405162461bcd60e51b81526004016107f390613706565b60405180910390fd5b818061ffff1661080f60005461ffff1690565b61ffff16146108305760405162461bcd60e51b81526004016107f390613734565b6001600160a01b0382166108765760405162461bcd60e51b815260206004820152600d60248201526c34b73b30b634b2103a37b5b2b760991b60448201526064016107f3565b61087f82611f29565b505050565b60006108986002546001600160a01b031690565b9050336001600160a01b038216146108f25760405162461bcd60e51b815260206004820152601b60248201527f63616c6c6572206d7573742062652070656e64696e674f776e6572000000000060448201526064016107f3565b60006109066001546001600160a01b031690565b600180546001600160a01b0319166001600160a01b038516179055905061092d60006120ce565b816001600160a01b0316816001600160a01b03167f0d18b5fd22306e373229b9439188228edca81207d1667f604daf6cef8aa3ee6760405160405180910390a35050565b600061097b6120f0565b600061098f6003546001600160a01b031690565b6001600160a01b0316631a90a2196040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109f09190613759565b9050803414610a365760405162461bcd60e51b8152602060048201526012602482015271696e73756666696369656e742076616c756560701b60448201526064016107f3565b6000610a4189612149565b9050610a56610a508983610ac3565b82610afd565b9750610a6289896121eb565b9750610aab6040518060c001604052808b6001600160a01b031681526020018360ff1681526020018a81526020018981526020018861ffff168152602001878152508584612221565b92505050610ab96001600d55565b9695505050505050565b600060088260ff161115610af457610adc600883613788565b610ae790600a613885565b610af19084613894565b92505b50815b92915050565b600060088260ff161115610af457610b16600883613788565b610b2190600a613885565b610af190846138b6565b6000805462010000900460ff16610b845760405162461bcd60e51b815260206004820181905260248201527f574554482066756e6374696f6e616c697479206e6f7420737570706f7274656460448201526064016107f3565b6000610b986003546001600160a01b031690565b6001600160a01b0316631a90a2196040518163ffffffff1660e01b8152600401602060405180830381865afa158015610bd5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bf99190613759565b9050803411610c3f5760405162461bcd60e51b8152602060048201526012602482015271696e73756666696369656e742076616c756560701b60448201526064016107f3565b6000610c4b82346138cd565b90506000610c64610c5d836012610ac3565b6012610afd565b610c6e90836138cd565b90508015610ca557604051339082156108fc029083906000818181858888f19350505050158015610ca3573d6000803e3d6000fd5b505b6000610cb182846138cd565b90506000610ccf6000546001600160a01b0363010000009091041690565b9050806001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b158015610d0c57600080fd5b505af1158015610d20573d6000803e3d6000fd5b5050505050610d6d6040518060c00160405280836001600160a01b03168152602001601260ff1681526020018481526020018c81526020018b61ffff1681526020018a8152508887612221565b9a9950505050505050505050565b600080610d8983602161253b565b90506000610d98846041612599565b9050610da760005461ffff1690565b61ffff168161ffff1614610e88576004546001600160a01b0316604051630ff8f14360e11b815261ffff83166004820152602481018490526001600160a01b039190911690631ff1e28690604401602060405180830381865afa158015610e12573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e3691906138e0565b92506001600160a01b038316610e835760405162461bcd60e51b81526020600482015260126024820152711d1bdad95b881b9bdd08185d1d195cdd195960721b60448201526064016107f3565b610e94565b610e91826125f6565b92505b5050919050565b33610eae6001546001600160a01b031690565b6001600160a01b031614610ed45760405162461bcd60e51b81526004016107f390613706565b818061ffff16610ee760005461ffff1690565b61ffff1614610f085760405162461bcd60e51b81526004016107f390613734565b60008211610f505760405162461bcd60e51b81526020600482015260156024820152740707265636973696f6e206d757374206265203e203605c1b60448201526064016107f3565b61087f82600555565b6000806000610f9d85858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061264a92505050565b9250925092506000610fae84611566565b90506000610fbf82606001516125f6565b90506000610fd660005460ff620100009091041690565b90506001600160a01b0382163303610ffe57610ff484838784612926565b5050505050505050565b600061100985612149565b9050611019846020015182610afd565b6020850152600054630100000090046001600160a01b03166001600160a01b0316856001600160a01b031603611064576110598684866020015185612a50565b505050505050505050565b60408401511561122b5761107c846040015182610afd565b604085015260008061108d87611c00565b905080866040015111156110a357604086018190525b6110b187876040015161171e565b915081156111ed57813410156111095760405162461bcd60e51b815260206004820181905260248201527f696e73756666696369656e74206e617469766520617373657420616d6f756e7460448201526064016107f3565b600061111583346138cd565b9050801561114c57604051339082156108fc029083906000818181858888f1935050505015801561114a573d6000803e3d6000fd5b505b6040516001600160a01b0387169084156108fc029085906000818181858888f19350505050158015611182573d6000803e3d6000fd5b50876001600160a01b0316336001600160a01b0316876001600160a01b03167f764f0dc063c06f32d89a3f3af80c0db4be8a090901f589a478b447e0a51f09f18a60400151876040516111df929190918252602082015260400190565b60405180910390a450611228565b6000604087015234156112285760405133903480156108fc02916000818181858888f19350505050158015611226573d6000803e3d6000fd5b505b50505b60008460400151856020015161124191906138fd565b9050801561125457611254863383612ba8565b6112688685611263848b6138cd565b612ba8565b50505050505050505050565b6001600160a01b038216600090815260086020526040812054806000036112d15760405162461bcd60e51b81526020600482015260116024820152701cddd85c081c985d19481b9bdd081cd95d607a1b60448201526064016107f3565b6006546112de90826138b6565b60055461ffff87166000908152600a602052604090205461130086600a613885565b61130a91906138b6565b61131491906138b6565b61131e9190613894565b95945050505050565b6001600160a01b03166000908152600b602052604090205460ff1690565b336113586001546001600160a01b031690565b6001600160a01b03161461137e5760405162461bcd60e51b81526004016107f390613706565b806113db5760405162461bcd60e51b815260206004820152602760248201527f636f6e7472616374416464726573732063616e6e6f7420657175616c206279746044820152666573333228302960c81b60648201526084016107f3565b61ffff8216158015906113f7575060005461ffff838116911614155b6114535760405162461bcd60e51b815260206004820152602760248201527f636861696e49645f2063616e6e6f7420657175616c2030206f7220746869732060448201526618da185a5b925960ca1b60648201526084016107f3565b61ffff91909116600090815260076020526040902055565b5050565b336114826001546001600160a01b031690565b6001600160a01b0316146114a85760405162461bcd60e51b81526004016107f390613706565b60005461ffff1661ffff168261ffff16036114f55760405162461bcd60e51b815260206004820152600d60248201526c34b73b30b634b21031b430b4b760991b60448201526064016107f3565b61ffff821660009081526007602052604090205461154e5760405162461bcd60e51b815260206004820152601660248201527518dbdb9d1c9858dd08191bd95cdb89dd08195e1a5cdd60521b60448201526064016107f3565b61ffff919091166000908152600a6020526040902055565b6040805160808101825260008082526020820181905291810182905260608101829052906115948382612c0b565b60ff1682526115a46001826138fd565b9050816000015160ff166001146115f15760405162461bcd60e51b81526020600482015260116024820152701a5b9d985b1a59081c185e5b1bd8591259607a1b60448201526064016107f3565b6115fb8382612c67565b60208084019190915261160e90826138fd565b905061161a8382612c67565b604083015261162a6020826138fd565b9050611636838261253b565b60608301526116466020826138fd565b9050825181146116915760405162461bcd60e51b81526020600482015260166024820152750d2dcecc2d8d2c840dacae6e6c2ceca40d8cadccee8d60531b60448201526064016107f3565b50919050565b336116aa6001546001600160a01b031690565b6001600160a01b0316146116d05760405162461bcd60e51b81526004016107f390613706565b818061ffff166116e360005461ffff1690565b61ffff16146117045760405162461bcd60e51b81526004016107f390613734565b6000805462ff000019166201000084151502179055505050565b60008061172a84612149565b9050600061175061174b6000546001600160a01b0363010000009091041690565b612149565b90508060ff168260ff1611156117ad5761176a8183613788565b61177590600a613885565b61177e86611aaa565b61178891906138b6565b8461179260055490565b61179c91906138b6565b6117a69190613894565b92506117f6565b6117b685611aaa565b6117c08383613788565b6117cb90600a613885565b856117d560055490565b6117df91906138b6565b6117e991906138b6565b6117f39190613894565b92505b505092915050565b336118116001546001600160a01b031690565b6001600160a01b0316146118375760405162461bcd60e51b81526004016107f390613706565b818061ffff1661184a60005461ffff1690565b61ffff161461186b5760405162461bcd60e51b81526004016107f390613734565b600082116118b35760405162461bcd60e51b81526020600482015260156024820152740707265636973696f6e206d757374206265203e203605c1b60448201526064016107f3565b61087f82600655565b336118cf6001546001600160a01b031690565b6001600160a01b0316146118f55760405162461bcd60e51b81526004016107f390613706565b818061ffff1661190860005461ffff1690565b61ffff16146119295760405162461bcd60e51b81526004016107f390613734565b6001600160a01b03821661197f5760405162461bcd60e51b815260206004820181905260248201527f6e65774f776e65722063616e6e6f7420657175616c206164647265737328302960448201526064016107f3565b61087f826120ce565b6060816000015160ff166001146119d55760405162461bcd60e51b81526020600482015260116024820152701a5b9d985b1a59081c185e5b1bd8591259607a1b60448201526064016107f3565b815160208084015160408086015160608701519151611a1d95949192910160f89490941b6001600160f81b031916845260018401929092526021830152604182015260610190565b6040516020818303038152906040529050919050565b33611a466001546001600160a01b031690565b6001600160a01b031614611a6c5760405162461bcd60e51b81526004016107f390613706565b808061ffff16611a7f60005461ffff1690565b61ffff1614611aa05760405162461bcd60e51b81526004016107f390613734565b61146b60006120ce565b600080546001600160a01b0363010000009091048116825260086020526040808320549184168352822054600082118015611ae55750600081115b611b255760405162461bcd60e51b81526020600482015260116024820152701cddd85c081c985d19481b9bdd081cd95d607a1b60448201526064016107f3565b8082611b3060055490565b611b3a91906138b6565b610e919190613894565b33611b576001546001600160a01b031690565b6001600160a01b031614611b7d5760405162461bcd60e51b81526004016107f390613706565b818061ffff16611b9060005461ffff1690565b61ffff1614611bb15760405162461bcd60e51b81526004016107f390613734565b6001600160a01b038216611bf75760405162461bcd60e51b815260206004820152600d60248201526c34b73b30b634b2103a37b5b2b760991b60448201526064016107f3565b61087f82612cbc565b600080611c0c83612149565b90506000611c2d61174b6000546001600160a01b0363010000009091041690565b90508060ff168260ff161115611c9c57600554611c4a8284613788565b611c5590600a613885565b611c5e86611aaa565b6001600160a01b038716600090815260096020526040902054611c8191906138b6565b611c8b91906138b6565b611c959190613894565b9250610e94565b600554611ca98383613788565b611cb490600a613885565b611cbe91906138b6565b611cc785611aaa565b6001600160a01b038616600090815260096020526040902054611b30565b60606000600c01805480602002602001604051908101604052809291908181526020018280548015611d4057602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611d22575b5050505050905090565b33611d5d6001546001600160a01b031690565b6001600160a01b031614611d835760405162461bcd60e51b81526004016107f390613706565b828061ffff16611d9660005461ffff1690565b61ffff1614611db75760405162461bcd60e51b81526004016107f390613734565b611dc083611327565b611ddc5760405162461bcd60e51b81526004016107f390613910565b60008211611e2c5760405162461bcd60e51b815260206004820152601960248201527f737761702072617465206d757374206265206e6f6e7a65726f0000000000000060448201526064016107f3565b6001600160a01b038316600081815260086020526040808220859055518492917fc6eb9fb936b61b402d503deeffc822f46492e15c2c8f079815cc4850ad7b02b091a350505050565b33611e886001546001600160a01b031690565b6001600160a01b031614611eae5760405162461bcd60e51b81526004016107f390613706565b828061ffff16611ec160005461ffff1690565b61ffff1614611ee25760405162461bcd60e51b81526004016107f390613734565b611eeb83611327565b611f075760405162461bcd60e51b81526004016107f390613910565b6001600160a01b03831660009081526009602052604090208290555b50505050565b6001600160a01b0381166000908152600b602052604090205460ff16611f885760405162461bcd60e51b81526020600482015260146024820152731d1bdad95b881b9bdd081c9959da5cdd195c995960621b60448201526064016107f3565b6001600160a01b0381166000908152600b60209081526040808320805460ff191690556008825280832083905560099091528120819055600c54905b8181101561200e57826001600160a01b03166000600c018281548110611fec57611fec61393c565b6000918252602090912001546001600160a01b03161461200e57600101611fc4565b81811461087f57600182111561209657600c61202b6001846138cd565b8154811061203b5761203b61393c565b600091825260209091200154600c80546001600160a01b0390921691839081106120675761206761393c565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b031602179055505b600c8054806120a7576120a7613952565b600082815260209020810160001990810180546001600160a01b0319169055019055505050565b600280546001600160a01b0319166001600160a01b0392909216919091179055565b6002600d54036121425760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064016107f3565b6002600d55565b60408051600481526024810182526020810180516001600160e01b031663313ce56760e01b179052905160009182916001600160a01b0385169161218c91613968565b600060405180830381855afa9150503d80600081146121c7576040519150601f19603f3d011682016040523d82523d6000602084013e6121cc565b606091505b50915050808060200190518101906121e49190613994565b9392505050565b6000806121f784612d8b565b905061220584333086612e37565b8061220f85612d8b565b61221991906138cd565b949350505050565b60006122308460000151611327565b61224c5760405162461bcd60e51b81526004016107f390613910565b60a08401516122a95760405162461bcd60e51b8152602060048201526024808201527f746172676574526563697069656e742063616e6e6f74206265206279746573336044820152633228302960e01b60648201526084016107f3565b60006122bd85604001518660200151610ac3565b90506000811161230f5760405162461bcd60e51b815260206004820152601d60248201527f6e6f726d616c697a656420616d6f756e74206d757374206265203e203000000060448201526064016107f3565b600061232386606001518760200151610ac3565b90508560600151600014806123385750600081115b6123845760405162461bcd60e51b815260206004820152601b60248201527f696e76616c696420746f4e6174697665546f6b656e416d6f756e74000000000060448201526064016107f3565b608086015161ffff16600090815260076020526040902054806123e15760405162461bcd60e51b81526020600482015260156024820152741d185c99d95d081b9bdd081c9959da5cdd195c9959605a1b60448201526064016107f3565b60006124076123fd89608001518a600001518b60200151611274565b8960200151610ac3565b905061241383826138fd565b84116124575760405162461bcd60e51b81526020600482015260136024820152721a5b9cdd59999a58da595b9d08185b5bdd5b9d606a1b60448201526064016107f3565b600061248a6040518060800160405280600160ff1681526020018481526020018681526020018b60a00151815250611988565b905060006124a06004546001600160a01b031690565b90506124b58a60000151828c60400151612e6f565b806001600160a01b031663c5a5ebda898c600001518d604001518e60800151898f896040518863ffffffff1660e01b81526004016124f8969594939291906139b1565b60206040518083038185885af1158015612516573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190610d6d9190613a17565b60006125488260206138fd565b835110156125905760405162461bcd60e51b8152602060048201526015602482015274746f427974657333325f6f75744f66426f756e647360581b60448201526064016107f3565b50016020015190565b60006125a68260026138fd565b835110156125ed5760405162461bcd60e51b8152602060048201526014602482015273746f55696e7431365f6f75744f66426f756e647360601b60448201526064016107f3565b50016002015190565b60006001600160a01b03198216156126465760405162461bcd60e51b8152602060048201526013602482015272696e76616c69642045564d206164647265737360681b60448201526064016107f3565b5090565b606060008060006126636003546001600160a01b031690565b6001600160a01b031663a9e11893866040518263ffffffff1660e01b815260040161268e9190613665565b600060405180830381865afa1580156126ab573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526126d39190810190613b56565b905060006126e48260e00151610d7b565b90506126ef81611327565b6127325760405162461bcd60e51b81526020600482015260146024820152731d1bdad95b881b9bdd081c9959da5cdd195c995960621b60448201526064016107f3565b600061273d82612d8b565b905060006127536004546001600160a01b031690565b90506000816001600160a01b031663c3f511c18a6040518263ffffffff1660e01b81526004016127839190613665565b6000604051808303816000875af11580156127a2573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526127ca9190810190613c7a565b90506000836127d886612d8b565b6127e291906138cd565b90506000836001600160a01b031663ea63738d846040518263ffffffff1660e01b81526004016128129190613665565b600060405180830381865afa15801561282f573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526128579190810190613cae565b9050612877876060015161ffff1660009081526007602052604090205490565b8160c00151146128c95760405162461bcd60e51b815260206004820152601760248201527f636f6e7472616374206e6f74207265676973746572656400000000000000000060448201526064016107f3565b8660a001516001600160401b03168760800151886060015161ffff167fcaf280c8cfeba144da67230d9b009c8f868a75bac9a528fa0474be1ba317c16960405160405180910390a460e001519a9099509397509295505050505050565b34156129805760405162461bcd60e51b815260206004820152602360248201527f726563697069656e742063616e6e6f742073776170206e61746976652061737360448201526265747360e81b60648201526084016107f3565b6000546001600160a01b0363010000009091048116908516811480156129a35750815b15612a3e57604051632e1a7d4d60e01b8152600481018490526001600160a01b03821690632e1a7d4d90602401600060405180830381600087803b1580156129ea57600080fd5b505af11580156129fe573d6000803e3d6000fd5b50506040516001600160a01b038716925085156108fc02915085906000818181858888f19350505050158015612a38573d6000803e3d6000fd5b50612a49565b612a49858585612ba8565b5050505050565b3415612a935760405162461bcd60e51b815260206004820152601260248201527176616c7565206d757374206265207a65726f60701b60448201526064016107f3565b8015612b7957600054604051632e1a7d4d60e01b81526004810186905263010000009091046001600160a01b031690632e1a7d4d90602401600060405180830381600087803b158015612ae557600080fd5b505af1158015612af9573d6000803e3d6000fd5b50505050826001600160a01b03166108fc8386612b1691906138cd565b6040518115909202916000818181858888f19350505050158015612b3e573d6000803e3d6000fd5b508115612b7457604051339083156108fc029084906000818181858888f19350505050158015612b72573d6000803e3d6000fd5b505b611f23565b600054630100000090046001600160a01b0316612b9b818561126386896138cd565b8215612a4957612a498133855b6040516001600160a01b03831660248201526044810182905261087f90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152612f84565b6000612c188260016138fd565b83511015612c5e5760405162461bcd60e51b8152602060048201526013602482015272746f55696e74385f6f75744f66426f756e647360681b60448201526064016107f3565b50016001015190565b6000612c748260206138fd565b835110156125905760405162461bcd60e51b8152602060048201526015602482015274746f55696e743235365f6f75744f66426f756e647360581b60448201526064016107f3565b6001600160a01b0381166000908152600b602052604090205460ff1615612d255760405162461bcd60e51b815260206004820152601860248201527f746f6b656e20616c72656164792072656769737465726564000000000000000060448201526064016107f3565b6001600160a01b03166000818152600b60205260408120805460ff19166001908117909155600c805491820181559091527fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c70180546001600160a01b0319169091179055565b604080513060248083019190915282518083039091018152604490910182526020810180516001600160e01b03166370a0823160e01b179052905160009182916001600160a01b03851691612ddf91613968565b600060405180830381855afa9150503d8060008114612e1a576040519150601f19603f3d011682016040523d82523d6000602084013e612e1f565b606091505b50915050808060200190518101906121e49190613759565b6040516001600160a01b0380851660248301528316604482015260648101829052611f239085906323b872dd60e01b90608401612bd4565b801580612ee95750604051636eb1769f60e11b81523060048201526001600160a01b03838116602483015284169063dd62ed3e90604401602060405180830381865afa158015612ec3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ee79190613759565b155b612f545760405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b60648201526084016107f3565b6040516001600160a01b03831660248201526044810182905261087f90849063095ea7b360e01b90606401612bd4565b6000612fd9826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166130569092919063ffffffff16565b80519091501561087f5780806020019051810190612ff79190613d76565b61087f5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016107f3565b6060612219848460008585600080866001600160a01b0316858760405161307d9190613968565b60006040518083038185875af1925050503d80600081146130ba576040519150601f19603f3d011682016040523d82523d6000602084013e6130bf565b606091505b50915091506130d0878383876130db565b979650505050505050565b6060831561314a578251600003613143576001600160a01b0385163b6131435760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016107f3565b5081612219565b612219838381511561315f5781518083602001fd5b8060405162461bcd60e51b81526004016107f39190613665565b61ffff8116811461318957600080fd5b50565b6001600160a01b038116811461318957600080fd5b600080604083850312156131b457600080fd5b82356131bf81613179565b915060208301356131cf8161318c565b809150509250929050565b63ffffffff8116811461318957600080fd5b60008060008060008060c0878903121561320557600080fd5b86356132108161318c565b95506020870135945060408701359350606087013561322e81613179565b92506080870135915060a0870135613245816131da565b809150509295509295509295565b60ff8116811461318957600080fd5b6000806040838503121561327557600080fd5b8235915060208301356131cf81613253565b6000806000806080858703121561329d57600080fd5b8435935060208501356132af81613179565b92506040850135915060608501356132c6816131da565b939692955090935050565b6000602082840312156132e357600080fd5b81356121e48161318c565b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b0381118282101715613326576133266132ee565b60405290565b60405161016081016001600160401b0381118282101715613326576133266132ee565b60405161010081016001600160401b0381118282101715613326576133266132ee565b604051601f8201601f191681016001600160401b038111828210171561339a5761339a6132ee565b604052919050565b60006001600160401b038211156133bb576133bb6132ee565b50601f01601f191660200190565b6000602082840312156133db57600080fd5b81356001600160401b038111156133f157600080fd5b8201601f8101841361340257600080fd5b8035613415613410826133a2565b613372565b81815285602083850101111561342a57600080fd5b81602084016020830137600091810160200191909152949350505050565b6000806040838503121561345b57600080fd5b823561346681613179565b946020939093013593505050565b6000806020838503121561348757600080fd5b82356001600160401b038082111561349e57600080fd5b818501915085601f8301126134b257600080fd5b8135818111156134c157600080fd5b8660208285010111156134d357600080fd5b60209290920196919550909350505050565b6000806000606084860312156134fa57600080fd5b833561350581613179565b925060208401356135158161318c565b9150604084013561352581613253565b809150509250925092565b60006020828403121561354257600080fd5b81356121e481613179565b801515811461318957600080fd5b6000806040838503121561356e57600080fd5b823561357981613179565b915060208301356131cf8161354d565b6000806040838503121561359c57600080fd5b82356134668161318c565b6000608082840312156135b957600080fd5b604051608081018181106001600160401b03821117156135db576135db6132ee565b60405282356135e981613253565b808252506020830135602082015260408301356040820152606083013560608201528091505092915050565b60005b83811015613630578181015183820152602001613618565b50506000910152565b60008151808452613651816020860160208601613615565b601f01601f19169290920160200192915050565b6020815260006121e46020830184613639565b6020808252825182820181905260009190848201906040850190845b818110156136b95783516001600160a01b031683529284019291840191600101613694565b50909695505050505050565b6000806000606084860312156136da57600080fd5b83356136e581613179565b925060208401356136f58161318c565b929592945050506040919091013590565b60208082526014908201527331b0b63632b9103737ba103a34329037bbb732b960611b604082015260600190565b6020808252600b908201526a3bb937b7339031b430b4b760a91b604082015260600190565b60006020828403121561376b57600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b60ff8281168282160390811115610af757610af7613772565b600181815b808511156137dc5781600019048211156137c2576137c2613772565b808516156137cf57918102915b93841c93908002906137a6565b509250929050565b6000826137f357506001610af7565b8161380057506000610af7565b816001811461381657600281146138205761383c565b6001915050610af7565b60ff84111561383157613831613772565b50506001821b610af7565b5060208310610133831016604e8410600b841016171561385f575081810a610af7565b61386983836137a1565b806000190482111561387d5761387d613772565b029392505050565b60006121e460ff8416836137e4565b6000826138b157634e487b7160e01b600052601260045260246000fd5b500490565b8082028115828204841417610af757610af7613772565b81810381811115610af757610af7613772565b6000602082840312156138f257600080fd5b81516121e48161318c565b80820180821115610af757610af7613772565b6020808252601290820152711d1bdad95b881b9bdd081858d8d95c1d195960721b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052603160045260246000fd5b6000825161397a818460208701613615565b9190910192915050565b805161398f81613253565b919050565b6000602082840312156139a657600080fd5b81516121e481613253565b60018060a01b038716815285602082015261ffff8516604082015283606082015263ffffffff8316608082015260c060a082015260006139f460c0830184613639565b98975050505050505050565b80516001600160401b038116811461398f57600080fd5b600060208284031215613a2957600080fd5b6121e482613a00565b805161398f816131da565b805161398f81613179565b600082601f830112613a5957600080fd5b8151613a67613410826133a2565b818152846020838601011115613a7c57600080fd5b612219826020830160208701613615565b600082601f830112613a9e57600080fd5b815160206001600160401b03821115613ab957613ab96132ee565b613ac7818360051b01613372565b82815260079290921b84018101918181019086841115613ae657600080fd5b8286015b84811015613b4b5760808189031215613b035760008081fd5b613b0b613304565b815181528482015185820152604080830151613b2681613253565b90820152606082810151613b3981613253565b90820152835291830191608001613aea565b509695505050505050565b600060208284031215613b6857600080fd5b81516001600160401b0380821115613b7f57600080fd5b908301906101608286031215613b9457600080fd5b613b9c61332c565b613ba583613984565b8152613bb360208401613a32565b6020820152613bc460408401613a32565b6040820152613bd560608401613a3d565b606082015260808301516080820152613bf060a08401613a00565b60a0820152613c0160c08401613984565b60c082015260e083015182811115613c1857600080fd5b613c2487828601613a48565b60e083015250610100613c38818501613a32565b908201526101208381015183811115613c5057600080fd5b613c5c88828701613a8d565b91830191909152506101409283015192810192909252509392505050565b600060208284031215613c8c57600080fd5b81516001600160401b03811115613ca257600080fd5b61221984828501613a48565b600060208284031215613cc057600080fd5b81516001600160401b0380821115613cd757600080fd5b908301906101008286031215613cec57600080fd5b613cf461334f565b613cfd83613984565b81526020830151602082015260408301516040820152613d1f60608401613a3d565b606082015260808301516080820152613d3a60a08401613a3d565b60a082015260c083015160c082015260e083015182811115613d5b57600080fd5b613d6787828601613a48565b60e08301525095945050505050565b600060208284031215613d8857600080fd5b81516121e48161354d56fea2646970667358221220043ecf0d1429bfc7866a5607e98ae2b1cca0eeef46c7279047c0211efe0f076764736f6c63430008110033000000000000000000000000000000000000000000000000000000000000000200000000000000000000000098f3c9e6e3face36baad05fe09d375ef1464288b0000000000000000000000003ee18b2214aff97000d974cf647e7c347e8fa585000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000001

Deployed Bytecode

0x6080604052600436106102345760003560e01c80637c9dec7a1161012e578063a2f32c8f116100ab578063cd601c781161006f578063cd601c781461071a578063df160d761461073a578063e30c39781461075c578063f23f82ef1461077a578063fff982a81461079a57600080fd5b8063a2f32c8f14610677578063ad5c464814610697578063b5419523146106bc578063c6328a46146106dc578063c9c22f9f146106fa57600080fd5b806390a1afaa116100f257806390a1afaa146105c957806394cc743d146105e95780639a8a0592146106095780639cf278cd1461062c5780639fe089ad1461064a57600080fd5b80637c9dec7a146104f75780638335572f1461054d57806384acd1bb1461056d5780638da5cb5b1461058b5780638e151dd1146105a957600080fd5b80632def3e16116101bc5780633d62cca0116101805780633d62cca014610440578063415828bf1461047157806351e2d7b5146104865780635b9cf0e1146104a657806366775a6b146104c657600080fd5b80632def3e16146103875780632efe4f42146103a75780632f25e25f146103dd57806339ba6611146103f05780633b6e750f1461041057600080fd5b806327105ab91161020357806327105ab9146102c657806328ded8e3146102e657806329ac8361146103065780632ca8008c146103195780632d77e8fe1461034f57600080fd5b8063016aa20514610240578063038c0b66146102625780631019d654146102775780631acdab4b146102a757600080fd5b3661023b57005b600080fd5b34801561024c57600080fd5b5061026061025b3660046131a1565b6107ba565b005b34801561026e57600080fd5b50610260610884565b61028a6102853660046131ec565b610971565b6040516001600160401b0390911681526020015b60405180910390f35b3480156102b357600080fd5b506005545b60405190815260200161029e565b3480156102d257600080fd5b506102b86102e1366004613262565b610ac3565b3480156102f257600080fd5b506102b8610301366004613262565b610afd565b61028a610314366004613287565b610b2b565b34801561032557600080fd5b506102b86103343660046132d1565b6001600160a01b031660009081526008602052604090205490565b34801561035b57600080fd5b5061036f61036a3660046133c9565b610d7b565b6040516001600160a01b03909116815260200161029e565b34801561039357600080fd5b506102606103a2366004613448565b610e9b565b3480156103b357600080fd5b506102b86103c23660046132d1565b6001600160a01b031660009081526009602052604090205490565b6102606103eb366004613474565b610f59565b3480156103fc57600080fd5b506102b861040b3660046134e5565b611274565b34801561041c57600080fd5b5061043061042b3660046132d1565b611327565b604051901515815260200161029e565b34801561044c57600080fd5b506102b861045b366004613530565b61ffff1660009081526007602052604090205490565b34801561047d57600080fd5b506006546102b8565b34801561049257600080fd5b506102606104a1366004613448565b611345565b3480156104b257600080fd5b506102606104c1366004613448565b61146f565b3480156104d257600080fd5b506102b86104e1366004613530565b61ffff166000908152600a602052604090205490565b34801561050357600080fd5b506105176105123660046133c9565b611566565b60405161029e9190815160ff16815260208083015190820152604080830151908201526060918201519181019190915260800190565b34801561055957600080fd5b5061026061056836600461355b565b611697565b34801561057957600080fd5b506003546001600160a01b031661036f565b34801561059757600080fd5b506001546001600160a01b031661036f565b3480156105b557600080fd5b506102b86105c4366004613589565b61171e565b3480156105d557600080fd5b506102606105e4366004613448565b6117fe565b3480156105f557600080fd5b506102606106043660046131a1565b6118bc565b34801561061557600080fd5b5060005460405161ffff909116815260200161029e565b34801561063857600080fd5b5060005462010000900460ff16610430565b34801561065657600080fd5b5061066a6106653660046135a7565b611988565b60405161029e9190613665565b34801561068357600080fd5b50610260610692366004613530565b611a33565b3480156106a357600080fd5b50600054630100000090046001600160a01b031661036f565b3480156106c857600080fd5b506102b86106d73660046132d1565b611aaa565b3480156106e857600080fd5b506004546001600160a01b031661036f565b34801561070657600080fd5b506102606107153660046131a1565b611b44565b34801561072657600080fd5b506102b86107353660046132d1565b611c00565b34801561074657600080fd5b5061074f611ce5565b60405161029e9190613678565b34801561076857600080fd5b506002546001600160a01b031661036f565b34801561078657600080fd5b506102606107953660046136c5565b611d4a565b3480156107a657600080fd5b506102606107b53660046136c5565b611e75565b336107cd6001546001600160a01b031690565b6001600160a01b0316146107fc5760405162461bcd60e51b81526004016107f390613706565b60405180910390fd5b818061ffff1661080f60005461ffff1690565b61ffff16146108305760405162461bcd60e51b81526004016107f390613734565b6001600160a01b0382166108765760405162461bcd60e51b815260206004820152600d60248201526c34b73b30b634b2103a37b5b2b760991b60448201526064016107f3565b61087f82611f29565b505050565b60006108986002546001600160a01b031690565b9050336001600160a01b038216146108f25760405162461bcd60e51b815260206004820152601b60248201527f63616c6c6572206d7573742062652070656e64696e674f776e6572000000000060448201526064016107f3565b60006109066001546001600160a01b031690565b600180546001600160a01b0319166001600160a01b038516179055905061092d60006120ce565b816001600160a01b0316816001600160a01b03167f0d18b5fd22306e373229b9439188228edca81207d1667f604daf6cef8aa3ee6760405160405180910390a35050565b600061097b6120f0565b600061098f6003546001600160a01b031690565b6001600160a01b0316631a90a2196040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109f09190613759565b9050803414610a365760405162461bcd60e51b8152602060048201526012602482015271696e73756666696369656e742076616c756560701b60448201526064016107f3565b6000610a4189612149565b9050610a56610a508983610ac3565b82610afd565b9750610a6289896121eb565b9750610aab6040518060c001604052808b6001600160a01b031681526020018360ff1681526020018a81526020018981526020018861ffff168152602001878152508584612221565b92505050610ab96001600d55565b9695505050505050565b600060088260ff161115610af457610adc600883613788565b610ae790600a613885565b610af19084613894565b92505b50815b92915050565b600060088260ff161115610af457610b16600883613788565b610b2190600a613885565b610af190846138b6565b6000805462010000900460ff16610b845760405162461bcd60e51b815260206004820181905260248201527f574554482066756e6374696f6e616c697479206e6f7420737570706f7274656460448201526064016107f3565b6000610b986003546001600160a01b031690565b6001600160a01b0316631a90a2196040518163ffffffff1660e01b8152600401602060405180830381865afa158015610bd5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bf99190613759565b9050803411610c3f5760405162461bcd60e51b8152602060048201526012602482015271696e73756666696369656e742076616c756560701b60448201526064016107f3565b6000610c4b82346138cd565b90506000610c64610c5d836012610ac3565b6012610afd565b610c6e90836138cd565b90508015610ca557604051339082156108fc029083906000818181858888f19350505050158015610ca3573d6000803e3d6000fd5b505b6000610cb182846138cd565b90506000610ccf6000546001600160a01b0363010000009091041690565b9050806001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b158015610d0c57600080fd5b505af1158015610d20573d6000803e3d6000fd5b5050505050610d6d6040518060c00160405280836001600160a01b03168152602001601260ff1681526020018481526020018c81526020018b61ffff1681526020018a8152508887612221565b9a9950505050505050505050565b600080610d8983602161253b565b90506000610d98846041612599565b9050610da760005461ffff1690565b61ffff168161ffff1614610e88576004546001600160a01b0316604051630ff8f14360e11b815261ffff83166004820152602481018490526001600160a01b039190911690631ff1e28690604401602060405180830381865afa158015610e12573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e3691906138e0565b92506001600160a01b038316610e835760405162461bcd60e51b81526020600482015260126024820152711d1bdad95b881b9bdd08185d1d195cdd195960721b60448201526064016107f3565b610e94565b610e91826125f6565b92505b5050919050565b33610eae6001546001600160a01b031690565b6001600160a01b031614610ed45760405162461bcd60e51b81526004016107f390613706565b818061ffff16610ee760005461ffff1690565b61ffff1614610f085760405162461bcd60e51b81526004016107f390613734565b60008211610f505760405162461bcd60e51b81526020600482015260156024820152740707265636973696f6e206d757374206265203e203605c1b60448201526064016107f3565b61087f82600555565b6000806000610f9d85858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061264a92505050565b9250925092506000610fae84611566565b90506000610fbf82606001516125f6565b90506000610fd660005460ff620100009091041690565b90506001600160a01b0382163303610ffe57610ff484838784612926565b5050505050505050565b600061100985612149565b9050611019846020015182610afd565b6020850152600054630100000090046001600160a01b03166001600160a01b0316856001600160a01b031603611064576110598684866020015185612a50565b505050505050505050565b60408401511561122b5761107c846040015182610afd565b604085015260008061108d87611c00565b905080866040015111156110a357604086018190525b6110b187876040015161171e565b915081156111ed57813410156111095760405162461bcd60e51b815260206004820181905260248201527f696e73756666696369656e74206e617469766520617373657420616d6f756e7460448201526064016107f3565b600061111583346138cd565b9050801561114c57604051339082156108fc029083906000818181858888f1935050505015801561114a573d6000803e3d6000fd5b505b6040516001600160a01b0387169084156108fc029085906000818181858888f19350505050158015611182573d6000803e3d6000fd5b50876001600160a01b0316336001600160a01b0316876001600160a01b03167f764f0dc063c06f32d89a3f3af80c0db4be8a090901f589a478b447e0a51f09f18a60400151876040516111df929190918252602082015260400190565b60405180910390a450611228565b6000604087015234156112285760405133903480156108fc02916000818181858888f19350505050158015611226573d6000803e3d6000fd5b505b50505b60008460400151856020015161124191906138fd565b9050801561125457611254863383612ba8565b6112688685611263848b6138cd565b612ba8565b50505050505050505050565b6001600160a01b038216600090815260086020526040812054806000036112d15760405162461bcd60e51b81526020600482015260116024820152701cddd85c081c985d19481b9bdd081cd95d607a1b60448201526064016107f3565b6006546112de90826138b6565b60055461ffff87166000908152600a602052604090205461130086600a613885565b61130a91906138b6565b61131491906138b6565b61131e9190613894565b95945050505050565b6001600160a01b03166000908152600b602052604090205460ff1690565b336113586001546001600160a01b031690565b6001600160a01b03161461137e5760405162461bcd60e51b81526004016107f390613706565b806113db5760405162461bcd60e51b815260206004820152602760248201527f636f6e7472616374416464726573732063616e6e6f7420657175616c206279746044820152666573333228302960c81b60648201526084016107f3565b61ffff8216158015906113f7575060005461ffff838116911614155b6114535760405162461bcd60e51b815260206004820152602760248201527f636861696e49645f2063616e6e6f7420657175616c2030206f7220746869732060448201526618da185a5b925960ca1b60648201526084016107f3565b61ffff91909116600090815260076020526040902055565b5050565b336114826001546001600160a01b031690565b6001600160a01b0316146114a85760405162461bcd60e51b81526004016107f390613706565b60005461ffff1661ffff168261ffff16036114f55760405162461bcd60e51b815260206004820152600d60248201526c34b73b30b634b21031b430b4b760991b60448201526064016107f3565b61ffff821660009081526007602052604090205461154e5760405162461bcd60e51b815260206004820152601660248201527518dbdb9d1c9858dd08191bd95cdb89dd08195e1a5cdd60521b60448201526064016107f3565b61ffff919091166000908152600a6020526040902055565b6040805160808101825260008082526020820181905291810182905260608101829052906115948382612c0b565b60ff1682526115a46001826138fd565b9050816000015160ff166001146115f15760405162461bcd60e51b81526020600482015260116024820152701a5b9d985b1a59081c185e5b1bd8591259607a1b60448201526064016107f3565b6115fb8382612c67565b60208084019190915261160e90826138fd565b905061161a8382612c67565b604083015261162a6020826138fd565b9050611636838261253b565b60608301526116466020826138fd565b9050825181146116915760405162461bcd60e51b81526020600482015260166024820152750d2dcecc2d8d2c840dacae6e6c2ceca40d8cadccee8d60531b60448201526064016107f3565b50919050565b336116aa6001546001600160a01b031690565b6001600160a01b0316146116d05760405162461bcd60e51b81526004016107f390613706565b818061ffff166116e360005461ffff1690565b61ffff16146117045760405162461bcd60e51b81526004016107f390613734565b6000805462ff000019166201000084151502179055505050565b60008061172a84612149565b9050600061175061174b6000546001600160a01b0363010000009091041690565b612149565b90508060ff168260ff1611156117ad5761176a8183613788565b61177590600a613885565b61177e86611aaa565b61178891906138b6565b8461179260055490565b61179c91906138b6565b6117a69190613894565b92506117f6565b6117b685611aaa565b6117c08383613788565b6117cb90600a613885565b856117d560055490565b6117df91906138b6565b6117e991906138b6565b6117f39190613894565b92505b505092915050565b336118116001546001600160a01b031690565b6001600160a01b0316146118375760405162461bcd60e51b81526004016107f390613706565b818061ffff1661184a60005461ffff1690565b61ffff161461186b5760405162461bcd60e51b81526004016107f390613734565b600082116118b35760405162461bcd60e51b81526020600482015260156024820152740707265636973696f6e206d757374206265203e203605c1b60448201526064016107f3565b61087f82600655565b336118cf6001546001600160a01b031690565b6001600160a01b0316146118f55760405162461bcd60e51b81526004016107f390613706565b818061ffff1661190860005461ffff1690565b61ffff16146119295760405162461bcd60e51b81526004016107f390613734565b6001600160a01b03821661197f5760405162461bcd60e51b815260206004820181905260248201527f6e65774f776e65722063616e6e6f7420657175616c206164647265737328302960448201526064016107f3565b61087f826120ce565b6060816000015160ff166001146119d55760405162461bcd60e51b81526020600482015260116024820152701a5b9d985b1a59081c185e5b1bd8591259607a1b60448201526064016107f3565b815160208084015160408086015160608701519151611a1d95949192910160f89490941b6001600160f81b031916845260018401929092526021830152604182015260610190565b6040516020818303038152906040529050919050565b33611a466001546001600160a01b031690565b6001600160a01b031614611a6c5760405162461bcd60e51b81526004016107f390613706565b808061ffff16611a7f60005461ffff1690565b61ffff1614611aa05760405162461bcd60e51b81526004016107f390613734565b61146b60006120ce565b600080546001600160a01b0363010000009091048116825260086020526040808320549184168352822054600082118015611ae55750600081115b611b255760405162461bcd60e51b81526020600482015260116024820152701cddd85c081c985d19481b9bdd081cd95d607a1b60448201526064016107f3565b8082611b3060055490565b611b3a91906138b6565b610e919190613894565b33611b576001546001600160a01b031690565b6001600160a01b031614611b7d5760405162461bcd60e51b81526004016107f390613706565b818061ffff16611b9060005461ffff1690565b61ffff1614611bb15760405162461bcd60e51b81526004016107f390613734565b6001600160a01b038216611bf75760405162461bcd60e51b815260206004820152600d60248201526c34b73b30b634b2103a37b5b2b760991b60448201526064016107f3565b61087f82612cbc565b600080611c0c83612149565b90506000611c2d61174b6000546001600160a01b0363010000009091041690565b90508060ff168260ff161115611c9c57600554611c4a8284613788565b611c5590600a613885565b611c5e86611aaa565b6001600160a01b038716600090815260096020526040902054611c8191906138b6565b611c8b91906138b6565b611c959190613894565b9250610e94565b600554611ca98383613788565b611cb490600a613885565b611cbe91906138b6565b611cc785611aaa565b6001600160a01b038616600090815260096020526040902054611b30565b60606000600c01805480602002602001604051908101604052809291908181526020018280548015611d4057602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611d22575b5050505050905090565b33611d5d6001546001600160a01b031690565b6001600160a01b031614611d835760405162461bcd60e51b81526004016107f390613706565b828061ffff16611d9660005461ffff1690565b61ffff1614611db75760405162461bcd60e51b81526004016107f390613734565b611dc083611327565b611ddc5760405162461bcd60e51b81526004016107f390613910565b60008211611e2c5760405162461bcd60e51b815260206004820152601960248201527f737761702072617465206d757374206265206e6f6e7a65726f0000000000000060448201526064016107f3565b6001600160a01b038316600081815260086020526040808220859055518492917fc6eb9fb936b61b402d503deeffc822f46492e15c2c8f079815cc4850ad7b02b091a350505050565b33611e886001546001600160a01b031690565b6001600160a01b031614611eae5760405162461bcd60e51b81526004016107f390613706565b828061ffff16611ec160005461ffff1690565b61ffff1614611ee25760405162461bcd60e51b81526004016107f390613734565b611eeb83611327565b611f075760405162461bcd60e51b81526004016107f390613910565b6001600160a01b03831660009081526009602052604090208290555b50505050565b6001600160a01b0381166000908152600b602052604090205460ff16611f885760405162461bcd60e51b81526020600482015260146024820152731d1bdad95b881b9bdd081c9959da5cdd195c995960621b60448201526064016107f3565b6001600160a01b0381166000908152600b60209081526040808320805460ff191690556008825280832083905560099091528120819055600c54905b8181101561200e57826001600160a01b03166000600c018281548110611fec57611fec61393c565b6000918252602090912001546001600160a01b03161461200e57600101611fc4565b81811461087f57600182111561209657600c61202b6001846138cd565b8154811061203b5761203b61393c565b600091825260209091200154600c80546001600160a01b0390921691839081106120675761206761393c565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b031602179055505b600c8054806120a7576120a7613952565b600082815260209020810160001990810180546001600160a01b0319169055019055505050565b600280546001600160a01b0319166001600160a01b0392909216919091179055565b6002600d54036121425760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064016107f3565b6002600d55565b60408051600481526024810182526020810180516001600160e01b031663313ce56760e01b179052905160009182916001600160a01b0385169161218c91613968565b600060405180830381855afa9150503d80600081146121c7576040519150601f19603f3d011682016040523d82523d6000602084013e6121cc565b606091505b50915050808060200190518101906121e49190613994565b9392505050565b6000806121f784612d8b565b905061220584333086612e37565b8061220f85612d8b565b61221991906138cd565b949350505050565b60006122308460000151611327565b61224c5760405162461bcd60e51b81526004016107f390613910565b60a08401516122a95760405162461bcd60e51b8152602060048201526024808201527f746172676574526563697069656e742063616e6e6f74206265206279746573336044820152633228302960e01b60648201526084016107f3565b60006122bd85604001518660200151610ac3565b90506000811161230f5760405162461bcd60e51b815260206004820152601d60248201527f6e6f726d616c697a656420616d6f756e74206d757374206265203e203000000060448201526064016107f3565b600061232386606001518760200151610ac3565b90508560600151600014806123385750600081115b6123845760405162461bcd60e51b815260206004820152601b60248201527f696e76616c696420746f4e6174697665546f6b656e416d6f756e74000000000060448201526064016107f3565b608086015161ffff16600090815260076020526040902054806123e15760405162461bcd60e51b81526020600482015260156024820152741d185c99d95d081b9bdd081c9959da5cdd195c9959605a1b60448201526064016107f3565b60006124076123fd89608001518a600001518b60200151611274565b8960200151610ac3565b905061241383826138fd565b84116124575760405162461bcd60e51b81526020600482015260136024820152721a5b9cdd59999a58da595b9d08185b5bdd5b9d606a1b60448201526064016107f3565b600061248a6040518060800160405280600160ff1681526020018481526020018681526020018b60a00151815250611988565b905060006124a06004546001600160a01b031690565b90506124b58a60000151828c60400151612e6f565b806001600160a01b031663c5a5ebda898c600001518d604001518e60800151898f896040518863ffffffff1660e01b81526004016124f8969594939291906139b1565b60206040518083038185885af1158015612516573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190610d6d9190613a17565b60006125488260206138fd565b835110156125905760405162461bcd60e51b8152602060048201526015602482015274746f427974657333325f6f75744f66426f756e647360581b60448201526064016107f3565b50016020015190565b60006125a68260026138fd565b835110156125ed5760405162461bcd60e51b8152602060048201526014602482015273746f55696e7431365f6f75744f66426f756e647360601b60448201526064016107f3565b50016002015190565b60006001600160a01b03198216156126465760405162461bcd60e51b8152602060048201526013602482015272696e76616c69642045564d206164647265737360681b60448201526064016107f3565b5090565b606060008060006126636003546001600160a01b031690565b6001600160a01b031663a9e11893866040518263ffffffff1660e01b815260040161268e9190613665565b600060405180830381865afa1580156126ab573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526126d39190810190613b56565b905060006126e48260e00151610d7b565b90506126ef81611327565b6127325760405162461bcd60e51b81526020600482015260146024820152731d1bdad95b881b9bdd081c9959da5cdd195c995960621b60448201526064016107f3565b600061273d82612d8b565b905060006127536004546001600160a01b031690565b90506000816001600160a01b031663c3f511c18a6040518263ffffffff1660e01b81526004016127839190613665565b6000604051808303816000875af11580156127a2573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526127ca9190810190613c7a565b90506000836127d886612d8b565b6127e291906138cd565b90506000836001600160a01b031663ea63738d846040518263ffffffff1660e01b81526004016128129190613665565b600060405180830381865afa15801561282f573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526128579190810190613cae565b9050612877876060015161ffff1660009081526007602052604090205490565b8160c00151146128c95760405162461bcd60e51b815260206004820152601760248201527f636f6e7472616374206e6f74207265676973746572656400000000000000000060448201526064016107f3565b8660a001516001600160401b03168760800151886060015161ffff167fcaf280c8cfeba144da67230d9b009c8f868a75bac9a528fa0474be1ba317c16960405160405180910390a460e001519a9099509397509295505050505050565b34156129805760405162461bcd60e51b815260206004820152602360248201527f726563697069656e742063616e6e6f742073776170206e61746976652061737360448201526265747360e81b60648201526084016107f3565b6000546001600160a01b0363010000009091048116908516811480156129a35750815b15612a3e57604051632e1a7d4d60e01b8152600481018490526001600160a01b03821690632e1a7d4d90602401600060405180830381600087803b1580156129ea57600080fd5b505af11580156129fe573d6000803e3d6000fd5b50506040516001600160a01b038716925085156108fc02915085906000818181858888f19350505050158015612a38573d6000803e3d6000fd5b50612a49565b612a49858585612ba8565b5050505050565b3415612a935760405162461bcd60e51b815260206004820152601260248201527176616c7565206d757374206265207a65726f60701b60448201526064016107f3565b8015612b7957600054604051632e1a7d4d60e01b81526004810186905263010000009091046001600160a01b031690632e1a7d4d90602401600060405180830381600087803b158015612ae557600080fd5b505af1158015612af9573d6000803e3d6000fd5b50505050826001600160a01b03166108fc8386612b1691906138cd565b6040518115909202916000818181858888f19350505050158015612b3e573d6000803e3d6000fd5b508115612b7457604051339083156108fc029084906000818181858888f19350505050158015612b72573d6000803e3d6000fd5b505b611f23565b600054630100000090046001600160a01b0316612b9b818561126386896138cd565b8215612a4957612a498133855b6040516001600160a01b03831660248201526044810182905261087f90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152612f84565b6000612c188260016138fd565b83511015612c5e5760405162461bcd60e51b8152602060048201526013602482015272746f55696e74385f6f75744f66426f756e647360681b60448201526064016107f3565b50016001015190565b6000612c748260206138fd565b835110156125905760405162461bcd60e51b8152602060048201526015602482015274746f55696e743235365f6f75744f66426f756e647360581b60448201526064016107f3565b6001600160a01b0381166000908152600b602052604090205460ff1615612d255760405162461bcd60e51b815260206004820152601860248201527f746f6b656e20616c72656164792072656769737465726564000000000000000060448201526064016107f3565b6001600160a01b03166000818152600b60205260408120805460ff19166001908117909155600c805491820181559091527fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c70180546001600160a01b0319169091179055565b604080513060248083019190915282518083039091018152604490910182526020810180516001600160e01b03166370a0823160e01b179052905160009182916001600160a01b03851691612ddf91613968565b600060405180830381855afa9150503d8060008114612e1a576040519150601f19603f3d011682016040523d82523d6000602084013e612e1f565b606091505b50915050808060200190518101906121e49190613759565b6040516001600160a01b0380851660248301528316604482015260648101829052611f239085906323b872dd60e01b90608401612bd4565b801580612ee95750604051636eb1769f60e11b81523060048201526001600160a01b03838116602483015284169063dd62ed3e90604401602060405180830381865afa158015612ec3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ee79190613759565b155b612f545760405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b60648201526084016107f3565b6040516001600160a01b03831660248201526044810182905261087f90849063095ea7b360e01b90606401612bd4565b6000612fd9826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166130569092919063ffffffff16565b80519091501561087f5780806020019051810190612ff79190613d76565b61087f5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016107f3565b6060612219848460008585600080866001600160a01b0316858760405161307d9190613968565b60006040518083038185875af1925050503d80600081146130ba576040519150601f19603f3d011682016040523d82523d6000602084013e6130bf565b606091505b50915091506130d0878383876130db565b979650505050505050565b6060831561314a578251600003613143576001600160a01b0385163b6131435760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016107f3565b5081612219565b612219838381511561315f5781518083602001fd5b8060405162461bcd60e51b81526004016107f39190613665565b61ffff8116811461318957600080fd5b50565b6001600160a01b038116811461318957600080fd5b600080604083850312156131b457600080fd5b82356131bf81613179565b915060208301356131cf8161318c565b809150509250929050565b63ffffffff8116811461318957600080fd5b60008060008060008060c0878903121561320557600080fd5b86356132108161318c565b95506020870135945060408701359350606087013561322e81613179565b92506080870135915060a0870135613245816131da565b809150509295509295509295565b60ff8116811461318957600080fd5b6000806040838503121561327557600080fd5b8235915060208301356131cf81613253565b6000806000806080858703121561329d57600080fd5b8435935060208501356132af81613179565b92506040850135915060608501356132c6816131da565b939692955090935050565b6000602082840312156132e357600080fd5b81356121e48161318c565b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b0381118282101715613326576133266132ee565b60405290565b60405161016081016001600160401b0381118282101715613326576133266132ee565b60405161010081016001600160401b0381118282101715613326576133266132ee565b604051601f8201601f191681016001600160401b038111828210171561339a5761339a6132ee565b604052919050565b60006001600160401b038211156133bb576133bb6132ee565b50601f01601f191660200190565b6000602082840312156133db57600080fd5b81356001600160401b038111156133f157600080fd5b8201601f8101841361340257600080fd5b8035613415613410826133a2565b613372565b81815285602083850101111561342a57600080fd5b81602084016020830137600091810160200191909152949350505050565b6000806040838503121561345b57600080fd5b823561346681613179565b946020939093013593505050565b6000806020838503121561348757600080fd5b82356001600160401b038082111561349e57600080fd5b818501915085601f8301126134b257600080fd5b8135818111156134c157600080fd5b8660208285010111156134d357600080fd5b60209290920196919550909350505050565b6000806000606084860312156134fa57600080fd5b833561350581613179565b925060208401356135158161318c565b9150604084013561352581613253565b809150509250925092565b60006020828403121561354257600080fd5b81356121e481613179565b801515811461318957600080fd5b6000806040838503121561356e57600080fd5b823561357981613179565b915060208301356131cf8161354d565b6000806040838503121561359c57600080fd5b82356134668161318c565b6000608082840312156135b957600080fd5b604051608081018181106001600160401b03821117156135db576135db6132ee565b60405282356135e981613253565b808252506020830135602082015260408301356040820152606083013560608201528091505092915050565b60005b83811015613630578181015183820152602001613618565b50506000910152565b60008151808452613651816020860160208601613615565b601f01601f19169290920160200192915050565b6020815260006121e46020830184613639565b6020808252825182820181905260009190848201906040850190845b818110156136b95783516001600160a01b031683529284019291840191600101613694565b50909695505050505050565b6000806000606084860312156136da57600080fd5b83356136e581613179565b925060208401356136f58161318c565b929592945050506040919091013590565b60208082526014908201527331b0b63632b9103737ba103a34329037bbb732b960611b604082015260600190565b6020808252600b908201526a3bb937b7339031b430b4b760a91b604082015260600190565b60006020828403121561376b57600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b60ff8281168282160390811115610af757610af7613772565b600181815b808511156137dc5781600019048211156137c2576137c2613772565b808516156137cf57918102915b93841c93908002906137a6565b509250929050565b6000826137f357506001610af7565b8161380057506000610af7565b816001811461381657600281146138205761383c565b6001915050610af7565b60ff84111561383157613831613772565b50506001821b610af7565b5060208310610133831016604e8410600b841016171561385f575081810a610af7565b61386983836137a1565b806000190482111561387d5761387d613772565b029392505050565b60006121e460ff8416836137e4565b6000826138b157634e487b7160e01b600052601260045260246000fd5b500490565b8082028115828204841417610af757610af7613772565b81810381811115610af757610af7613772565b6000602082840312156138f257600080fd5b81516121e48161318c565b80820180821115610af757610af7613772565b6020808252601290820152711d1bdad95b881b9bdd081858d8d95c1d195960721b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052603160045260246000fd5b6000825161397a818460208701613615565b9190910192915050565b805161398f81613253565b919050565b6000602082840312156139a657600080fd5b81516121e481613253565b60018060a01b038716815285602082015261ffff8516604082015283606082015263ffffffff8316608082015260c060a082015260006139f460c0830184613639565b98975050505050505050565b80516001600160401b038116811461398f57600080fd5b600060208284031215613a2957600080fd5b6121e482613a00565b805161398f816131da565b805161398f81613179565b600082601f830112613a5957600080fd5b8151613a67613410826133a2565b818152846020838601011115613a7c57600080fd5b612219826020830160208701613615565b600082601f830112613a9e57600080fd5b815160206001600160401b03821115613ab957613ab96132ee565b613ac7818360051b01613372565b82815260079290921b84018101918181019086841115613ae657600080fd5b8286015b84811015613b4b5760808189031215613b035760008081fd5b613b0b613304565b815181528482015185820152604080830151613b2681613253565b90820152606082810151613b3981613253565b90820152835291830191608001613aea565b509695505050505050565b600060208284031215613b6857600080fd5b81516001600160401b0380821115613b7f57600080fd5b908301906101608286031215613b9457600080fd5b613b9c61332c565b613ba583613984565b8152613bb360208401613a32565b6020820152613bc460408401613a32565b6040820152613bd560608401613a3d565b606082015260808301516080820152613bf060a08401613a00565b60a0820152613c0160c08401613984565b60c082015260e083015182811115613c1857600080fd5b613c2487828601613a48565b60e083015250610100613c38818501613a32565b908201526101208381015183811115613c5057600080fd5b613c5c88828701613a8d565b91830191909152506101409283015192810192909252509392505050565b600060208284031215613c8c57600080fd5b81516001600160401b03811115613ca257600080fd5b61221984828501613a48565b600060208284031215613cc057600080fd5b81516001600160401b0380821115613cd757600080fd5b908301906101008286031215613cec57600080fd5b613cf461334f565b613cfd83613984565b81526020830151602082015260408301516040820152613d1f60608401613a3d565b606082015260808301516080820152613d3a60a08401613a3d565b60a082015260c083015160c082015260e083015182811115613d5b57600080fd5b613d6787828601613a48565b60e08301525095945050505050565b600060208284031215613d8857600080fd5b81516121e48161354d56fea2646970667358221220043ecf0d1429bfc7866a5607e98ae2b1cca0eeef46c7279047c0211efe0f076764736f6c63430008110033

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

000000000000000000000000000000000000000000000000000000000000000200000000000000000000000098f3c9e6e3face36baad05fe09d375ef1464288b0000000000000000000000003ee18b2214aff97000d974cf647e7c347e8fa585000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000001

-----Decoded View---------------
Arg [0] : chainId (uint16): 2
Arg [1] : wormhole (address): 0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B
Arg [2] : tokenBridge_ (address): 0x3ee18B2214AFF97000D974cf647E7C347E8fa585
Arg [3] : wethAddress (address): 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Arg [4] : unwrapWeth_ (bool): True

-----Encoded View---------------
5 Constructor Arguments found :
Arg [0] : 0000000000000000000000000000000000000000000000000000000000000002
Arg [1] : 00000000000000000000000098f3c9e6e3face36baad05fe09d375ef1464288b
Arg [2] : 0000000000000000000000003ee18b2214aff97000d974cf647e7c347e8fa585
Arg [3] : 000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
Arg [4] : 0000000000000000000000000000000000000000000000000000000000000001


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.