Avalanche Fuji Testnet

Contract

0x43B2A45ac6D463902826e65Aaa31Ae0Ef846C6Ec
Source Code Source Code

Overview

AVAX Balance

Avalanche C-Chain LogoAvalanche C-Chain LogoAvalanche C-Chain Logo0 AVAX

Token Holdings

More Info

Multichain Info

N/A
Transaction Hash
Method
Block
From
To
Amount
Send515300892026-02-05 22:25:034 days ago1770330303IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send514864842026-02-04 21:38:105 days ago1770241090IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send514849312026-02-04 20:42:245 days ago1770237744IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send514847482026-02-04 20:36:445 days ago1770237404IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send514815742026-02-04 18:37:245 days ago1770230244IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send514650242026-02-04 8:25:306 days ago1770193530IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send514649732026-02-04 8:22:526 days ago1770193372IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send514649492026-02-04 8:21:346 days ago1770193294IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send514494972026-02-03 19:50:256 days ago1770148225IN
0x43B2A45a...Ef846C6Ec
0 AVAX0.000152640.75
Send514490692026-02-03 19:28:046 days ago1770146884IN
0x43B2A45a...Ef846C6Ec
0 AVAX0.00012720.625
Send514489542026-02-03 19:21:076 days ago1770146467IN
0x43B2A45a...Ef846C6Ec
0 AVAX0.000127480.625
Send514170512026-02-02 17:17:197 days ago1770052639IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send514117402026-02-02 13:40:348 days ago1770039634IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send513020882026-01-30 19:45:0310 days ago1769802303IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send513016882026-01-30 19:28:5410 days ago1769801334IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send512680222026-01-29 21:39:1211 days ago1769722752IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send512402252026-01-29 5:05:3212 days ago1769663132IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send512266872026-01-28 19:21:4512 days ago1769628105IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send512210932026-01-28 15:51:1412 days ago1769615474IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send512205712026-01-28 15:36:5012 days ago1769614610IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send511784212026-01-27 18:58:3213 days ago1769540312IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send511778552026-01-27 18:40:4813 days ago1769539248IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send511195292026-01-26 13:48:1815 days ago1769435298IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send509809992026-01-23 15:45:5217 days ago1769183152IN
0x43B2A45a...Ef846C6Ec
0 AVAX00
Send507862312026-01-19 17:53:3221 days ago1768845212IN
0x43B2A45a...Ef846C6Ec
0 AVAX00

Latest 25 internal transactions (View All)

Parent Transaction Hash Block From To Amount
516671482026-02-09 17:53:4220 hrs ago1770659622
0x43B2A45a...Ef846C6Ec
0 AVAX
516671482026-02-09 17:53:4220 hrs ago1770659622
0x43B2A45a...Ef846C6Ec
0 AVAX
516671482026-02-09 17:53:4220 hrs ago1770659622
0x43B2A45a...Ef846C6Ec
0 AVAX
516671482026-02-09 17:53:4220 hrs ago1770659622
0x43B2A45a...Ef846C6Ec
0 AVAX
516671482026-02-09 17:53:4220 hrs ago1770659622
0x43B2A45a...Ef846C6Ec
0 AVAX
516671482026-02-09 17:53:4220 hrs ago1770659622
0x43B2A45a...Ef846C6Ec
0 AVAX
516671332026-02-09 17:52:5420 hrs ago1770659574
0x43B2A45a...Ef846C6Ec
0 AVAX
516671332026-02-09 17:52:5420 hrs ago1770659574
0x43B2A45a...Ef846C6Ec
0 AVAX
516671332026-02-09 17:52:5420 hrs ago1770659574
0x43B2A45a...Ef846C6Ec
0 AVAX
516671332026-02-09 17:52:5420 hrs ago1770659574
0x43B2A45a...Ef846C6Ec
0 AVAX
516671332026-02-09 17:52:5420 hrs ago1770659574
0x43B2A45a...Ef846C6Ec
0 AVAX
516671332026-02-09 17:52:5420 hrs ago1770659574
0x43B2A45a...Ef846C6Ec
0 AVAX
516668272026-02-09 17:40:4420 hrs ago1770658844
0x43B2A45a...Ef846C6Ec
0 AVAX
516668272026-02-09 17:40:4420 hrs ago1770658844
0x43B2A45a...Ef846C6Ec
0 AVAX
516668272026-02-09 17:40:4420 hrs ago1770658844
0x43B2A45a...Ef846C6Ec
0 AVAX
516668272026-02-09 17:40:4420 hrs ago1770658844
0x43B2A45a...Ef846C6Ec
0 AVAX
516668272026-02-09 17:40:4420 hrs ago1770658844
0x43B2A45a...Ef846C6Ec
0 AVAX
516668272026-02-09 17:40:4420 hrs ago1770658844
0x43B2A45a...Ef846C6Ec
0 AVAX
516668112026-02-09 17:40:2520 hrs ago1770658825
0x43B2A45a...Ef846C6Ec
0 AVAX
516668112026-02-09 17:40:2520 hrs ago1770658825
0x43B2A45a...Ef846C6Ec
0 AVAX
516668112026-02-09 17:40:2520 hrs ago1770658825
0x43B2A45a...Ef846C6Ec
0 AVAX
516668112026-02-09 17:40:2520 hrs ago1770658825
0x43B2A45a...Ef846C6Ec
0 AVAX
516668112026-02-09 17:40:2520 hrs ago1770658825
0x43B2A45a...Ef846C6Ec
0 AVAX
516668112026-02-09 17:40:2520 hrs ago1770658825
0x43B2A45a...Ef846C6Ec
0 AVAX
516587452026-02-09 10:54:2827 hrs ago1770634468
0x43B2A45a...Ef846C6Ec
0 AVAX
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
ERC20TokenHome

Compiler Version
v0.8.18+commit.87f61d96

Optimization Enabled:
Yes with 200 runs

Other Settings:
paris EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {TokenHome} from "./TokenHome.sol";
import {IERC20TokenHome} from "./interfaces/IERC20TokenHome.sol";
import {IERC20SendAndCallReceiver} from "../interfaces/IERC20SendAndCallReceiver.sol";
import {
    SendTokensInput,
    SendAndCallInput,
    SingleHopCallMessage
} from "../interfaces/ITokenTransferrer.sol";
import {IERC20} from "@openzeppelin/[email protected]/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/[email protected]/token/ERC20/utils/SafeERC20.sol";
import {SafeERC20TransferFrom} from "../utils/SafeERC20TransferFrom.sol";
import {CallUtils} from "../utils/CallUtils.sol";

/**
 * @title ERC20TokenHome
 * @notice An {IERC20TokenHome} implementation that locks a specified ERC20 token to be sent to
 * TokenRemote instances on other chains.
 * @custom:security-contact https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/SECURITY.md
 */
contract ERC20TokenHome is IERC20TokenHome, TokenHome {
    using SafeERC20 for IERC20;

    /// @notice The ERC20 token this home contract transfers to TokenRemote instances.
    IERC20 public immutable token;

    /**
     * @notice Initializes the token TokenHome instance to send ERC20 tokens to TokenRemote instances on other chains.
     * @param teleporterRegistryAddress The current blockchain ID's Teleporter registry
     * address. See here for details: https://github.com/ava-labs/teleporter/tree/main/contracts/src/Teleporter/upgrades
     * @param teleporterManager Address that manages this contract's integration with the
     * Teleporter registry and Teleporter versions.
     * @param tokenAddress_ The ERC20 token contract address to be transferred by the home.
     * @param tokenDecimals_ The number of decimals for the ERC20 token
     */
    constructor(
        address teleporterRegistryAddress,
        address teleporterManager,
        address tokenAddress_,
        uint8 tokenDecimals_
    ) TokenHome(teleporterRegistryAddress, teleporterManager, tokenAddress_, tokenDecimals_) {
        token = IERC20(tokenAddress);
    }

    /**
     * @dev See {IERC20TokenTransferrer-send}
     */
    function send(SendTokensInput calldata input, uint256 amount) external {
        _send(input, amount);
    }

    /**
     * @dev See {IERC20TokenTransferrer-sendAndCall}
     */
    function sendAndCall(SendAndCallInput calldata input, uint256 amount) external {
        _sendAndCall({
            sourceBlockchainID: blockchainID,
            originTokenTransferrerAddress: address(this),
            originSenderAddress: _msgSender(),
            input: input,
            amount: amount
        });
    }

    /**
     * @dev See {IERC20TokenHome-addCollateral}
     */
    function addCollateral(
        bytes32 remoteBlockchainID,
        address remoteTokenTransferrerAddress,
        uint256 amount
    ) external {
        _addCollateral(remoteBlockchainID, remoteTokenTransferrerAddress, amount);
    }

    /**
     * @dev See {TokenHome-_deposit}
     */
    function _deposit(uint256 amount) internal virtual override returns (uint256) {
        return SafeERC20TransferFrom.safeTransferFrom(token, _msgSender(), amount);
    }

    /**
     * @dev See {TokenHome-_withdraw}
     */
    function _withdraw(address recipient, uint256 amount) internal virtual override {
        emit TokensWithdrawn(recipient, amount);
        token.safeTransfer(recipient, amount);
    }

    /**
     * @dev See {TokenHome-_handleSendAndCall}
     *
     * Approves the recipient contract to spend the amount of tokens from this contract,
     * and calls {IERC20SendAndCallReceiver-receiveTokens} on the recipient contract.
     * If the call fails or doesn't spend all of the tokens, the remaining amount is
     * sent to the fallback recipient.
     */
    function _handleSendAndCall(
        SingleHopCallMessage memory message,
        uint256 amount
    ) internal virtual override {
        // Approve the recipient contract to spend the amount from the collateral.
        SafeERC20.safeIncreaseAllowance(token, message.recipientContract, amount);

        // Encode the call to {IERC20SendAndCallReceiver-receiveTokens}
        bytes memory payload = abi.encodeCall(
            IERC20SendAndCallReceiver.receiveTokens,
            (
                message.sourceBlockchainID,
                message.originTokenTransferrerAddress,
                message.originSenderAddress,
                address(token),
                amount,
                message.recipientPayload
            )
        );

        // Call the recipient contract with the given payload and gas amount.
        bool success = CallUtils._callWithExactGas(
            message.recipientGasLimit, message.recipientContract, payload
        );

        uint256 remainingAllowance = token.allowance(address(this), message.recipientContract);

        // Reset the recipient contract allowance to 0.
        // Use of {safeApprove} is okay to reset the allowance to 0.
        SafeERC20.safeApprove(token, message.recipientContract, 0);

        if (success) {
            emit CallSucceeded(message.recipientContract, amount);
        } else {
            emit CallFailed(message.recipientContract, amount);
        }

        // Transfer any remaining allowance to the fallback recipient. This will be the
        // full amount if the call failed.
        if (remainingAllowance > 0) {
            token.safeTransfer(message.fallbackRecipient, remainingAllowance);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated 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;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * The default value of {decimals} is 18. To select a different value for
     * {decimals} you should overload it.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the value {ERC20} uses, unless this function is
     * overridden;
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _balances[account] += amount;
        }
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
            // Overflow not possible: amount <= accountBalance <= totalSupply.
            _totalSupply -= amount;
        }

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance amount in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}
}

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

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

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

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// (c) 2022-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

struct WarpMessage {
  bytes32 sourceChainID;
  address originSenderAddress;
  bytes payload;
}

struct WarpBlockHash {
  bytes32 sourceChainID;
  bytes32 blockHash;
}

interface IWarpMessenger {
  event SendWarpMessage(address indexed sender, bytes32 indexed messageID, bytes message);

  // sendWarpMessage emits a request for the subnet to send a warp message from [msg.sender]
  // with the specified parameters.
  // This emits a SendWarpMessage log from the precompile. When the corresponding block is accepted
  // the Accept hook of the Warp precompile is invoked with all accepted logs emitted by the Warp
  // precompile.
  // Each validator then adds the UnsignedWarpMessage encoded in the log to the set of messages
  // it is willing to sign for an off-chain relayer to aggregate Warp signatures.
  function sendWarpMessage(bytes calldata payload) external returns (bytes32 messageID);

  // getVerifiedWarpMessage parses the pre-verified warp message in the
  // predicate storage slots as a WarpMessage and returns it to the caller.
  // If the message exists and passes verification, returns the verified message
  // and true.
  // Otherwise, returns false and the empty value for the message.
  function getVerifiedWarpMessage(uint32 index) external view returns (WarpMessage calldata message, bool valid);

  // getVerifiedWarpBlockHash parses the pre-verified WarpBlockHash message in the
  // predicate storage slots as a WarpBlockHash message and returns it to the caller.
  // If the message exists and passes verification, returns the verified message
  // and true.
  // Otherwise, returns false and the empty value for the message.
  function getVerifiedWarpBlockHash(
    uint32 index
  ) external view returns (WarpBlockHash calldata warpBlockHash, bool valid);

  // getBlockchainID returns the snow.Context BlockchainID of this chain.
  // This blockchainID is the hash of the transaction that created this blockchain on the P-Chain
  // and is not related to the Ethereum ChainID.
  function getBlockchainID() external view returns (bytes32 blockchainID);
}

// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

// A message receipt identifies the message that was delivered by its nonce,
// and the address that can redeem the reward for that message.
struct TeleporterMessageReceipt {
    uint256 receivedMessageNonce;
    address relayerRewardAddress;
}

// Represents all of the information required for submitting a Teleporter message
// to be sent to the given destination chain ID and address. Includes the fee
// information for the message, the amount of gas the relayer must provide to execute
// the message on the destination chain, the relayer accounts allowed to deliver the
// message, and the message data itself.
struct TeleporterMessageInput {
    bytes32 destinationBlockchainID;
    address destinationAddress;
    TeleporterFeeInfo feeInfo;
    uint256 requiredGasLimit;
    address[] allowedRelayerAddresses;
    bytes message;
}

// Represents a message sent or received by an implementation of {ITeleporterMessenger}.
struct TeleporterMessage {
    uint256 messageNonce;
    address originSenderAddress;
    bytes32 destinationBlockchainID;
    address destinationAddress;
    uint256 requiredGasLimit;
    address[] allowedRelayerAddresses;
    TeleporterMessageReceipt[] receipts;
    bytes message;
}

// Represents the fee information associated to a given Teleporter message.
// The contract address is the asset contract the fee will be paid in, and
// the amount is the amount of that specified asset.
struct TeleporterFeeInfo {
    address feeTokenAddress;
    uint256 amount;
}

/**
 * @dev Interface that describes functionalities for a cross-chain messenger implementing the Teleporter protcol.
 *
 * @custom:security-contact https://github.com/ava-labs/teleporter/blob/main/SECURITY.md
 */
interface ITeleporterMessenger {
    /**
     * @notice Emitted when the blockchain ID of the contract instance is initialized using the Warp precompile.
     */
    event BlockchainIDInitialized(bytes32 indexed blockchainID);

    /**
     * @notice Emitted when sending a Teleporter message cross-chain.
     */
    event SendCrossChainMessage(
        bytes32 indexed messageID,
        bytes32 indexed destinationBlockchainID,
        TeleporterMessage message,
        TeleporterFeeInfo feeInfo
    );

    /**
     * @notice Emitted when an additional fee amount is added to a Teleporter message that had previously
     * been sent, but not yet delivered to the destination chain.
     */
    event AddFeeAmount(bytes32 indexed messageID, TeleporterFeeInfo updatedFeeInfo);

    /**
     * @notice Emitted when a Teleporter message is being delivered on the destination chain to an address,
     * but message execution fails. Failed messages can then be retried with `retryMessageExecution`
     */
    event MessageExecutionFailed(
        bytes32 indexed messageID, bytes32 indexed sourceBlockchainID, TeleporterMessage message
    );

    /**
     * @notice Emitted when a Teleporter message is successfully executed with the
     * specified destination address and message call data. This can occur either when
     * the message is initially received, or on a retry attempt.
     *
     * Each message received can be executed successfully at most once.
     */
    event MessageExecuted(bytes32 indexed messageID, bytes32 indexed sourceBlockchainID);

    /**
     * @notice Emitted when a TeleporterMessage is successfully received.
     */
    event ReceiveCrossChainMessage(
        bytes32 indexed messageID,
        bytes32 indexed sourceBlockchainID,
        address indexed deliverer,
        address rewardRedeemer,
        TeleporterMessage message
    );

    /**
     * @notice Emitted when a receipt is marked as received on the source chain that sent the
     * corresponding Teleporter message.
     */
    event ReceiptReceived(
        bytes32 indexed messageID,
        bytes32 indexed destinationBlockchainID,
        address indexed relayerRewardAddress,
        TeleporterFeeInfo feeInfo
    );

    /**
     * @notice Emitted when an account redeems accumulated relayer rewards.
     */
    event RelayerRewardsRedeemed(address indexed redeemer, address indexed asset, uint256 amount);

    /**
     * @notice Called by transactions to initiate the sending of a cross-chain message.
     * @return The message ID of the newly sent message.
     */
    function sendCrossChainMessage(TeleporterMessageInput calldata messageInput)
        external
        returns (bytes32);

    /**
     * @notice Called by transactions to retry the sending of a cross-chain message.
     *
     * @dev Retriggers the sending of a message previously emitted by sendCrossChainMessage that has not yet been acknowledged
     * with a receipt from the destination chain. This may be necessary in the unlikely event that less than the required
     * threshold of stake weight successfully inserted the message in their messages DB at the time of the first submission.
     * The message is checked to have already been previously submitted by comparing its message hash against those kept in
     * state until a receipt is received for the message.
     */
    function retrySendCrossChainMessage(TeleporterMessage calldata message) external;

    /**
     * @notice Adds the additional fee amount to the amount to be paid to the relayer that delivers
     * the given message ID to the destination chain.
     *
     * @dev The fee token address must be the same asset type as the fee asset specified in the original
     * call to sendCrossChainMessage. Reverts if the message doesn't exist or there is already
     * receipt of delivery of the message.
     */
    function addFeeAmount(
        bytes32 messageID,
        address feeTokenAddress,
        uint256 additionalFeeAmount
    ) external;

    /**
     * @notice Receives a cross-chain message, and marks the `relayerRewardAddress` for fee reward for a successful delivery.
     *
     * @dev The message specified by `messageIndex` must be provided at that index in the access list storage slots of the transaction,
     * and is verified in the precompile predicate.
     */
    function receiveCrossChainMessage(uint32 messageIndex, address relayerRewardAddress) external;

    /**
     * @notice Retries the execution of a previously delivered message by verifying the payload matches
     * the hash of the payload originally delivered, and calling the destination address again.
     *
     * @dev Intended to be used if message excution failed on initial delivery of the Teleporter message.
     * For example, this may occur if the original required gas limit was not sufficient for the message
     * execution, or if the destination address did not contain a contract, but a compatible contract
     * was later deployed to that address. Messages are ensured to be successfully executed at most once.
     */
    function retryMessageExecution(
        bytes32 sourceBlockchainID,
        TeleporterMessage calldata message
    ) external;

    /**
     * @notice Sends the receipts for the given `messageIDs`.
     *
     * @dev Sends the specified message receipts in a new message (with an empty payload) back to the source chain.
     * This is intended for use in sending receipts that have not been sent in a timely manner by the standard
     * receipt delivery mechanism.
     * @return The message ID of the newly sent message.
     */
    function sendSpecifiedReceipts(
        bytes32 sourceBlockchainID,
        bytes32[] calldata messageIDs,
        TeleporterFeeInfo calldata feeInfo,
        address[] calldata allowedRelayerAddresses
    ) external returns (bytes32);

    /**
     * @notice Sends any fee amount rewards for the given fee asset out to the caller.
     */
    function redeemRelayerRewards(address feeTokenAddress) external;

    /**
     * @notice Gets the hash of a given message stored in the EVM state, if the message exists.
     * @return The message hash
     */
    function getMessageHash(bytes32 messageID) external view returns (bytes32);

    /**
     * @notice Checks whether or not the given message has been received by this chain.
     * @return Boolean representing if the given message has been received.
     */
    function messageReceived(bytes32 messageID) external view returns (bool);

    /**
     * @notice Returns the address the relayer reward should be sent to on the source chain
     * for a given message, assuming that the message has already been delivered.
     * @return The relayer reward address for the given message.
     */
    function getRelayerRewardAddress(bytes32 messageID) external view returns (address);

    /**
     * @notice Gets the current reward amount of a given fee asset that is redeemable by the given relayer.
     * @return The amount of the fee asset redeemable by the specified relayer.
     */
    function checkRelayerRewardAmount(
        address relayer,
        address feeTokenAddress
    ) external view returns (uint256);

    /**
     * @notice Gets the fee token address and amount for a given sent message.
     * @return The fee token address and fee amount for a the given sent message ID.
     * If the message ID is not found, zero address and amount values are returned.
     */
    function getFeeInfo(bytes32 messageID) external view returns (address, uint256);

    /**
     * @notice Gets the message ID that would currently be used for the next message sent from the contract
     * instance to the given destination blockchain.
     *
     * @dev This message ID may never be used in the event that the next call to sendCrossChainMessage in a
     * transaction uses a different destination blockchain. The current value as returned by this function will
     * change with each successful call to sendCrossChainMessage.
     * @return The specified message ID.
     */
    function getNextMessageID(bytes32 destinationBlockchainID) external view returns (bytes32);

    /**
     * @notice Gets the number of receipts that are waiting to be sent to the given source chain ID.
     * @return Size of the given queue.
     */
    function getReceiptQueueSize(bytes32 sourceBlockchainID) external view returns (uint256);

    /**
     * @notice Gets the receipt at the given index in the queue for the given source chain ID.
     * @return The receipt requested.
     */
    function getReceiptAtIndex(
        bytes32 sourceBlockchainID,
        uint256 index
    ) external view returns (TeleporterMessageReceipt memory);
}

// (c) 2022-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

/**
 * @dev Interface that cross-chain applications must implement to receive messages from Teleporter.
 *
 * @custom:security-contact https://github.com/ava-labs/teleporter/blob/main/SECURITY.md
 */
interface ITeleporterReceiver {
    /**
     * @dev Called by TeleporterMessenger on the receiving chain.
     *
     * @param sourceBlockchainID is provided by the TeleporterMessenger contract.
     * @param originSenderAddress is provided by the TeleporterMessenger contract.
     * @param message is the TeleporterMessage payload set by the sender.
     */
    function receiveTeleporterMessage(
        bytes32 sourceBlockchainID,
        address originSenderAddress,
        bytes calldata message
    ) external;
}

File 14 of 26 : TeleporterOwnerUpgradeable.sol
// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {TeleporterUpgradeable} from "./TeleporterUpgradeable.sol";
import {Ownable} from "@openzeppelin/[email protected]/access/Ownable.sol";

/**
 * @dev Contract that inherits {TeleporterUpgradeable} and allows
 * only owners of the contract to update the minimum Teleporter version.
 *
 * @custom:security-contact https://github.com/ava-labs/teleporter/blob/main/SECURITY.md
 */
abstract contract TeleporterOwnerUpgradeable is TeleporterUpgradeable, Ownable {
    constructor(
        address teleporterRegistryAddress,
        address initialOwner
    ) TeleporterUpgradeable(teleporterRegistryAddress) {
        transferOwnership(initialOwner);
    }

    /**
     * @dev See {TeleporterUpgradeable-_checkTeleporterUpgradeAccess}
     *
     * Checks that the caller is the owner of the contract for upgrade access.
     */
    function _checkTeleporterUpgradeAccess() internal view virtual override {
        _checkOwner();
    }
}

// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {ITeleporterMessenger} from "../ITeleporterMessenger.sol";
import {
    IWarpMessenger,
    WarpMessage
} from "@avalabs/[email protected]/contracts/interfaces/IWarpMessenger.sol";

// Registry entry that represents a mapping between protocolAddress and Teleporter version.
struct ProtocolRegistryEntry {
    uint256 version;
    address protocolAddress;
}

/**
 * @dev TeleporterRegistry contract provides an upgrade mechanism for {ITeleporterMessenger} contracts
 * through Warp off-chain messages
 *
 * @custom:security-contact https://github.com/ava-labs/teleporter/blob/main/SECURITY.md
 */
contract TeleporterRegistry {
    /**
     * @notice Address that the off-chain Warp message sets as the "source" address.
     * @dev The address is not owned by any EOA or smart contract account, so it
     * cannot possibly be the source address of any other Warp message emitted by the VM.
     */
    address public constant VALIDATORS_SOURCE_ADDRESS = address(0);

    /**
     * @notice Warp precompile used for sending and receiving Warp messages.
     */
    IWarpMessenger public constant WARP_MESSENGER =
        IWarpMessenger(0x0200000000000000000000000000000000000005);

    /**
     * @notice The blockchain ID of the chain the contract is deployed on.
     */
    bytes32 public immutable blockchainID;

    /**
     * @notice The maximum version increment allowed when adding a new protocol version.
     */
    uint256 public constant MAX_VERSION_INCREMENT = 500;

    /**
     * @notice The latest protocol version.
     * @dev 0 means no protocol version has been added, and isn't a valid version.
     */
    uint256 public latestVersion;

    /**
     * @notice Mappings that keep track of the protocol version and corresponding contract address.
     */
    mapping(uint256 version => address protocolAddress) private _versionToAddress;
    mapping(address protocolAddress => uint256 version) private _addressToVersion;

    /**
     * @notice Emitted when a new protocol version is added to the registry.
     */
    event AddProtocolVersion(uint256 indexed version, address indexed protocolAddress);

    /**
     * @notice Emitted when the latest version is updated.
     */
    event LatestVersionUpdated(uint256 indexed oldVersion, uint256 indexed newVersion);

    /**
     * @dev Initializes the contract by setting `blockchainID` and `latestVersion`.
     * Also adds the initial protocol versions to the registry.
     */
    constructor(ProtocolRegistryEntry[] memory initialEntries) {
        blockchainID = WARP_MESSENGER.getBlockchainID();

        uint256 length = initialEntries.length;
        for (uint256 i; i < length; ++i) {
            _addToRegistry(initialEntries[i]);
        }
    }

    /**
     * @dev Receives a Warp off-chain message containing a new protocol version and address to be registered,
     * and adds the new values to the respective mappings.
     * If a version is greater than the current latest version, it will be set as the latest version.
     * If a version is less than the current latest version, it is added to the registry, but
     * doesn't change the latest version.
     *
     * Emits a {AddProtocolVersion} event when successful.
     * Emits a {LatestVersionUpdated} event when a new protocol version greater than the current latest version is added.
     * Requirements:
     *
     * - a valid Warp off-chain message must be provided.
     * - source chain ID must be the same as the blockchain ID of the registry.
     * - origin sender address must be the same as the `VALIDATORS_SOURCE_ADDRESS`.
     * - destination address must be the same as the address of this registry.
     */
    function addProtocolVersion(uint32 messageIndex) external {
        // Get the verified Warp message, and check that it was sent to this registry via a Warp off-chain message.
        (WarpMessage memory message, bool success) =
            WARP_MESSENGER.getVerifiedWarpMessage(messageIndex);
        require(success, "TeleporterRegistry: invalid warp message");
        require(
            message.sourceChainID == blockchainID, "TeleporterRegistry: invalid source chain ID"
        );
        // Check that the message is sent through a Warp off-chain message.
        require(
            message.originSenderAddress == VALIDATORS_SOURCE_ADDRESS,
            "TeleporterRegistry: invalid origin sender address"
        );

        (ProtocolRegistryEntry memory entry, address destinationAddress) =
            abi.decode(message.payload, (ProtocolRegistryEntry, address));

        // Check that the message is sent to the registry.
        require(
            destinationAddress == address(this), "TeleporterRegistry: invalid destination address"
        );

        _addToRegistry(entry);
    }

    /**
     * @dev Gets the latest {ITeleporterMessenger} contract.
     */
    function getLatestTeleporter() external view returns (ITeleporterMessenger) {
        return ITeleporterMessenger(getAddressFromVersion(latestVersion));
    }

    /**
     * @dev Gets the {ITeleporterMessenger} contract of the given `version`.
     * Requirements:
     *
     * - `version` must be a valid registered version.
     */
    function getTeleporterFromVersion(uint256 version)
        external
        view
        returns (ITeleporterMessenger)
    {
        return ITeleporterMessenger(getAddressFromVersion(version));
    }

    /**
     * @dev Gets the address of a protocol version.
     * Requirements:
     *
     * - `version` must be a valid registered version.
     */
    function getAddressFromVersion(uint256 version) public view returns (address) {
        require(version != 0, "TeleporterRegistry: zero version");
        address protocolAddress = _versionToAddress[version];
        require(protocolAddress != address(0), "TeleporterRegistry: version not found");
        return protocolAddress;
    }

    /**
     * @dev Gets the version of the given `protocolAddress`.
     * Requirements:
     *
     * - `protocolAddress` must be a valid registered protocol address.
     */
    function getVersionFromAddress(address protocolAddress) public view returns (uint256) {
        require(protocolAddress != address(0), "TeleporterRegistry: zero protocol address");
        uint256 version = _addressToVersion[protocolAddress];
        require(version != 0, "TeleporterRegistry: protocol address not found");
        return version;
    }

    /**
     * @dev Adds the new protocol version address to the registry.
     * Updates latest version if the version is greater than the current latest version.
     *
     * Emits a {AddProtocolVersion} event when successful.
     * Emits a {LatestVersionUpdated} event when a new protocol version greater than the current latest version is added.
     * Note: `entry.protocolAddress` doesn't have to be a contract address, allowing a new protocol address to be registered before the contract is deployed.
     * Requirements:
     *
     * - `entry.version` is not zero
     * - `entry.version` is not already registered
     * - `entry.protocolAddress` is not zero address
     */
    function _addToRegistry(ProtocolRegistryEntry memory entry) private {
        require(entry.version != 0, "TeleporterRegistry: zero version");
        // Check that the version has not previously been registered.
        require(
            _versionToAddress[entry.version] == address(0),
            "TeleporterRegistry: version already exists"
        );
        require(entry.protocolAddress != address(0), "TeleporterRegistry: zero protocol address");

        uint256 latestVersion_ = latestVersion;
        require(
            entry.version <= latestVersion_ + MAX_VERSION_INCREMENT,
            "TeleporterRegistry: version increment too high"
        );

        _versionToAddress[entry.version] = entry.protocolAddress;

        // Since a protocol address can be registered multiple times,
        // only update the version if the new version is greater than the current version.
        if (entry.version > _addressToVersion[entry.protocolAddress]) {
            _addressToVersion[entry.protocolAddress] = entry.version;
        }
        emit AddProtocolVersion(entry.version, entry.protocolAddress);

        // Set latest version if the version is greater than the current latest version.
        if (entry.version > latestVersion_) {
            latestVersion = entry.version;
            emit LatestVersionUpdated(latestVersion_, entry.version);
        }
    }
}

// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {TeleporterRegistry} from "./TeleporterRegistry.sol";
import {ITeleporterReceiver} from "../ITeleporterReceiver.sol";
import {ITeleporterMessenger, TeleporterMessageInput} from "../ITeleporterMessenger.sol";
import {Context} from "@openzeppelin/[email protected]/utils/Context.sol";
import {ReentrancyGuard} from "@openzeppelin/[email protected]/security/ReentrancyGuard.sol";
import {SafeERC20} from "@openzeppelin/[email protected]/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/[email protected]/token/ERC20/IERC20.sol";

/**
 * @dev TeleporterUpgradeable provides upgrade utility for applications built on top
 * of the Teleporter protocol by integrating with the {TeleporterRegistry}.
 *
 * This contract is intended to be inherited by other contracts that wish to use the
 * upgrade mechanism. It provides an interface that restricts access to only Teleporter
 * versions that are greater than or equal to `minTeleporterVersion`.
 *
 * @custom:security-contact https://github.com/ava-labs/teleporter/blob/main/SECURITY.md
 */
abstract contract TeleporterUpgradeable is Context, ITeleporterReceiver, ReentrancyGuard {
    using SafeERC20 for IERC20;

    // The Teleporter registry contract manages different Teleporter contract versions.
    TeleporterRegistry public immutable teleporterRegistry;
    /**
     * @dev A mapping that keeps track of paused Teleporter addresses.
     */
    mapping(address teleporterAddress => bool paused) private _pausedTeleporterAddresses;

    /**
     * @dev The minimum required Teleporter version that the contract is allowed
     * to receive messages from. Should only be updated by `_setMinTeleporterVersion`
     */
    uint256 private _minTeleporterVersion;

    /**
     * @dev Emitted when `minTeleporterVersion` is updated.
     */
    event MinTeleporterVersionUpdated(
        uint256 indexed oldMinTeleporterVersion, uint256 indexed newMinTeleporterVersion
    );

    /**
     * @dev Emitted when a Teleporter address is paused.
     */
    event TeleporterAddressPaused(address indexed teleporterAddress);

    /**
     * @dev Emitted when a Teleporter address is unpaused.
     */
    event TeleporterAddressUnpaused(address indexed teleporterAddress);

    /**
     * @dev Initializes the {TeleporterUpgradeable} contract by getting `teleporterRegistry`
     * instance and setting `_minTeleporterVersion`.
     */
    constructor(address teleporterRegistryAddress) {
        require(
            teleporterRegistryAddress != address(0),
            "TeleporterUpgradeable: zero teleporter registry address"
        );

        teleporterRegistry = TeleporterRegistry(teleporterRegistryAddress);
        _minTeleporterVersion = teleporterRegistry.latestVersion();
    }

    /**
     * @dev See {ITeleporterReceiver-receiveTeleporterMessage}
     * `nonReentrant` is a reentrancy guard that protects again multiple versions of the
     * TeleporterMessengerContract delivering a message in the same call. Any internal calls
     * will not be able to call functions also marked with `nonReentrant`.
     *
     * Requirements:
     *
     * - `_msgSender()` must be a Teleporter version greater than or equal to `minTeleporterVersion`.
     */
    function receiveTeleporterMessage(
        bytes32 sourceBlockchainID,
        address originSenderAddress,
        bytes calldata message
    ) external nonReentrant {
        // Checks that `_msgSender()` matches a Teleporter version greater than or equal to `minTeleporterVersion`.
        require(
            teleporterRegistry.getVersionFromAddress(_msgSender()) >= _minTeleporterVersion,
            "TeleporterUpgradeable: invalid Teleporter sender"
        );

        // Check against the paused Teleporter addresses.
        require(
            !isTeleporterAddressPaused(_msgSender()),
            "TeleporterUpgradeable: Teleporter address paused"
        );

        _receiveTeleporterMessage(sourceBlockchainID, originSenderAddress, message);
    }

    /**
     * @dev Updates the minimum Teleporter version allowed for delivering Teleporer messages
     * to this contract.
     *
     * To prevent anyone from being able to call this function, which would disallow messages
     * from old Teleporter versions from being received, this function should be safeguarded with access
     * controls. This is done by overriding the implementation of {_checkTeleporterUpgradeAccess}.
     */
    function updateMinTeleporterVersion(uint256 version) public virtual {
        _checkTeleporterUpgradeAccess();
        _setMinTeleporterVersion(version);
    }

    /**
     * @dev Pauses a Teleporter address from interacting with this contract.
     * After pausing a Teleporter address, it will no longer be able to deliver messages
     * to this contract, and this contract will not send messages through that Teleporter address.
     * The address does not need to be registered with the Teleporter registry.
     * Emits a {TeleporterAddressPaused} event if successfully paused.
     * Requirements:
     *
     * - `_msgSender()` must have Teleporter upgrade access.
     * - `teleporterAddress` is not the zero address.
     * - `teleporterAddress` is not already paused.
     */
    function pauseTeleporterAddress(address teleporterAddress) public virtual {
        _checkTeleporterUpgradeAccess();
        require(teleporterAddress != address(0), "TeleporterUpgradeable: zero Teleporter address");
        require(
            !isTeleporterAddressPaused(teleporterAddress),
            "TeleporterUpgradeable: address already paused"
        );
        _pausedTeleporterAddresses[teleporterAddress] = true;
        emit TeleporterAddressPaused(teleporterAddress);
    }

    /**
     * @dev Unpauses a Teleporter address from interacting with this contract.
     * After unpausing a Teleporter address, it will again be able to deliver messages
     * to this contract, and this contract can send messages through that Teleporter address.
     * The address does not need to be registered with the Teleporter registry.
     * Emits a {TeleporterAddressUnpaused} event if successfully unpaused.
     * Requirements:
     *
     * - `_msgSender()` must have Teleporter upgrade access.
     * - `teleporterAddress` is not the zero address.
     * - `teleporterAddress` is already paused.
     */
    function unpauseTeleporterAddress(address teleporterAddress) public virtual {
        _checkTeleporterUpgradeAccess();
        require(teleporterAddress != address(0), "TeleporterUpgradeable: zero Teleporter address");
        require(
            isTeleporterAddressPaused(teleporterAddress),
            "TeleporterUpgradeable: address not paused"
        );
        emit TeleporterAddressUnpaused(teleporterAddress);
        _pausedTeleporterAddresses[teleporterAddress] = false;
    }

    /**
     * @dev Public getter for `_minTeleporterVersion`.
     */
    function getMinTeleporterVersion() public view returns (uint256) {
        return _minTeleporterVersion;
    }

    /**
     * @dev Checks if a Teleporter address is paused.
     */
    function isTeleporterAddressPaused(address teleporterAddress)
        public
        view
        virtual
        returns (bool)
    {
        return _pausedTeleporterAddresses[teleporterAddress];
    }

    /**
     * @dev Sets the minimum Teleporter version allowed for delivering Teleporter messages.
     * Emits a {MinTeleporterVersionUpdated} event if the minimum Teleporter version was updated.
     * Requirements:
     *
     * - `version` must be less than or equal to the latest Teleporter version.
     * - `version` must be greater than the current minimum Teleporter version.
     *
     */
    function _setMinTeleporterVersion(uint256 version) internal virtual {
        uint256 latestTeleporterVersion = teleporterRegistry.latestVersion();
        uint256 oldMinTeleporterVersion = _minTeleporterVersion;

        require(
            version <= latestTeleporterVersion, "TeleporterUpgradeable: invalid Teleporter version"
        );
        require(
            version > oldMinTeleporterVersion,
            "TeleporterUpgradeable: not greater than current minimum version"
        );

        _minTeleporterVersion = version;
        emit MinTeleporterVersionUpdated(oldMinTeleporterVersion, version);
    }

    /**
     * @dev Receives Teleporter messages and handles accordingly.
     * This function should be overridden by contracts that inherit from this contract.
     */
    function _receiveTeleporterMessage(
        bytes32 sourceBlockchainID,
        address originSenderAddress,
        bytes memory message
    ) internal virtual;

    /**
     * @dev Checks that the caller has access to update the minimum Teleporter version
     * allowed for delivering Teleporter messages to this contract.
     *
     * This function should be overridden by contracts that inherit from this contract.
     */
    function _checkTeleporterUpgradeAccess() internal virtual;

    /**
     * @dev Sends a cross chain message using the TeleporterMessenger contract.
     *
     * The fee amount should be transferred to this contract prior to calling this function.
     * The fee amount is then allocated from this contract's token balance to
     * Teleporter's allowance to pay for sending the message.
     *
     * @return `messageID` The unique identifier for the Teleporter message.
     */
    function _sendTeleporterMessage(TeleporterMessageInput memory messageInput)
        internal
        virtual
        returns (bytes32)
    {
        ITeleporterMessenger teleporterMessenger = _getTeleporterMessenger();
        // For non-zero fee amounts increase the Teleporter contract's allowance.
        if (messageInput.feeInfo.amount > 0) {
            require(
                messageInput.feeInfo.feeTokenAddress != address(0),
                "TeleporterUpgradeable: zero fee token address"
            );
            IERC20(messageInput.feeInfo.feeTokenAddress).safeIncreaseAllowance(
                address(teleporterMessenger), messageInput.feeInfo.amount
            );
        }
        return teleporterMessenger.sendCrossChainMessage(messageInput);
    }

    /**
     * @dev Returns the Teleporter messenger used to send Teleporter messages,
     * and checks that the Teleporter messenger is not paused.
     *
     * By default returns the latest Teleporter messenger, but can be overriden to
     * return a Teleporter messenger of a specific version.
     */
    function _getTeleporterMessenger() internal view virtual returns (ITeleporterMessenger) {
        ITeleporterMessenger teleporter = teleporterRegistry.getLatestTeleporter();
        require(
            !isTeleporterAddressPaused(address(teleporter)),
            "TeleporterUpgradeable: Teleporter sending paused"
        );

        return teleporter;
    }
}

// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {TeleporterMessageInput, TeleporterFeeInfo} from "@teleporter/ITeleporterMessenger.sol";
import {TeleporterOwnerUpgradeable} from "@teleporter/upgrades/TeleporterOwnerUpgradeable.sol";
import {ITokenHome} from "./interfaces/ITokenHome.sol";
import {
    SendTokensInput,
    SendAndCallInput,
    TransferrerMessageType,
    TransferrerMessage,
    SingleHopSendMessage,
    SingleHopCallMessage,
    MultiHopSendMessage,
    MultiHopCallMessage,
    RegisterRemoteMessage
} from "../interfaces/ITokenTransferrer.sol";
import {SendReentrancyGuard} from "../utils/SendReentrancyGuard.sol";
import {TokenScalingUtils} from "../utils/TokenScalingUtils.sol";
import {SafeERC20TransferFrom} from "../utils/SafeERC20TransferFrom.sol";
import {IWarpMessenger} from
    "@avalabs/[email protected]/contracts/interfaces/IWarpMessenger.sol";
import {IERC20} from "@openzeppelin/[email protected]/token/ERC20/ERC20.sol";

/**
 * @notice Each TokenRemote instance registers with the home contract, and provides settings for transferring
 * to the remote token transfer contract.
 * @param registered Whether the remote token transferrer is registered
 * @param collateralNeeded The amount of tokens that must be first added as collateral,
 * through {addCollateral} calls, before tokens can be transferred to the remote token transferrer.
 * @param tokenMultiplier The scaling factor for the amount of tokens to be transferred to the remote.
 * @param multiplyOnRemote Whether the {tokenMultiplier} should be applied when transferring tokens to
 * the remote (multiplyOnRemote=true), or when transferring tokens back to the home (multiplyOnRemote=false).
 */
struct RemoteTokenTransferrerSettings {
    bool registered;
    uint256 collateralNeeded;
    uint256 tokenMultiplier;
    bool multiplyOnRemote;
}

/**
 * @title TokenHome
 * @dev Abstract contract for a token transferrer home that sends its specified token to {TokenRemote} instances.
 *
 * This contract also handles multi-hop transfers, where tokens sent from a {TokenRemote}
 * instance are forwarded to another {TokenRemote} instance.
 *
 * @custom:security-contact https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/SECURITY.md
 */
abstract contract TokenHome is ITokenHome, TeleporterOwnerUpgradeable, SendReentrancyGuard {
    /// @notice The blockchain ID of the chain this contract is deployed on.
    bytes32 public immutable blockchainID;

    /**
     * @notice The token address this home contract transfers to TokenRemote instances.
     * For multi-hop transfers, this {tokenAddress} is always used to pay for the secondary message fees.
     * If the token is an ERC20 token, the contract address is directly passed in.
     * If the token is a native asset, the contract address is the wrapped token contract.
     */
    address public immutable tokenAddress;

    uint8 public immutable tokenDecimals;

    /**
     * @notice Tracks the settings for each remote token transferrer instance. TokenRemote instances
     * must register with their {TokenHome} contracts via Teleporter message to be able to
     * receive tokens from this contract.
     */
    mapping(
        bytes32 remoteBlockchainID
            => mapping(
                address remoteTokenTransferrerAddress
                    => RemoteTokenTransferrerSettings remoteSettings
            )
    ) public registeredRemotes;

    /**
     * @notice Tracks the balances of tokens sent to TokenRemote instances.
     * Balances are represented in the remote token's denomination,
     * and token transferrers are not allowed to unwrap more than has been sent to them.
     * @dev (remoteBlockchainID, remoteTokenTransferrerAddress) -> balance
     */
    mapping(
        bytes32 remoteBlockchainID
            => mapping(address remoteTokenTransferrerAddress => uint256 balance)
    ) public transferredBalances;

    /**
     * @notice Initializes this home token transferrer instance to send tokens to TokenRemote instances on other chains.
     */
    constructor(
        address teleporterRegistryAddress,
        address teleporterManager,
        address tokenAddress_,
        uint8 tokenDecimals_
    ) TeleporterOwnerUpgradeable(teleporterRegistryAddress, teleporterManager) {
        blockchainID = IWarpMessenger(0x0200000000000000000000000000000000000005).getBlockchainID();
        require(tokenAddress_ != address(0), "TokenHome: zero token address");
        require(
            tokenDecimals_ <= TokenScalingUtils.MAX_TOKEN_DECIMALS,
            "TokenHome: token decimals too high"
        );
        tokenAddress = tokenAddress_;
        tokenDecimals = tokenDecimals_;
    }

    function _registerRemote(
        bytes32 remoteBlockchainID,
        address remoteTokenTransferrerAddress,
        RegisterRemoteMessage memory message
    ) internal {
        require(remoteBlockchainID != bytes32(0), "TokenHome: zero remote blockchain ID");
        require(
            remoteBlockchainID != blockchainID, "TokenHome: cannot register remote on same chain"
        );
        require(
            remoteTokenTransferrerAddress != address(0),
            "TokenHome: zero remote token transferrer address"
        );
        require(
            !registeredRemotes[remoteBlockchainID][remoteTokenTransferrerAddress].registered,
            "TokenHome: remote already registered"
        );
        require(
            message.remoteTokenDecimals <= TokenScalingUtils.MAX_TOKEN_DECIMALS,
            "TokenHome: remote token decimals too high"
        );
        require(
            message.homeTokenDecimals == tokenDecimals, "TokenHome: invalid home token decimals"
        );

        (uint256 tokenMultiplier, bool multiplyOnRemote) = TokenScalingUtils
            .deriveTokenMultiplierValues(tokenDecimals, message.remoteTokenDecimals);

        // Calculate the collateral needed in home token denomination.
        uint256 collateralNeeded = TokenScalingUtils.removeTokenScale(
            tokenMultiplier, multiplyOnRemote, message.initialReserveImbalance
        );

        // Round up the collateral needed by 1 in the case that {multiplyOnRemote} is true and
        // {initialReserveImbalance} is not divisible by the {tokenMultiplier} to
        // ensure that the full amount is accounted for.
        if (multiplyOnRemote && message.initialReserveImbalance % tokenMultiplier != 0) {
            collateralNeeded += 1;
        }

        registeredRemotes[remoteBlockchainID][remoteTokenTransferrerAddress] =
        RemoteTokenTransferrerSettings({
            registered: true,
            collateralNeeded: collateralNeeded,
            tokenMultiplier: tokenMultiplier,
            multiplyOnRemote: multiplyOnRemote
        });

        emit RemoteRegistered(
            remoteBlockchainID,
            remoteTokenTransferrerAddress,
            collateralNeeded,
            message.remoteTokenDecimals
        );
    }

    /**
     * @notice Sends tokens to the specified remote token transferrer instance.
     *
     * @dev Increases the balance sent to the remote token transferrer instance,
     * and uses Teleporter to send a cross chain message. The amount passed is assumed to
     * be already scaled to the local denomination for this token home.
     * Requirements:
     *
     * - The TokenRemote instance specified by {input.destinationBlockchainID} and {input.destinationTokenTransferrerAddress} must
     *   be registered with this contract.
     * - {input.recipient} cannot be the zero address
     * - {amount} must be greater than 0
     */
    function _send(SendTokensInput memory input, uint256 amount) internal sendNonReentrant {
        _validateSendTokensInput(input);
        // Require that a single hop transfer does not have a multi-hop fallback recipient.
        require(input.multiHopFallback == address(0), "TokenHome: non-zero multi-hop fallback");

        (uint256 adjustedAmount, uint256 feeAmount) = _prepareSend({
            remoteBlockchainID: input.destinationBlockchainID,
            remoteTokenTransferrerAddress: input.destinationTokenTransferrerAddress,
            amount: amount,
            primaryFeeTokenAddress: input.primaryFeeTokenAddress,
            feeAmount: input.primaryFee
        });

        TransferrerMessage memory message = TransferrerMessage({
            messageType: TransferrerMessageType.SINGLE_HOP_SEND,
            payload: abi.encode(
                SingleHopSendMessage({recipient: input.recipient, amount: adjustedAmount})
                )
        });

        // Send message to the TokenRemote instance
        bytes32 messageID = _sendTeleporterMessage(
            TeleporterMessageInput({
                destinationBlockchainID: input.destinationBlockchainID,
                destinationAddress: input.destinationTokenTransferrerAddress,
                feeInfo: TeleporterFeeInfo({
                    feeTokenAddress: input.primaryFeeTokenAddress,
                    amount: feeAmount
                }),
                requiredGasLimit: input.requiredGasLimit,
                allowedRelayerAddresses: new address[](0),
                message: abi.encode(message)
            })
        );

        emit TokensSent(messageID, _msgSender(), input, adjustedAmount);
    }

    /**
     * @notice Routes tokens from a multi-hop message to the specified remote token transferrer instance.
     *
     * @dev Increases the balance sent to the remote token transferrer instance,
     * and uses Teleporter to send a cross chain message. The amount passed is assumed to
     * be already scaled to the local denomination for this token home.
     * Requirements:
     *
     * - The TokenRemote instance specified by {input.destinationBlockchainID} and {input.destinationTokenTransferrerAddress} must
     *   be registered with this contract.
     * - {input.recipient} cannot be the zero address
     * - {amount} must be greater than 0
     */
    function _routeMultiHop(
        SendTokensInput memory input,
        uint256 amount
    ) internal sendNonReentrant {
        _validateSendTokensInput(input);

        uint256 adjustedAmount = _prepareMultiHopRouting(
            input.destinationBlockchainID,
            input.destinationTokenTransferrerAddress,
            amount,
            input.primaryFee
        );

        if (adjustedAmount == 0) {
            // If the adjusted amount is zero for any reason (i.e. unregistered remote,
            // being scaled down to zero, etc.), send the tokens to the multi-hop fallback.
            _withdraw(input.multiHopFallback, amount);
            return;
        }

        TransferrerMessage memory message = TransferrerMessage({
            messageType: TransferrerMessageType.SINGLE_HOP_SEND,
            payload: abi.encode(
                SingleHopSendMessage({recipient: input.recipient, amount: adjustedAmount})
                )
        });

        // Send message to the TokenRemote instance.
        bytes32 messageID = _sendTeleporterMessage(
            TeleporterMessageInput({
                destinationBlockchainID: input.destinationBlockchainID,
                destinationAddress: input.destinationTokenTransferrerAddress,
                feeInfo: TeleporterFeeInfo({
                    feeTokenAddress: input.primaryFeeTokenAddress,
                    amount: input.primaryFee
                }),
                requiredGasLimit: input.requiredGasLimit,
                allowedRelayerAddresses: new address[](0),
                message: abi.encode(message)
            })
        );

        emit TokensRouted(messageID, input, adjustedAmount);
    }

    function _sendAndCall(
        bytes32 sourceBlockchainID,
        address originTokenTransferrerAddress,
        address originSenderAddress,
        SendAndCallInput memory input,
        uint256 amount
    ) internal sendNonReentrant {
        _validateSendAndCallInput(input);

        // Require that a single hop transfer does not have a multi-hop fallback recipient.
        require(input.multiHopFallback == address(0), "TokenHome: non-zero multi-hop fallback");

        (uint256 adjustedAmount, uint256 feeAmount) = _prepareSend({
            remoteBlockchainID: input.destinationBlockchainID,
            remoteTokenTransferrerAddress: input.destinationTokenTransferrerAddress,
            amount: amount,
            primaryFeeTokenAddress: input.primaryFeeTokenAddress,
            feeAmount: input.primaryFee
        });

        TransferrerMessage memory message = TransferrerMessage({
            messageType: TransferrerMessageType.SINGLE_HOP_CALL,
            payload: abi.encode(
                SingleHopCallMessage({
                    sourceBlockchainID: sourceBlockchainID,
                    originTokenTransferrerAddress: originTokenTransferrerAddress,
                    originSenderAddress: originSenderAddress,
                    recipientContract: input.recipientContract,
                    amount: adjustedAmount,
                    recipientPayload: input.recipientPayload,
                    recipientGasLimit: input.recipientGasLimit,
                    fallbackRecipient: input.fallbackRecipient
                })
                )
        });

        // Send message to the TokenRemote instance.
        bytes32 messageID = _sendTeleporterMessage(
            TeleporterMessageInput({
                destinationBlockchainID: input.destinationBlockchainID,
                destinationAddress: input.destinationTokenTransferrerAddress,
                feeInfo: TeleporterFeeInfo({
                    feeTokenAddress: input.primaryFeeTokenAddress,
                    amount: feeAmount
                }),
                requiredGasLimit: input.requiredGasLimit,
                allowedRelayerAddresses: new address[](0),
                message: abi.encode(message)
            })
        );

        emit TokensAndCallSent(messageID, originSenderAddress, input, adjustedAmount);
    }

    function _routeMultiHopSendAndCall(
        bytes32 sourceBlockchainID,
        address originTokenTransferrerAddress,
        address originSenderAddress,
        SendAndCallInput memory input,
        uint256 amount
    ) internal sendNonReentrant {
        _validateSendAndCallInput(input);
        uint256 adjustedAmount = _prepareMultiHopRouting(
            input.destinationBlockchainID,
            input.destinationTokenTransferrerAddress,
            amount,
            input.primaryFee
        );

        if (adjustedAmount == 0) {
            // If the adjusted amount is zero for any reason (i.e. unregistered remote,
            // being scaled down to zero, etc.), send the tokens to the multi-hop fallback recipient.
            _withdraw(input.multiHopFallback, amount);
            return;
        }

        TransferrerMessage memory message = TransferrerMessage({
            messageType: TransferrerMessageType.SINGLE_HOP_CALL,
            payload: abi.encode(
                SingleHopCallMessage({
                    sourceBlockchainID: sourceBlockchainID,
                    originTokenTransferrerAddress: originTokenTransferrerAddress,
                    originSenderAddress: originSenderAddress,
                    recipientContract: input.recipientContract,
                    amount: adjustedAmount,
                    recipientPayload: input.recipientPayload,
                    recipientGasLimit: input.recipientGasLimit,
                    fallbackRecipient: input.fallbackRecipient
                })
                )
        });

        // Send message to the TokenRemote instance.
        bytes32 messageID = _sendTeleporterMessage(
            TeleporterMessageInput({
                destinationBlockchainID: input.destinationBlockchainID,
                destinationAddress: input.destinationTokenTransferrerAddress,
                feeInfo: TeleporterFeeInfo({
                    feeTokenAddress: input.primaryFeeTokenAddress,
                    amount: input.primaryFee
                }),
                requiredGasLimit: input.requiredGasLimit,
                allowedRelayerAddresses: new address[](0),
                message: abi.encode(message)
            })
        );

        emit TokensAndCallRouted(messageID, input, adjustedAmount);
    }

    /**
     * @dev See {INativeTokenHome-addCollateral}
     */
    function _addCollateral(
        bytes32 remoteBlockchainID,
        address remoteTokenTransferrerAddress,
        uint256 amount
    ) internal sendNonReentrant {
        RemoteTokenTransferrerSettings memory remoteSettings =
            registeredRemotes[remoteBlockchainID][remoteTokenTransferrerAddress];
        require(remoteSettings.registered, "TokenHome: remote not registered");
        require(remoteSettings.collateralNeeded > 0, "TokenHome: zero collateral needed");

        // Deposit the full amount, and withdraw back to the sender if there is excess.
        amount = _deposit(amount);

        // Calculate the remaining collateral needed, any excess amount, and adjust
        // {amount} to represent the amount of tokens added as collateral.
        uint256 remainingCollateralNeeded;
        uint256 excessAmount;
        if (amount >= remoteSettings.collateralNeeded) {
            remainingCollateralNeeded = 0;
            excessAmount = amount - remoteSettings.collateralNeeded;
            amount = remoteSettings.collateralNeeded;
        } else {
            remainingCollateralNeeded = remoteSettings.collateralNeeded - amount;
        }

        // Update the remaining collateral needed.
        registeredRemotes[remoteBlockchainID][remoteTokenTransferrerAddress].collateralNeeded =
            remainingCollateralNeeded;
        emit CollateralAdded(
            remoteBlockchainID, remoteTokenTransferrerAddress, amount, remainingCollateralNeeded
        );

        // If there is excess amount, send it back to the sender.
        if (excessAmount > 0) {
            _withdraw(_msgSender(), excessAmount);
        }
    }

    /**
     * @dev See {ITeleporterUpgradeable-_receiveTeleporterMessage}
     *
     * Handles the processing of Teleporter messages sent to this contract.
     * Supported message types include registering a TokenRemote instance, single-hop sends,
     * single-hop calls, multi-hop sends, and multi-hop calls.
     */
    function _receiveTeleporterMessage(
        bytes32 sourceBlockchainID,
        address originSenderAddress,
        bytes memory message
    ) internal override {
        TransferrerMessage memory transferrerMessage = abi.decode(message, (TransferrerMessage));
        if (transferrerMessage.messageType == TransferrerMessageType.SINGLE_HOP_SEND) {
            SingleHopSendMessage memory payload =
                abi.decode(transferrerMessage.payload, (SingleHopSendMessage));

            uint256 homeAmount =
                _processSingleHopTransfer(sourceBlockchainID, originSenderAddress, payload.amount);

            // Send the tokens to the recipient.
            _withdraw(payload.recipient, homeAmount);
            return;
        } else if (transferrerMessage.messageType == TransferrerMessageType.SINGLE_HOP_CALL) {
            SingleHopCallMessage memory payload =
                abi.decode(transferrerMessage.payload, (SingleHopCallMessage));

            uint256 homeAmount =
                _processSingleHopTransfer(sourceBlockchainID, originSenderAddress, payload.amount);

            // Verify that the payload's source blockchain ID and origin token transferrer address matches the source blockchain ID
            // and origin sender address passed from Teleporter.
            require(
                payload.sourceBlockchainID == sourceBlockchainID,
                "TokenHome: mismatched source blockchain ID"
            );
            require(
                payload.originTokenTransferrerAddress == originSenderAddress,
                "TokenHome: mismatched origin sender address"
            );

            _handleSendAndCall(payload, homeAmount);
            return;
        } else if (transferrerMessage.messageType == TransferrerMessageType.MULTI_HOP_SEND) {
            MultiHopSendMessage memory payload =
                abi.decode(transferrerMessage.payload, (MultiHopSendMessage));

            (uint256 homeAmount, uint256 fee) = _processMultiHopTransfer(
                sourceBlockchainID, originSenderAddress, payload.amount, payload.secondaryFee
            );

            // For a multi-hop send, the fee token address has to be {tokenAddress},
            // because the fee is taken from the amount that has already been deposited.
            // For ERC20 tokens, the token address of the contract is directly passed.
            // For native assets, the contract address is the wrapped token contract.
            _routeMultiHop(
                SendTokensInput({
                    destinationBlockchainID: payload.destinationBlockchainID,
                    destinationTokenTransferrerAddress: payload.destinationTokenTransferrerAddress,
                    recipient: payload.recipient,
                    primaryFeeTokenAddress: tokenAddress,
                    primaryFee: fee,
                    secondaryFee: 0,
                    requiredGasLimit: payload.secondaryGasLimit,
                    multiHopFallback: payload.multiHopFallback
                }),
                homeAmount
            );
            return;
        } else if (transferrerMessage.messageType == TransferrerMessageType.MULTI_HOP_CALL) {
            MultiHopCallMessage memory payload =
                abi.decode(transferrerMessage.payload, (MultiHopCallMessage));

            (uint256 homeAmount, uint256 fee) = _processMultiHopTransfer(
                sourceBlockchainID, originSenderAddress, payload.amount, payload.secondaryFee
            );

            // For a multi-hop send, the fee token address has to be {tokenAddress},
            // because the fee is taken from the amount that has already been deposited.
            // For ERC20 tokens, the token address of the contract is directly passed.
            // For native assets, the contract address is the wrapped token contract.
            _routeMultiHopSendAndCall({
                sourceBlockchainID: sourceBlockchainID,
                originTokenTransferrerAddress: originSenderAddress,
                originSenderAddress: payload.originSenderAddress,
                input: SendAndCallInput({
                    destinationBlockchainID: payload.destinationBlockchainID,
                    destinationTokenTransferrerAddress: payload.destinationTokenTransferrerAddress,
                    recipientContract: payload.recipientContract,
                    recipientPayload: payload.recipientPayload,
                    requiredGasLimit: payload.secondaryRequiredGasLimit,
                    recipientGasLimit: payload.recipientGasLimit,
                    multiHopFallback: payload.multiHopFallback,
                    fallbackRecipient: payload.fallbackRecipient,
                    primaryFeeTokenAddress: tokenAddress,
                    primaryFee: fee,
                    secondaryFee: 0
                }),
                amount: homeAmount
            });
            return;
        } else if (transferrerMessage.messageType == TransferrerMessageType.REGISTER_REMOTE) {
            RegisterRemoteMessage memory payload =
                abi.decode(transferrerMessage.payload, (RegisterRemoteMessage));
            _registerRemote(sourceBlockchainID, originSenderAddress, payload);
        }
    }

    /**
     * @notice Deposits tokens from the sender to this contract,
     * and returns the adjusted amount of tokens deposited.
     * @param amount The initial amount sent to this contract.
     * @return The actual amount deposited to this contract.
     */
    function _deposit(uint256 amount) internal virtual returns (uint256);

    /**
     * @notice Withdraws tokens to the recipient address.
     * @param recipient The address to withdraw tokens to
     * @param amount The amount of tokens to withdraw
     */
    function _withdraw(address recipient, uint256 amount) internal virtual;

    /**
     * @notice Processes a send and call message by calling the recipient contract.
     * @param message The send and call message include recipient calldata
     * @param amount The amount of tokens to be sent to the recipient. This amount is assumed to be
     * already scaled to the local denomination for this token home.
     */
    function _handleSendAndCall(
        SingleHopCallMessage memory message,
        uint256 amount
    ) internal virtual;

    /**
     * @notice Processes a received single hop transfer from a TokenRemote instance.
     * Validates that the message is sent from a registered TokenRemote instance,
     * and is already collateralized.
     * @param remoteBlockchainID The blockchain ID of the TokenRemote instance.
     * @param remoteTokenTransferrerAddress The address of the TokenRemote instance.
     * @param amount The amount of tokens sent back from remote, denominated by the
     * remote's token scale amount.
     */
    function _processSingleHopTransfer(
        bytes32 remoteBlockchainID,
        address remoteTokenTransferrerAddress,
        uint256 amount
    ) private returns (uint256) {
        RemoteTokenTransferrerSettings memory remoteSettings =
            registeredRemotes[remoteBlockchainID][remoteTokenTransferrerAddress];

        return _processReceivedTransfer(
            remoteSettings, remoteBlockchainID, remoteTokenTransferrerAddress, amount
        );
    }

    /**
     * @notice Processes a received multi-hop transfer from a TokenRemote instance.
     * Validates that the message is sent from a registered TokenRemote instance,
     * and is already collateralized.
     * @param remoteBlockchainID The blockchain ID of the TokenRemote instance.
     * @param remoteTokenTransferrerAddress The address of the TokenRemote instance.
     * @param amount The amount of tokens sent back from remote, denominated by the
     * remote's token scale amount.
     * @param secondaryFee The Teleporter fee for the second hop of the mutihop transfer
     */
    function _processMultiHopTransfer(
        bytes32 remoteBlockchainID,
        address remoteTokenTransferrerAddress,
        uint256 amount,
        uint256 secondaryFee
    ) private returns (uint256, uint256) {
        RemoteTokenTransferrerSettings memory remoteSettings =
            registeredRemotes[remoteBlockchainID][remoteTokenTransferrerAddress];

        uint256 transferAmount = _processReceivedTransfer(
            remoteSettings, remoteBlockchainID, remoteTokenTransferrerAddress, amount
        );

        uint256 fee = TokenScalingUtils.removeTokenScale(
            remoteSettings.tokenMultiplier, remoteSettings.multiplyOnRemote, secondaryFee
        );

        return (transferAmount, fee);
    }

    /**
     * @notice Processes a received transfer from a TokenRemote instance.
     * Deducts the balance transferred to the given TokenRemote instance.
     * Removes the token scaling of the remote, checks the associated home token
     * amount is greater than zero, and returns the home token amount.
     * @param remoteSettings The token transferrer settings for the TokenRemote instance we received the transfer from.
     * @param remoteBlockchainID The blockchain ID of the TokenRemote instance.
     * @param remoteTokenTransferrerAddress The address of the TokenRemote instance.
     * @param amount The amount of tokens sent back from remote, denominated by the
     * remote's token scale amount.
     */
    function _processReceivedTransfer(
        RemoteTokenTransferrerSettings memory remoteSettings,
        bytes32 remoteBlockchainID,
        address remoteTokenTransferrerAddress,
        uint256 amount
    ) private returns (uint256) {
        // Require that the remote is registered and has no collateral needed.
        require(remoteSettings.registered, "TokenHome: remote not registered");
        require(remoteSettings.collateralNeeded == 0, "TokenHome: remote not collateralized");

        // Deduct the balance transferred to the given TokenRemote instance prior to scaling the amount.
        _deductSenderBalance(remoteBlockchainID, remoteTokenTransferrerAddress, amount);

        // Remove the token scaling of the remote and get home token amount.
        uint256 homeAmount = TokenScalingUtils.removeTokenScale(
            remoteSettings.tokenMultiplier, remoteSettings.multiplyOnRemote, amount
        );

        // Require that the home token amount is greater than zero after removed scaling.
        require(homeAmount > 0, "TokenHome: zero token amount");
        return homeAmount;
    }

    /**
     * @notice Prepares a multi-hop send by checking the TokenRemote instance settings
     * and adjusting the amount to be sent.
     * @return The scaled amount to be sent to the TokenRemote instance. Zero can be returned if the
     * TokenRemote instance is not registered, needs collateral, or the scaled amount is zero.
     */
    function _prepareMultiHopRouting(
        bytes32 remoteBlockchainID,
        address remoteTokenTransferrerAddress,
        uint256 amount,
        uint256 fee
    ) private returns (uint256) {
        RemoteTokenTransferrerSettings memory remoteSettings =
            registeredRemotes[remoteBlockchainID][remoteTokenTransferrerAddress];
        if (!remoteSettings.registered || remoteSettings.collateralNeeded > 0) {
            return 0;
        }

        // Subtract fee amount from amount prior to scaling.
        require(amount > fee, "TokenHome: insufficient amount to cover fees");
        amount -= fee;

        // Scale the amount based on the token multiplier for the given TokenRemote instance.
        uint256 scaledAmount = TokenScalingUtils.applyTokenScale(
            remoteSettings.tokenMultiplier, remoteSettings.multiplyOnRemote, amount
        );
        if (scaledAmount == 0) {
            return 0;
        }

        // Increase the balance of the TokenRemote instance by the scaled amount.
        transferredBalances[remoteBlockchainID][remoteTokenTransferrerAddress] += scaledAmount;

        return scaledAmount;
    }

    /**
     * @dev Prepares tokens to be sent to another chain by handling the
     * locking of the token amount in this contract and updating the accounting
     * balances.
     */
    function _prepareSend(
        bytes32 remoteBlockchainID,
        address remoteTokenTransferrerAddress,
        uint256 amount,
        address primaryFeeTokenAddress,
        uint256 feeAmount
    ) private returns (uint256, uint256) {
        RemoteTokenTransferrerSettings memory remoteSettings =
            registeredRemotes[remoteBlockchainID][remoteTokenTransferrerAddress];
        require(remoteSettings.registered, "TokenHome: remote not registered");
        require(remoteSettings.collateralNeeded == 0, "TokenHome: collateral needed for remote");

        // Deposit the funds sent from the user to the token transferrer,
        // and set to adjusted amount after deposit.
        amount = _deposit(amount);

        if (feeAmount > 0) {
            feeAmount = SafeERC20TransferFrom.safeTransferFrom(
                IERC20(primaryFeeTokenAddress), _msgSender(), feeAmount
            );
        }

        // Scale the amount based on the token multiplier for the given TokenRemote instance.
        uint256 scaledAmount = TokenScalingUtils.applyTokenScale(
            remoteSettings.tokenMultiplier, remoteSettings.multiplyOnRemote, amount
        );
        require(scaledAmount > 0, "TokenHome: zero scaled amount");

        // Increase the balance of the TokenRemote instance by the scaled amount.
        transferredBalances[remoteBlockchainID][remoteTokenTransferrerAddress] += scaledAmount;

        return (scaledAmount, feeAmount);
    }

    function _deductSenderBalance(
        bytes32 remoteBlockchainID,
        address remoteTokenTransferrerAddress,
        uint256 amount
    ) private {
        uint256 senderBalance =
            transferredBalances[remoteBlockchainID][remoteTokenTransferrerAddress];
        require(senderBalance >= amount, "TokenHome: insufficient token transfer balance");
        transferredBalances[remoteBlockchainID][remoteTokenTransferrerAddress] =
            senderBalance - amount;
    }

    function _validateSendAndCallInput(SendAndCallInput memory input) private pure {
        require(input.recipientContract != address(0), "TokenHome: zero recipient contract address");
        require(input.requiredGasLimit > 0, "TokenHome: zero required gas limit");
        require(input.recipientGasLimit > 0, "TokenHome: zero recipient gas limit");
        require(
            input.recipientGasLimit < input.requiredGasLimit,
            "TokenHome: invalid recipient gas limit"
        );
        require(input.fallbackRecipient != address(0), "TokenHome: zero fallback recipient address");
        require(input.secondaryFee == 0, "TokenHome: non-zero secondary fee");
    }

    function _validateSendTokensInput(SendTokensInput memory input) private pure {
        require(input.recipient != address(0), "TokenHome: zero recipient address");
        require(input.requiredGasLimit > 0, "TokenHome: zero required gas limit");
        require(input.secondaryFee == 0, "TokenHome: non-zero secondary fee");
    }
}

// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {IERC20TokenTransferrer} from "../../interfaces/IERC20TokenTransferrer.sol";
import {ITokenHome} from "./ITokenHome.sol";

/**
 * @notice Interface for a ERC20 token "home" contract that locks its specified ERC20
 * token on its chain to be transferred to supported remote token transfer contracts on other chains.
 *
 * @custom:security-contact https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/SECURITY.md
 */
interface IERC20TokenHome is IERC20TokenTransferrer, ITokenHome {
    /**
     * @notice Adds collateral to the home token transfer contract for the specified TokenRemote instance. If more value is provided
     * than the amount of collateral needed, the excess amount is returned to the caller.
     * @param remoteBlockchainID The blockchain ID of the TokenRemote instance to add collateral for.
     * @param remoteTokenTransferrerAddress The address of the TokenRemote instance to add collateral for on the {remoteBlockchainID}.
     * @param amount Amount of tokens to add as collateral.
     */
    function addCollateral(
        bytes32 remoteBlockchainID,
        address remoteTokenTransferrerAddress,
        uint256 amount
    ) external;
}

File 19 of 26 : ITokenHome.sol
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {
    ITokenTransferrer,
    SendTokensInput,
    SendAndCallInput
} from "../../interfaces/ITokenTransferrer.sol";

/**
 * @dev Interface for a "home" token transferrer contract that locks a specific token
 * on its chain to be transferred to supported "remote" token transferrer contracts on other chains.
 */
interface ITokenHome is ITokenTransferrer {
    /**
     * @dev Emitted when tokens are added as collateral for a given TokenRemote instance.
     * The event emits a {remaining} value of 0 when the TokenRemote instance is fully collateralized.
     */
    event CollateralAdded(
        bytes32 indexed remoteBlockchainID,
        address indexed remoteTokenTransferrerAddress,
        uint256 amount,
        uint256 remaining
    );

    /**
     * @notice Emitted when a new TokenRemote instance is registered with the token transferrer.
     */
    event RemoteRegistered(
        bytes32 indexed remoteBlockchainID,
        address indexed remoteTokenTransferrerAddress,
        uint256 initialCollateralNeeded,
        uint8 tokenDecimals
    );

    /**
     * @notice Emitted when tokens are routed from a multi-hop send message to another chain.
     */
    event TokensRouted(bytes32 indexed teleporterMessageID, SendTokensInput input, uint256 amount);

    /**
     * @notice Emitted when tokens are routed from a mulit-hop send message,
     * with calldata for a contract recipient, to another chain.
     */
    event TokensAndCallRouted(
        bytes32 indexed teleporterMessageID, SendAndCallInput input, uint256 amount
    );
}

File 20 of 26 : IERC20SendAndCallReceiver.sol
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

/**
 * @notice Interface for contracts that are called to receive token transfers.
 */
interface IERC20SendAndCallReceiver {
    /**
     * @notice Called to receive the amount of the given token
     * @param sourceBlockchainID Blockchain ID that the transfer originated from
     * @param originTokenTransferrerAddress Address of the token transferrer that initiated the Teleporter message
     * @param originSenderAddress Address of the sender that sent the transfer. This value
     * should only be trusted if {originTokenTransferrerAddress} is verified and known.
     * @param token Address of the token to be received
     * @param amount Amount of the token to be received
     * @param payload Arbitrary data provided by the caller
     */
    function receiveTokens(
        bytes32 sourceBlockchainID,
        address originTokenTransferrerAddress,
        address originSenderAddress,
        address token,
        uint256 amount,
        bytes calldata payload
    ) external;
}

// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {ITokenTransferrer, SendTokensInput, SendAndCallInput} from "./ITokenTransferrer.sol";

/**
 * @notice Interface for an Avalanche interchain token transferrer that sends ERC20 tokens to another chain.
 *
 * @custom:security-contact https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/SECURITY.md
 */
interface IERC20TokenTransferrer is ITokenTransferrer {
    /**
     * @notice Sends ERC20 tokens to the specified destination.
     * @param input Specifies information for delivery of the tokens
     * @param amount Amount of tokens to send
     */
    function send(SendTokensInput calldata input, uint256 amount) external;

    /**
     * @notice Sends ERC20 tokens to the specified destination to be used in a smart contract interaction.
     * @param input Specifies information for delivery of the tokens
     * @param amount Amount of tokens to send
     */
    function sendAndCall(SendAndCallInput calldata input, uint256 amount) external;
}

File 22 of 26 : ITokenTransferrer.sol
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {ITeleporterReceiver} from "@teleporter/ITeleporterReceiver.sol";

/**
 * @notice Input parameters for transferring tokens to another chain as part of a simple transfer.
 * @param destinationBlockchainID Blockchain ID of the destination
 * @param destinationTokenTransferrerAddress Address of the destination token transferrer instance
 * @param recipient Address of the recipient on the destination chain
 * @param primaryFeeTokenAddress Address of the ERC20 contract to optionally pay a Teleporter message fee
 * @param primaryFee Amount of tokens to pay as the optional Teleporter message fee
 * @param secondaryFee Amount of tokens to pay for Teleporter fee if a multi-hop is needed
 * @param requiredGasLimit Gas limit requirement for sending to a token transferrer.
 * This is required because the gas requirement varies based on the token transferrer instance
 * specified by {destinationBlockchainID} and {destinationTokenTransferrerAddress}.
 * @param multiHopFallback In the case of a multi-hop transfer, the address where the tokens
 * are sent on the home chain if the transfer is unable to be routed to its final destination.
 * Note that this address must be able to receive the tokens held as collateral in the home contract.
 */
struct SendTokensInput {
    bytes32 destinationBlockchainID;
    address destinationTokenTransferrerAddress;
    address recipient;
    address primaryFeeTokenAddress;
    uint256 primaryFee;
    uint256 secondaryFee;
    uint256 requiredGasLimit;
    address multiHopFallback;
}

/**
 * @notice Input parameters for transferring tokens to another chain as part of a transfer with a contract call.
 * @param destinationBlockchainID BlockchainID of the destination
 * @param destinationTokenTransferrerAddress Address of the destination token transferrer instance
 * @param recipientContract The contract on the destination chain that will be called
 * @param recipientPayload The payload that will be provided to the recipient contract on the destination chain
 * @param requiredGasLimit The required amount of gas needed to deliver the message on its destination chain,
 * including token operations and the call to the recipient contract.
 * @param recipientGasLimit The amount of gas that will provided to the recipient contract on the destination chain,
 * which must be less than the requiredGasLimit of the message as a whole.
 * @param multiHopFallback In the case of a multi-hop transfer, the address where the tokens
 * are sent on the home chain if the transfer is unable to be routed to its final destination.
 * Note that this address must be able to receive the tokens held as collateral in the home contract.
 * @param fallbackRecipient Address on the {destinationBlockchainID} where the transferred tokens are sent to if the call
 * to the recipient contract fails. Note that this address must be able to receive the tokens on the destination
 * chain of the transfer.
 * @param primaryFeeTokenAddress Address of the ERC20 contract to optionally pay a Teleporter message fee
 * @param primaryFee Amount of tokens to pay for Teleporter fee on the chain that iniiated the transfer
 * @param secondaryFee Amount of tokens to pay for Teleporter fee if a multi-hop is needed
 */
struct SendAndCallInput {
    bytes32 destinationBlockchainID;
    address destinationTokenTransferrerAddress;
    address recipientContract;
    bytes recipientPayload;
    uint256 requiredGasLimit;
    uint256 recipientGasLimit;
    address multiHopFallback;
    address fallbackRecipient;
    address primaryFeeTokenAddress;
    uint256 primaryFee;
    uint256 secondaryFee;
}

enum TransferrerMessageType {
    REGISTER_REMOTE,
    SINGLE_HOP_SEND,
    SINGLE_HOP_CALL,
    MULTI_HOP_SEND,
    MULTI_HOP_CALL
}

/**
 * @dev The TransferrerMessage struct is used to wrap messages between two token transferrer contracts
 * with their message type so that the receiving token transferrer can decode the payload.
 */
struct TransferrerMessage {
    TransferrerMessageType messageType;
    bytes payload;
}

/**
 * @dev Register remote message payloads are sent to the home token transferrer contract to register a new remote contract
 * instance on another chain.
 * @param initialReserveImbalance The initial reserve imbalance of the remote contract to calculate
 * associated collateral needed on home contract.
 * @param homeTokenDecimals The number of decimals that the home token has.
 * @param remoteTokenDecimals The number of decimals that the remote token has.
 */
struct RegisterRemoteMessage {
    uint256 initialReserveImbalance;
    uint8 homeTokenDecimals;
    uint8 remoteTokenDecimals;
}

/**
 * @dev Single hop send message payloads include the recipient address and transferred amount.
 * The destination chain and token transferrer address for the transfer are defined by the Teleporter message.
 */
struct SingleHopSendMessage {
    address recipient;
    uint256 amount;
}

/**
 * @dev Single hop call message payloads include the required information to call
 * the target contract on the destination chain. The destination chain and token transferrer
 * address are defined by the Teleporter message. The message also includes the
 * blockchain ID and address of the original sender.
 */
struct SingleHopCallMessage {
    bytes32 sourceBlockchainID;
    address originTokenTransferrerAddress;
    address originSenderAddress;
    address recipientContract;
    uint256 amount;
    bytes recipientPayload;
    uint256 recipientGasLimit;
    address fallbackRecipient;
}

/**
 * @dev Multi hop send message payloads include the recipient address as well as all
 * the information the intermediate (home) chain token transferrer contract needs to route
 * the send message on to its final destination.
 */
struct MultiHopSendMessage {
    bytes32 destinationBlockchainID;
    address destinationTokenTransferrerAddress;
    address recipient;
    uint256 amount;
    uint256 secondaryFee;
    uint256 secondaryGasLimit;
    address multiHopFallback;
}

/**
 * @dev Multi hop call message payloads include the required information to call the target contract on the
 * destination chain, as well as the information the intermediate (home) chain token transferrer contract needs to route
 * the call message on to its final destination. This includes the {secondaryRequiredGasLimit}, which is the
 * required gas limit set for the second Teleporter message. The {secondaryRequiredGasLimit} should be sufficient
 * to cover the destination token operations as well as the call to the recipient contract, and will always be
 * greater than the recipientGasLimit. The multi-hop message also includes the address of the original sender.
 * The source blockchain ID of the sender is known from the Teleporter message.
 */
struct MultiHopCallMessage {
    address originSenderAddress;
    bytes32 destinationBlockchainID;
    address destinationTokenTransferrerAddress;
    address recipientContract;
    uint256 amount;
    bytes recipientPayload;
    uint256 recipientGasLimit;
    address fallbackRecipient;
    uint256 secondaryRequiredGasLimit;
    address multiHopFallback;
    uint256 secondaryFee;
}

/**
 * @notice Interface for an Avalanche interchain token transferrer that sends tokens to another chain.
 *
 * @custom:security-contact https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/SECURITY.md
 */
interface ITokenTransferrer is ITeleporterReceiver {
    /**
     * @notice Emitted when tokens are sent to another chain.
     */
    event TokensSent(
        bytes32 indexed teleporterMessageID,
        address indexed sender,
        SendTokensInput input,
        uint256 amount
    );

    /**
     * @notice Emitted when tokens are sent to another chain with calldata for a contract recipient.
     */
    event TokensAndCallSent(
        bytes32 indexed teleporterMessageID,
        address indexed sender,
        SendAndCallInput input,
        uint256 amount
    );

    /**
     * @notice Emitted when tokens are withdrawn from the token transferrer contract.
     */
    event TokensWithdrawn(address indexed recipient, uint256 amount);

    /**
     * @notice Emitted when a call to a recipient contract to receive token succeeds.
     */
    event CallSucceeded(address indexed recipientContract, uint256 amount);

    /**
     * @notice Emitted when a call to a recipient contract to receive token fails, and the tokens are sent
     * to a fallback recipient.
     */
    event CallFailed(address indexed recipientContract, uint256 amount);
}

// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem
pragma solidity 0.8.18;

library CallUtils {
    /**
     * @dev calls target address with exactly gasAmount gas and data as calldata
     * or reverts if at least gasAmount gas is not available.
     */
    function _callWithExactGas(
        uint256 gasAmount,
        address target,
        bytes memory data
    ) internal returns (bool) {
        return _callWithExactGasAndValue(gasAmount, 0, target, data);
    }

    /**
     * @dev calls target address with exactly gasAmount gas and data as calldata
     * or reverts if at least gasAmount gas is not available.
     */
    function _callWithExactGasAndValue(
        uint256 gasAmount,
        uint256 value,
        address target,
        bytes memory data
    ) internal returns (bool) {
        require(gasleft() >= gasAmount, "CallUtils: insufficient gas");
        require(address(this).balance >= value, "CallUtils: insufficient value");

        // If there is no code at the target, automatically consider the call to have failed since it
        // doesn't have any effect on state.
        if (target.code.length == 0) {
            return false;
        }

        // Call the target address of the message with the provided data and amount of gas.
        //
        // Assembly is used for the low-level call to avoid unnecessary expansion of the return data in memory.
        // This prevents possible "return bomb" vectors where the external contract could force the caller
        // to use an arbitrary amount of gas. See Solidity issue here: https://github.com/ethereum/solidity/issues/12306
        bool success;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            success :=
                call(
                    gasAmount, // gas provided to the call
                    target, // call target
                    value, // value transferred
                    add(data, 0x20), // input data - 0x20 needs to be added to an array because the first 32-byte slot contains the array length (0x20 in hex is 32 in decimal).
                    mload(data), // input data size - mload returns mem[p..(p+32)], which is the first 32-byte slot of the array. In this case, the array length.
                    0, // output
                    0 // output size
                )
        }
        return success;
    }
}

File 24 of 26 : SafeERC20TransferFrom.sol
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {IERC20} from "@openzeppelin/[email protected]/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/[email protected]/token/ERC20/utils/SafeERC20.sol";

/**
 * @dev Provides a wrapper used for calling an ERC20 transferFrom method to receive tokens to a contract
 * from a specified sender. Differs from the "SafeERC20TransferFrom" implementation found here
 * https://github.com/ava-labs/teleporter/blob/main/contracts/src/Teleporter/SafeERC20TransferFrom.sol in that
 * it supports passing arbitrary sender address values, allowing its use in ERC-2771 compliant meta-transactions.
 *
 * Checks the balance of the contract using the library before and after the call to safeTransferFrom, and
 * returns balance increase. Designed for safely handling ERC20 "fee on transfer" and "burn on transfer"
 * implementations.
 *
 * Note: Contracts that use this library must ensure that users cannot pass arbitrary addresses as
 * the {from} address for the {transferFrom} call. Proper authorization (such as msg.sender) must
 * be required to ensure no one can improperly transfer tokens from any address.
 *
 * Note: A reentrancy guard must always be used when calling token.safeTransferFrom in order to
 * prevent against possible "before-after" pattern vulnerabilities.
 *
 * @custom:security-contact https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/SECURITY.md
 */
library SafeERC20TransferFrom {
    using SafeERC20 for IERC20;

    /**
     * @dev Checks the balance of the contract before and after the call to safeTransferFrom, and returns the balance
     * increase. Designed for safely handling ERC20 "fee on transfer" and "burn on transfer" implementations.
     */
    // solhint-disable private-vars-leading-underscore
    function safeTransferFrom(
        IERC20 erc20,
        address from,
        uint256 amount
    ) internal returns (uint256) {
        uint256 balanceBefore = erc20.balanceOf(address(this));
        erc20.safeTransferFrom(from, address(this), amount);
        uint256 balanceAfter = erc20.balanceOf(address(this));

        require(balanceAfter > balanceBefore, "SafeERC20TransferFrom: balance not increased");

        return balanceAfter - balanceBefore;
    }
    // solhint-enable private-vars-leading-underscore
}

File 25 of 26 : SendReentrancyGuard.sol
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

/**
 * @dev Abstract contract that helps implement reentrancy guards for Avalanche interchain token transfer {_send} and {_sendAndCall}
 * functions.
 *
 * The send methods must not allow reentry given that can make calls to external contracts such as {safeTransferFrom}
 * and {safeDeposit}. However, the send methods should be allowed to be called from {receiveTeleporterMessage}, either
 * as a part of processing a multi-hop transfer, or as a part of an external call made to process a "sendAndCall"
 * message.
 *
 * @custom:security-contact https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/SECURITY.md
 */
abstract contract SendReentrancyGuard {
    uint256 internal constant _NOT_ENTERED = 1;
    uint256 internal constant _ENTERED = 2;
    uint256 private _sendEntered;

    // sendNonReentrant modifier makes sure there is not reentry between {_send} or {_sendAndCall} calls.
    modifier sendNonReentrant() {
        require(_sendEntered == _NOT_ENTERED, "SendReentrancyGuard: send reentrancy");
        _sendEntered = _ENTERED;
        _;
        _sendEntered = _NOT_ENTERED;
    }

    constructor() {
        _sendEntered = _NOT_ENTERED;
    }
}

// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem
pragma solidity 0.8.18;

library TokenScalingUtils {
    uint256 public constant MAX_TOKEN_DECIMALS = 18;

    /**
     * @notice Scales the {amount} of home tokens to a TokenRemote instance's token scale.
     * @param tokenMultiplier The token multiplier of the TokenRemote instance.
     * @param multiplyOnRemote Whether the amount of home tokens will be multiplied on the remote, or divided.
     * @param homeTokenAmount The amount of home tokens to scale.
     */
    function applyTokenScale(
        uint256 tokenMultiplier,
        bool multiplyOnRemote,
        uint256 homeTokenAmount
    ) internal pure returns (uint256) {
        return _scaleTokens(tokenMultiplier, multiplyOnRemote, homeTokenAmount, true);
    }

    /**
     * @notice Removes the TokenRemote instance's token scaling, and returns the corresponding
     * amount of home tokens.
     * @param tokenMultiplier The token multiplier of the TokenRemote instance.
     * @param multiplyOnRemote Whether the amount of home tokens will be multiplied on the remote, or divided.
     * @param remoteTokenAmount The amount of remote tokens to remove scaling from.
     */
    function removeTokenScale(
        uint256 tokenMultiplier,
        bool multiplyOnRemote,
        uint256 remoteTokenAmount
    ) internal pure returns (uint256) {
        return _scaleTokens(tokenMultiplier, multiplyOnRemote, remoteTokenAmount, false);
    }

    /**
     * @notice Takes both the home and remote token denominations and uses
     * them to derive the token transferrer scaling multiplier values
     * @param homeTokenDecimals The number of decimals of the home token.
     * @param remoteTokenDecimals The number of decimals of the remote token.
     */
    function deriveTokenMultiplierValues(
        uint8 homeTokenDecimals,
        uint8 remoteTokenDecimals
    ) internal pure returns (uint256, bool) {
        bool multiplyOnRemote = remoteTokenDecimals > homeTokenDecimals;
        uint256 tokenMultiplier = 10
            ** (
                multiplyOnRemote
                    ? uint256(remoteTokenDecimals - homeTokenDecimals)
                    : uint256(homeTokenDecimals - remoteTokenDecimals)
            );
        return (tokenMultiplier, multiplyOnRemote);
    }

    /**
     * @dev Scales {value} based on {tokenMultiplier} and if the amount is applying or
     * removing the TokenRemote instance's token scale.
     * Should be used for all tokens and fees being transferred to/from other subnets.
     * @param tokenMultiplier The token multiplier of the TokenRemote instance.
     * @param multiplyOnRemote Whether the amount of home tokens will be multiplied on the remote, or divided.
     * @param amount The amount of tokens to scale.
     * @param isSendToRemote If true, indicates the amount is being sent to the
     * TokenRemote instance, so applies token scale. If false, indicates the amount is being
     * sent back to the TokenHome instance, so removes token scale.
     */
    function _scaleTokens(
        uint256 tokenMultiplier,
        bool multiplyOnRemote,
        uint256 amount,
        bool isSendToRemote
    ) private pure returns (uint256) {
        // Multiply when multiplyOnRemote and isSendToRemote are
        // both true or both false.
        if (multiplyOnRemote == isSendToRemote) {
            return amount * tokenMultiplier;
        }
        // Otherwise divide.
        return amount / tokenMultiplier;
    }
}

Settings
{
  "evmVersion": "paris",
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "remappings": [
    ":@avalabs/[email protected]/=lib/teleporter/contracts/lib/subnet-evm/contracts/",
    ":@openzeppelin/[email protected]/=lib/teleporter/contracts/lib/openzeppelin-contracts/contracts/",
    ":@teleporter-mocks/=lib/teleporter/contracts/src/Mocks/",
    ":@teleporter/=lib/teleporter/contracts/src/Teleporter/",
    ":ds-test/=lib/forge-std/lib/ds-test/src/",
    ":forge-std/=lib/forge-std/src/",
    ":openzeppelin-contracts/=lib/teleporter/contracts/lib/openzeppelin-contracts/",
    ":subnet-evm/=lib/teleporter/contracts/lib/subnet-evm/",
    ":teleporter/=lib/teleporter/"
  ],
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  }
}

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"teleporterRegistryAddress","type":"address"},{"internalType":"address","name":"teleporterManager","type":"address"},{"internalType":"address","name":"tokenAddress_","type":"address"},{"internalType":"uint8","name":"tokenDecimals_","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipientContract","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"CallFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipientContract","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"CallSucceeded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"remoteBlockchainID","type":"bytes32"},{"indexed":true,"internalType":"address","name":"remoteTokenTransferrerAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"remaining","type":"uint256"}],"name":"CollateralAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"oldMinTeleporterVersion","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"newMinTeleporterVersion","type":"uint256"}],"name":"MinTeleporterVersionUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"remoteBlockchainID","type":"bytes32"},{"indexed":true,"internalType":"address","name":"remoteTokenTransferrerAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"initialCollateralNeeded","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"tokenDecimals","type":"uint8"}],"name":"RemoteRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"teleporterAddress","type":"address"}],"name":"TeleporterAddressPaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"teleporterAddress","type":"address"}],"name":"TeleporterAddressUnpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"teleporterMessageID","type":"bytes32"},{"components":[{"internalType":"bytes32","name":"destinationBlockchainID","type":"bytes32"},{"internalType":"address","name":"destinationTokenTransferrerAddress","type":"address"},{"internalType":"address","name":"recipientContract","type":"address"},{"internalType":"bytes","name":"recipientPayload","type":"bytes"},{"internalType":"uint256","name":"requiredGasLimit","type":"uint256"},{"internalType":"uint256","name":"recipientGasLimit","type":"uint256"},{"internalType":"address","name":"multiHopFallback","type":"address"},{"internalType":"address","name":"fallbackRecipient","type":"address"},{"internalType":"address","name":"primaryFeeTokenAddress","type":"address"},{"internalType":"uint256","name":"primaryFee","type":"uint256"},{"internalType":"uint256","name":"secondaryFee","type":"uint256"}],"indexed":false,"internalType":"struct SendAndCallInput","name":"input","type":"tuple"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokensAndCallRouted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"teleporterMessageID","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"components":[{"internalType":"bytes32","name":"destinationBlockchainID","type":"bytes32"},{"internalType":"address","name":"destinationTokenTransferrerAddress","type":"address"},{"internalType":"address","name":"recipientContract","type":"address"},{"internalType":"bytes","name":"recipientPayload","type":"bytes"},{"internalType":"uint256","name":"requiredGasLimit","type":"uint256"},{"internalType":"uint256","name":"recipientGasLimit","type":"uint256"},{"internalType":"address","name":"multiHopFallback","type":"address"},{"internalType":"address","name":"fallbackRecipient","type":"address"},{"internalType":"address","name":"primaryFeeTokenAddress","type":"address"},{"internalType":"uint256","name":"primaryFee","type":"uint256"},{"internalType":"uint256","name":"secondaryFee","type":"uint256"}],"indexed":false,"internalType":"struct SendAndCallInput","name":"input","type":"tuple"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokensAndCallSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"teleporterMessageID","type":"bytes32"},{"components":[{"internalType":"bytes32","name":"destinationBlockchainID","type":"bytes32"},{"internalType":"address","name":"destinationTokenTransferrerAddress","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"address","name":"primaryFeeTokenAddress","type":"address"},{"internalType":"uint256","name":"primaryFee","type":"uint256"},{"internalType":"uint256","name":"secondaryFee","type":"uint256"},{"internalType":"uint256","name":"requiredGasLimit","type":"uint256"},{"internalType":"address","name":"multiHopFallback","type":"address"}],"indexed":false,"internalType":"struct SendTokensInput","name":"input","type":"tuple"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokensRouted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"teleporterMessageID","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"components":[{"internalType":"bytes32","name":"destinationBlockchainID","type":"bytes32"},{"internalType":"address","name":"destinationTokenTransferrerAddress","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"address","name":"primaryFeeTokenAddress","type":"address"},{"internalType":"uint256","name":"primaryFee","type":"uint256"},{"internalType":"uint256","name":"secondaryFee","type":"uint256"},{"internalType":"uint256","name":"requiredGasLimit","type":"uint256"},{"internalType":"address","name":"multiHopFallback","type":"address"}],"indexed":false,"internalType":"struct SendTokensInput","name":"input","type":"tuple"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokensSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokensWithdrawn","type":"event"},{"inputs":[{"internalType":"bytes32","name":"remoteBlockchainID","type":"bytes32"},{"internalType":"address","name":"remoteTokenTransferrerAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"addCollateral","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"blockchainID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMinTeleporterVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"teleporterAddress","type":"address"}],"name":"isTeleporterAddressPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"teleporterAddress","type":"address"}],"name":"pauseTeleporterAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"sourceBlockchainID","type":"bytes32"},{"internalType":"address","name":"originSenderAddress","type":"address"},{"internalType":"bytes","name":"message","type":"bytes"}],"name":"receiveTeleporterMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"remoteBlockchainID","type":"bytes32"},{"internalType":"address","name":"remoteTokenTransferrerAddress","type":"address"}],"name":"registeredRemotes","outputs":[{"internalType":"bool","name":"registered","type":"bool"},{"internalType":"uint256","name":"collateralNeeded","type":"uint256"},{"internalType":"uint256","name":"tokenMultiplier","type":"uint256"},{"internalType":"bool","name":"multiplyOnRemote","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"destinationBlockchainID","type":"bytes32"},{"internalType":"address","name":"destinationTokenTransferrerAddress","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"address","name":"primaryFeeTokenAddress","type":"address"},{"internalType":"uint256","name":"primaryFee","type":"uint256"},{"internalType":"uint256","name":"secondaryFee","type":"uint256"},{"internalType":"uint256","name":"requiredGasLimit","type":"uint256"},{"internalType":"address","name":"multiHopFallback","type":"address"}],"internalType":"struct SendTokensInput","name":"input","type":"tuple"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"destinationBlockchainID","type":"bytes32"},{"internalType":"address","name":"destinationTokenTransferrerAddress","type":"address"},{"internalType":"address","name":"recipientContract","type":"address"},{"internalType":"bytes","name":"recipientPayload","type":"bytes"},{"internalType":"uint256","name":"requiredGasLimit","type":"uint256"},{"internalType":"uint256","name":"recipientGasLimit","type":"uint256"},{"internalType":"address","name":"multiHopFallback","type":"address"},{"internalType":"address","name":"fallbackRecipient","type":"address"},{"internalType":"address","name":"primaryFeeTokenAddress","type":"address"},{"internalType":"uint256","name":"primaryFee","type":"uint256"},{"internalType":"uint256","name":"secondaryFee","type":"uint256"}],"internalType":"struct SendAndCallInput","name":"input","type":"tuple"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"sendAndCall","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"teleporterRegistry","outputs":[{"internalType":"contract TeleporterRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenDecimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"remoteBlockchainID","type":"bytes32"},{"internalType":"address","name":"remoteTokenTransferrerAddress","type":"address"}],"name":"transferredBalances","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"teleporterAddress","type":"address"}],"name":"unpauseTeleporterAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"version","type":"uint256"}],"name":"updateMinTeleporterVersion","outputs":[],"stateMutability":"nonpayable","type":"function"}]

6101206040523480156200001257600080fd5b506040516200465938038062004659833981016040819052620000359162000402565b6001600055838383838383816001600160a01b038116620000c35760405162461bcd60e51b815260206004820152603760248201527f54656c65706f727465725570677261646561626c653a207a65726f2074656c6560448201527f706f72746572207265676973747279206164647265737300000000000000000060648201526084015b60405180910390fd5b6001600160a01b03811660808190526040805163301fd1f560e21b8152905163c07f47d4916004808201926020929091908290030181865afa1580156200010e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000134919062000466565b600255506200014333620002b6565b6200014e8162000308565b505060016004819055507302000000000000000000000000000000000000056001600160a01b0316634213cf786040518163ffffffff1660e01b8152600401602060405180830381865afa158015620001ab573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001d1919062000466565b60a0526001600160a01b0382166200022c5760405162461bcd60e51b815260206004820152601d60248201527f546f6b656e486f6d653a207a65726f20746f6b656e20616464726573730000006044820152606401620000ba565b60128160ff1611156200028d5760405162461bcd60e51b815260206004820152602260248201527f546f6b656e486f6d653a20746f6b656e20646563696d616c7320746f6f2068696044820152610ced60f31b6064820152608401620000ba565b6001600160a01b0390911660c081905260ff90911660e052610100525062000480945050505050565b600380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6200031262000387565b6001600160a01b038116620003795760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401620000ba565b6200038481620002b6565b50565b6003546001600160a01b03163314620003e35760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401620000ba565b565b80516001600160a01b0381168114620003fd57600080fd5b919050565b600080600080608085870312156200041957600080fd5b6200042485620003e5565b93506200043460208601620003e5565b92506200044460408601620003e5565b9150606085015160ff811681146200045b57600080fd5b939692955090935050565b6000602082840312156200047957600080fd5b5051919050565b60805160a05160c05160e05161010051614124620005356000396000818161037c01528181611b2b01528181611b5701528181611b9401528181611c4301528181611cb701528181611d9501526125b5015260008181610184015281816123e701526124750152600081816102970152818161117301526112b70152600081816102d1015281816105e8015261220801526000818161012b0152818161064101528181610a93015261276601526141246000f3fe608060405234801561001057600080fd5b50600436106101215760003560e01c80638da5cb5b116100ad578063d2cc7a7011610071578063d2cc7a70146102f3578063d536ec33146102fb578063f2fde38b14610364578063fc0c546a14610377578063fd6582681461039e57600080fd5b80638da5cb5b1461024557806397314297146102565780639d76ea5814610292578063c868efaa146102b9578063d127dc9b146102cc57600080fd5b80634511243e116100f45780634511243e146101f15780635d16225d146102045780635eb9951414610217578063656900381461022a578063715018a61461023d57600080fd5b80631a7f5bec146101265780632b0d8f181461016a5780633b97e8561461017f5780633ca563e3146101b8575b600080fd5b61014d7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b61017d61017836600461315b565b6103b1565b005b6101a67f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff9091168152602001610161565b6101e36101c6366004613178565b600660209081526000928352604080842090915290825290205481565b604051908152602001610161565b61017d6101ff36600461315b565b6104b6565b61017d6102123660046131a8565b6105b3565b61017d6102253660046131da565b6105cf565b61017d6102383660046131f3565b6105e3565b61017d610618565b6003546001600160a01b031661014d565b61028261026436600461315b565b6001600160a01b031660009081526001602052604090205460ff1690565b6040519015158152602001610161565b61014d7f000000000000000000000000000000000000000000000000000000000000000081565b61017d6102c736600461323d565b61062c565b6101e37f000000000000000000000000000000000000000000000000000000000000000081565b6002546101e3565b610340610309366004613178565b6005602090815260009283526040808420909152908252902080546001820154600283015460039093015460ff9283169391921684565b60408051941515855260208501939093529183015215156060820152608001610161565b61017d61037236600461315b565b6107f6565b61014d7f000000000000000000000000000000000000000000000000000000000000000081565b61017d6103ac3660046132c5565b61086c565b6103b961087c565b6001600160a01b0381166103e85760405162461bcd60e51b81526004016103df906132fd565b60405180910390fd5b6001600160a01b03811660009081526001602052604090205460ff16156104675760405162461bcd60e51b815260206004820152602d60248201527f54656c65706f727465725570677261646561626c653a2061646472657373206160448201526c1b1c9958591e481c185d5cd959609a1b60648201526084016103df565b6001600160a01b0381166000818152600160208190526040808320805460ff1916909217909155517f933f93e57a222e6330362af8b376d0a8725b6901e9a2fb86d00f169702b28a4c9190a250565b6104be61087c565b6001600160a01b0381166104e45760405162461bcd60e51b81526004016103df906132fd565b6001600160a01b03811660009081526001602052604090205460ff1661055e5760405162461bcd60e51b815260206004820152602960248201527f54656c65706f727465725570677261646561626c653a2061646472657373206e6044820152681bdd081c185d5cd95960ba1b60648201526084016103df565b6040516001600160a01b038216907f844e2f3154214672229235858fd029d1dfd543901c6d05931f0bc2480a2d72c390600090a26001600160a01b03166000908152600160205260409020805460ff19169055565b6105cb6105c5368490038401846133ff565b82610884565b5050565b6105d761087c565b6105e081610a8f565b50565b6105cb7f0000000000000000000000000000000000000000000000000000000000000000303361061286613508565b85610c2f565b610620610e35565b61062a6000610e8f565b565b610634610ee1565b6002546001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016634c1f08ce336040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602401602060405180830381865afa1580156106ab573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106cf91906135d9565b10156107365760405162461bcd60e51b815260206004820152603060248201527f54656c65706f727465725570677261646561626c653a20696e76616c6964205460448201526f32b632b837b93a32b91039b2b73232b960811b60648201526084016103df565b61073f33610264565b156107a55760405162461bcd60e51b815260206004820152603060248201527f54656c65706f727465725570677261646561626c653a2054656c65706f72746560448201526f1c881859191c995cdcc81c185d5cd95960821b60648201526084016103df565b6107e6848484848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610f3a92505050565b6107f06001600055565b50505050565b6107fe610e35565b6001600160a01b0381166108635760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016103df565b6105e081610e8f565b61087783838361133d565b505050565b61062a610e35565b6001600454146108a65760405162461bcd60e51b81526004016103df906135f2565b60026004556108b48261150a565b60e08201516001600160a01b0316156108df5760405162461bcd60e51b81526004016103df90613636565b6000806108ff8460000151856020015185876060015188608001516115b4565b9150915060006040518060400160405280600160048111156109235761092361367c565b8152602001604051806040016040528088604001516001600160a01b031681526020018681525060405160200161095a9190613692565b60405160208183030381529060405281525090506000610a3c6040518060c001604052808860000151815260200188602001516001600160a01b0316815260200160405180604001604052808a606001516001600160a01b031681526020018781525081526020018860c00151815260200160006001600160401b038111156109e5576109e561334b565b604051908082528060200260200182016040528015610a0e578160200160208202803683370190505b50815260200184604051602001610a259190613702565b60405160208183030381529060405281525061175e565b9050336001600160a01b0316817f93f19bf1ec58a15dc643b37e7e18a1c13e85e06cd11929e283154691ace9fb528887604051610a7a929190613747565b60405180910390a35050600160045550505050565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663c07f47d46040518163ffffffff1660e01b8152600401602060405180830381865afa158015610aef573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b1391906135d9565b60025490915081831115610b835760405162461bcd60e51b815260206004820152603160248201527f54656c65706f727465725570677261646561626c653a20696e76616c6964205460448201527032b632b837b93a32b9103b32b939b4b7b760791b60648201526084016103df565b808311610bf85760405162461bcd60e51b815260206004820152603f60248201527f54656c65706f727465725570677261646561626c653a206e6f7420677265617460448201527f6572207468616e2063757272656e74206d696e696d756d2076657273696f6e0060648201526084016103df565b6002839055604051839082907fa9a7ef57e41f05b4c15480842f5f0c27edfcbb553fed281f7c4068452cc1c02d90600090a3505050565b600160045414610c515760405162461bcd60e51b81526004016103df906135f2565b6002600455610c5f82611884565b60c08201516001600160a01b031615610c8a5760405162461bcd60e51b81526004016103df90613636565b600080610cac84600001518560200151858761010001518861012001516115b4565b915091506000604051806040016040528060026004811115610cd057610cd061367c565b81526020016040518061010001604052808b81526020018a6001600160a01b03168152602001896001600160a01b0316815260200188604001516001600160a01b03168152602001868152602001886060015181526020018860a0015181526020018860e001516001600160a01b0316815250604051602001610d5391906137c9565b60405160208183030381529060405281525090506000610ddf6040518060c001604052808860000151815260200188602001516001600160a01b0316815260200160405180604001604052808a61010001516001600160a01b031681526020018781525081526020018860800151815260200160006001600160401b038111156109e5576109e561334b565b9050866001600160a01b0316817f5d76dff81bf773b908b050fa113d39f7d8135bb4175398f313ea19cd3a1a0b168887604051610e1d929190613868565b60405180910390a35050600160045550505050505050565b6003546001600160a01b0316331461062a5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103df565b600380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600260005403610f335760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064016103df565b6002600055565b600081806020019051810190610f509190613990565b9050600181516004811115610f6757610f6761367c565b03610fb15760008160200151806020019051810190610f869190613a1e565b90506000610f9986868460200151611a6b565b9050610fa9826000015182611adb565b505050505050565b600281516004811115610fc657610fc661367c565b036110e15760008160200151806020019051810190610fe59190613a58565b90506000610ff886868460800151611a6b565b8251909150861461105e5760405162461bcd60e51b815260206004820152602a60248201527f546f6b656e486f6d653a206d69736d61746368656420736f7572636520626c6f60448201526918dad8da185a5b88125160b21b60648201526084016103df565b846001600160a01b031682602001516001600160a01b0316146110d75760405162461bcd60e51b815260206004820152602b60248201527f546f6b656e486f6d653a206d69736d617463686564206f726967696e2073656e60448201526a646572206164647265737360a81b60648201526084016103df565b610fa98282611b52565b6003815160048111156110f6576110f661367c565b036111d757600081602001518060200190518101906111159190613b27565b905060008061112e878785606001518660800151611dbc565b915091506111ce6040518061010001604052808560000151815260200185602001516001600160a01b0316815260200185604001516001600160a01b031681526020017f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602001838152602001600081526020018560a0015181526020018560c001516001600160a01b031681525083611e4d565b50505050505050565b6004815160048111156111ec576111ec61367c565b036112f5576000816020015180602001905181019061120b9190613bc2565b905060008061122587878560800151866101400151611dbc565b915091506111ce878785600001516040518061016001604052808860200151815260200188604001516001600160a01b0316815260200188606001516001600160a01b031681526020018860a00151815260200188610100015181526020018860c0015181526020018861012001516001600160a01b031681526020018860e001516001600160a01b031681526020017f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602001868152602001600081525086611fd4565b60008151600481111561130a5761130a61367c565b036107f057600081602001518060200190518101906113299190613cd0565b90506113368585836121ad565b5050505050565b60016004541461135f5760405162461bcd60e51b81526004016103df906135f2565b6002600481905560008481526005602090815260408083206001600160a01b03871684528252918290208251608081018452815460ff908116151580835260018401549483019490945294820154938101939093526003015490921615156060820152906113df5760405162461bcd60e51b81526004016103df90613d34565b600081602001511161143d5760405162461bcd60e51b815260206004820152602160248201527f546f6b656e486f6d653a207a65726f20636f6c6c61746572616c206e656564656044820152601960fa1b60648201526084016103df565b611446826125ae565b915060008082602001518410611477576020830151600092506114699085613d7f565b90508260200151935061148a565b8383602001516114879190613d7f565b91505b60008681526005602090815260408083206001600160a01b03891680855290835292819020600101859055805187815291820185905288917f6769a5f9bfc8b6e0db839ab981cbf9239274ae72d2d035081a9157d43bd33cb6910160405180910390a380156114fd576114fd3382611adb565b5050600160045550505050565b60408101516001600160a01b031661156e5760405162461bcd60e51b815260206004820152602160248201527f546f6b656e486f6d653a207a65726f20726563697069656e74206164647265736044820152607360f81b60648201526084016103df565b60008160c00151116115925760405162461bcd60e51b81526004016103df90613d92565b60a0810151156105e05760405162461bcd60e51b81526004016103df90613dd4565b60008581526005602090815260408083206001600160a01b038816845282528083208151608081018352815460ff90811615158083526001840154958301959095526002830154938201939093526003909101549091161515606082015282916116305760405162461bcd60e51b81526004016103df90613d34565b6020810151156116925760405162461bcd60e51b815260206004820152602760248201527f546f6b656e486f6d653a20636f6c6c61746572616c206e656564656420666f726044820152662072656d6f746560c81b60648201526084016103df565b61169b866125ae565b955083156116b1576116ae8533866125e1565b93505b60006116c6826040015183606001518961274a565b9050600081116117185760405162461bcd60e51b815260206004820152601d60248201527f546f6b656e486f6d653a207a65726f207363616c656420616d6f756e7400000060448201526064016103df565b60008981526006602090815260408083206001600160a01b038c1684529091528120805483929061174a908490613e15565b909155509099949850939650505050505050565b600080611769612761565b6040840151602001519091501561180e576040830151516001600160a01b03166117eb5760405162461bcd60e51b815260206004820152602d60248201527f54656c65706f727465725570677261646561626c653a207a65726f206665652060448201526c746f6b656e206164647265737360981b60648201526084016103df565b60408301516020810151905161180e916001600160a01b03909116908390612875565b604051630624488560e41b81526001600160a01b0382169063624488509061183a908690600401613e28565b6020604051808303816000875af1158015611859573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061187d91906135d9565b9392505050565b60408101516001600160a01b03166118f15760405162461bcd60e51b815260206004820152602a60248201527f546f6b656e486f6d653a207a65726f20726563697069656e7420636f6e7472616044820152696374206164647265737360b01b60648201526084016103df565b60008160800151116119155760405162461bcd60e51b81526004016103df90613d92565b60008160a00151116119755760405162461bcd60e51b815260206004820152602360248201527f546f6b656e486f6d653a207a65726f20726563697069656e7420676173206c696044820152621b5a5d60ea1b60648201526084016103df565b80608001518160a00151106119db5760405162461bcd60e51b815260206004820152602660248201527f546f6b656e486f6d653a20696e76616c696420726563697069656e7420676173604482015265081b1a5b5a5d60d21b60648201526084016103df565b60e08101516001600160a01b0316611a485760405162461bcd60e51b815260206004820152602a60248201527f546f6b656e486f6d653a207a65726f2066616c6c6261636b20726563697069656044820152696e74206164647265737360b01b60648201526084016103df565b610140810151156105e05760405162461bcd60e51b81526004016103df90613dd4565b60008381526005602090815260408083206001600160a01b038616845282528083208151608081018352815460ff908116151582526001830154948201949094526002820154928101929092526003015490911615156060820152611ad28186868661295a565b95945050505050565b816001600160a01b03167f6352c5382c4a4578e712449ca65e83cdb392d045dfcf1cad9615189db2da244b82604051611b1691815260200190565b60405180910390a26105cb6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168383612a4b565b611b817f0000000000000000000000000000000000000000000000000000000000000000836060015183612875565b60008260000151836020015184604001517f0000000000000000000000000000000000000000000000000000000000000000858760a00151604051602401611bce96959493929190613ee1565b60408051601f198184030181529190526020810180516001600160e01b03166394395edd60e01b17905260c08401516060850151919250600091611c13919084612a7b565b6060850151604051636eb1769f60e11b81523060048201526001600160a01b0391821660248201529192506000917f00000000000000000000000000000000000000000000000000000000000000009091169063dd62ed3e90604401602060405180830381865afa158015611c8c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cb091906135d9565b9050611ce27f000000000000000000000000000000000000000000000000000000000000000086606001516000612a8a565b8115611d345784606001516001600160a01b03167f104deb555f67e63782bb817bc26c39050894645f9b9f29c4be8ae68d0e8b7ff485604051611d2791815260200190565b60405180910390a2611d7c565b84606001516001600160a01b03167fb9eaeae386d339f8115782f297a9e5f0e13fb587cd6b0d502f113cb8dd4d6cb085604051611d7391815260200190565b60405180910390a25b80156113365760e0850151611336906001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169083612a4b565b60008481526005602090815260408083206001600160a01b038716845282528083208151608081018352815460ff908116151582526001830154948201949094526002820154928101929092526003015490911615156060820152819081611e268289898961295a565b90506000611e3d8360400151846060015188612b9f565b9199919850909650505050505050565b600160045414611e6f5760405162461bcd60e51b81526004016103df906135f2565b6002600455611e7d8261150a565b6000611e9783600001518460200151848660800151612bae565b905080600003611eb557611eaf8360e0015183611adb565b50611fcb565b604080518082019091526000908060018152602001604051806040016040528087604001516001600160a01b0316815260200185815250604051602001611efc9190613692565b60405160208183030381529060405281525090506000611f8b6040518060c001604052808760000151815260200187602001516001600160a01b03168152602001604051806040016040528089606001516001600160a01b03168152602001896080015181525081526020018760c00151815260200160006001600160401b038111156109e5576109e561334b565b9050807f825080857c76cef4a1629c0705a7f8b4ef0282ddcafde0b6715c4fb34b68aaf08685604051611fbf929190613747565b60405180910390a25050505b50506001600455565b600160045414611ff65760405162461bcd60e51b81526004016103df906135f2565b600260045561200482611884565b600061201f8360000151846020015184866101200151612bae565b90508060000361203d576120378360c0015183611adb565b506121a1565b6040805180820190915260009080600281526020016040518061010001604052808a8152602001896001600160a01b03168152602001886001600160a01b0316815260200187604001516001600160a01b03168152602001858152602001876060015181526020018760a0015181526020018760e001516001600160a01b03168152506040516020016120d091906137c9565b604051602081830303815290604052815250905060006121616040518060c001604052808760000151815260200187602001516001600160a01b0316815260200160405180604001604052808961010001516001600160a01b0316815260200189610120015181525081526020018760800151815260200160006001600160401b038111156109e5576109e561334b565b9050807f42eff9005856e3c586b096d67211a566dc926052119fd7cc08023c70937ecb308685604051612195929190613868565b60405180910390a25050505b50506001600455505050565b826122065760405162461bcd60e51b8152602060048201526024808201527f546f6b656e486f6d653a207a65726f2072656d6f746520626c6f636b636861696044820152631b88125160e21b60648201526084016103df565b7f0000000000000000000000000000000000000000000000000000000000000000830361228d5760405162461bcd60e51b815260206004820152602f60248201527f546f6b656e486f6d653a2063616e6e6f742072656769737465722072656d6f7460448201526e329037b71039b0b6b29031b430b4b760891b60648201526084016103df565b6001600160a01b0382166122fc5760405162461bcd60e51b815260206004820152603060248201527f546f6b656e486f6d653a207a65726f2072656d6f746520746f6b656e2074726160448201526f6e73666572726572206164647265737360801b60648201526084016103df565b60008381526005602090815260408083206001600160a01b038616845290915290205460ff161561237b5760405162461bcd60e51b8152602060048201526024808201527f546f6b656e486f6d653a2072656d6f746520616c726561647920726567697374604482015263195c995960e21b60648201526084016103df565b6012816040015160ff1611156123e55760405162461bcd60e51b815260206004820152602960248201527f546f6b656e486f6d653a2072656d6f746520746f6b656e20646563696d616c73604482015268040e8dede40d0d2ced60bb1b60648201526084016103df565b7f000000000000000000000000000000000000000000000000000000000000000060ff16816020015160ff161461246d5760405162461bcd60e51b815260206004820152602660248201527f546f6b656e486f6d653a20696e76616c696420686f6d6520746f6b656e20646560448201526563696d616c7360d01b60648201526084016103df565b60008061249e7f00000000000000000000000000000000000000000000000000000000000000008460400151612d07565b9150915060006124b383838660000151612b9f565b90508180156124cd575083516124ca908490613f38565b15155b156124e0576124dd600182613e15565b90505b60408051608081018252600180825260208083018581528385018881528715156060860190815260008d8152600585528781206001600160a01b038e1680835295528790209551865490151560ff1991821617875592519486019490945551600285015591516003909301805493151593909216929092179055858201519151909188917ff229b02a51a4c8d5ef03a096ae0dd727d7b48b710d21b50ebebb560eef739b909161259e9186919091825260ff16602082015260400190565b60405180910390a3505050505050565b60006125db7f000000000000000000000000000000000000000000000000000000000000000033846125e1565b92915050565b6040516370a0823160e01b815230600482015260009081906001600160a01b038616906370a0823190602401602060405180830381865afa15801561262a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061264e91906135d9565b90506126656001600160a01b038616853086612d50565b6040516370a0823160e01b81523060048201526000906001600160a01b038716906370a0823190602401602060405180830381865afa1580156126ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126d091906135d9565b90508181116127365760405162461bcd60e51b815260206004820152602c60248201527f5361666545524332305472616e7366657246726f6d3a2062616c616e6365206e60448201526b1bdd081a5b98dc99585cd95960a21b60648201526084016103df565b6127408282613d7f565b9695505050505050565b60006127598484846001612d88565b949350505050565b6000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d820e64f6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156127c2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127e69190613f4c565b905061280a816001600160a01b031660009081526001602052604090205460ff1690565b156128705760405162461bcd60e51b815260206004820152603060248201527f54656c65706f727465725570677261646561626c653a2054656c65706f72746560448201526f1c881cd95b991a5b99c81c185d5cd95960821b60648201526084016103df565b919050565b604051636eb1769f60e11b81523060048201526001600160a01b038381166024830152600091839186169063dd62ed3e90604401602060405180830381865afa1580156128c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128ea91906135d9565b6128f49190613e15565b6040516001600160a01b0385166024820152604481018290529091506107f090859063095ea7b360e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152612db0565b835160009061297b5760405162461bcd60e51b81526004016103df90613d34565b6020850151156129d95760405162461bcd60e51b8152602060048201526024808201527f546f6b656e486f6d653a2072656d6f7465206e6f7420636f6c6c61746572616c6044820152631a5e995960e21b60648201526084016103df565b6129e4848484612e82565b60006129f98660400151876060015185612b9f565b905060008111611ad25760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e486f6d653a207a65726f20746f6b656e20616d6f756e740000000060448201526064016103df565b6040516001600160a01b03831660248201526044810182905261087790849063a9059cbb60e01b90606401612923565b60006127598460008585612f43565b801580612b045750604051636eb1769f60e11b81523060048201526001600160a01b03838116602483015284169063dd62ed3e90604401602060405180830381865afa158015612ade573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b0291906135d9565b155b612b6f5760405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b60648201526084016103df565b6040516001600160a01b03831660248201526044810182905261087790849063095ea7b360e01b90606401612923565b60006127598484846000612d88565b60008481526005602090815260408083206001600160a01b038716845282528083208151608081018352815460ff9081161580158352600184015495830195909552600283015493820193909352600390910154909116151560608201529080612c1c575060008160200151115b15612c2b576000915050612759565b828411612c8f5760405162461bcd60e51b815260206004820152602c60248201527f546f6b656e486f6d653a20696e73756666696369656e7420616d6f756e74207460448201526b6f20636f766572206665657360a01b60648201526084016103df565b612c998385613d7f565b93506000612cb0826040015183606001518761274a565b905080600003612cc557600092505050612759565b60008781526006602090815260408083206001600160a01b038a16845290915281208054839290612cf7908490613e15565b9091555090979650505050505050565b60008060ff808516908416118181612d2b57612d238587613f69565b60ff16612d39565b612d358686613f69565b60ff165b612d4490600a614066565b96919550909350505050565b6040516001600160a01b03808516602483015283166044820152606481018290526107f09085906323b872dd60e01b90608401612923565b600081151584151503612da657612d9f8584614072565b9050612759565b611ad28584614089565b6000612e05826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166130189092919063ffffffff16565b8051909150156108775780806020019051810190612e23919061409d565b6108775760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016103df565b60008381526006602090815260408083206001600160a01b038616845290915290205481811015612f0c5760405162461bcd60e51b815260206004820152602e60248201527f546f6b656e486f6d653a20696e73756666696369656e7420746f6b656e20747260448201526d616e736665722062616c616e636560901b60648201526084016103df565b612f168282613d7f565b60009485526006602090815260408087206001600160a01b03909616875294905292909320919091555050565b6000845a1015612f955760405162461bcd60e51b815260206004820152601b60248201527f43616c6c5574696c733a20696e73756666696369656e7420676173000000000060448201526064016103df565b83471015612fe55760405162461bcd60e51b815260206004820152601d60248201527f43616c6c5574696c733a20696e73756666696369656e742076616c756500000060448201526064016103df565b826001600160a01b03163b600003612fff57506000612759565b600080600084516020860188888bf19695505050505050565b6060612759848460008585600080866001600160a01b0316858760405161303f91906140bf565b60006040518083038185875af1925050503d806000811461307c576040519150601f19603f3d011682016040523d82523d6000602084013e613081565b606091505b50915091506130928783838761309d565b979650505050505050565b6060831561310c578251600003613105576001600160a01b0385163b6131055760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016103df565b5081612759565b61275983838151156131215781518083602001fd5b8060405162461bcd60e51b81526004016103df91906140db565b6001600160a01b03811681146105e057600080fd5b80356128708161313b565b60006020828403121561316d57600080fd5b813561187d8161313b565b6000806040838503121561318b57600080fd5b82359150602083013561319d8161313b565b809150509250929050565b6000808284036101208112156131bd57600080fd5b610100808212156131cd57600080fd5b9395938601359450505050565b6000602082840312156131ec57600080fd5b5035919050565b6000806040838503121561320657600080fd5b82356001600160401b0381111561321c57600080fd5b8301610160818603121561322f57600080fd5b946020939093013593505050565b6000806000806060858703121561325357600080fd5b8435935060208501356132658161313b565b925060408501356001600160401b038082111561328157600080fd5b818701915087601f83011261329557600080fd5b8135818111156132a457600080fd5b8860208285010111156132b657600080fd5b95989497505060200194505050565b6000806000606084860312156132da57600080fd5b8335925060208401356132ec8161313b565b929592945050506040919091013590565b6020808252602e908201527f54656c65706f727465725570677261646561626c653a207a65726f2054656c6560408201526d706f72746572206164647265737360901b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b60405161010081016001600160401b03811182821017156133845761338461334b565b60405290565b60405161016081016001600160401b03811182821017156133845761338461334b565b604080519081016001600160401b03811182821017156133845761338461334b565b604051601f8201601f191681016001600160401b03811182821017156133f7576133f761334b565b604052919050565b6000610100828403121561341257600080fd5b61341a613361565b82358152602083013561342c8161313b565b6020820152604083013561343f8161313b565b604082015261345060608401613150565b60608201526080830135608082015260a083013560a082015260c083013560c082015261347f60e08401613150565b60e08201529392505050565b60006001600160401b038211156134a4576134a461334b565b50601f01601f191660200190565b600082601f8301126134c357600080fd5b81356134d66134d18261348b565b6133cf565b8181528460208386010111156134eb57600080fd5b816020850160208301376000918101602001919091529392505050565b6000610160823603121561351b57600080fd5b61352361338a565b8235815261353360208401613150565b602082015261354460408401613150565b604082015260608301356001600160401b0381111561356257600080fd5b61356e368286016134b2565b6060830152506080830135608082015260a083013560a082015261359460c08401613150565b60c08201526135a560e08401613150565b60e08201526101006135b8818501613150565b90820152610120838101359082015261014092830135928101929092525090565b6000602082840312156135eb57600080fd5b5051919050565b60208082526024908201527f53656e645265656e7472616e637947756172643a2073656e64207265656e7472604082015263616e637960e01b606082015260800190565b60208082526026908201527f546f6b656e486f6d653a206e6f6e2d7a65726f206d756c74692d686f702066616040820152656c6c6261636b60d01b606082015260800190565b634e487b7160e01b600052602160045260246000fd5b81516001600160a01b0316815260208083015190820152604081016125db565b60005b838110156136cd5781810151838201526020016136b5565b50506000910152565b600081518084526136ee8160208601602086016136b2565b601f01601f19169290920160200192915050565b60208152600082516005811061372857634e487b7160e01b600052602160045260246000fd5b80602084015250602083015160408084015261275960608401826136d6565b60006101208201905083518252602084015160018060a01b03808216602085015280604087015116604085015280606087015116606085015250506080840151608083015260a084015160a083015260c084015160c083015260e08401516137ba60e08401826001600160a01b03169052565b50826101008301529392505050565b60208152815160208201526000602083015160018060a01b0380821660408501528060408601511660608501525050606083015161381260808401826001600160a01b03169052565b50608083015160a083015260a08301516101008060c08501526138396101208501836136d6565b915060c085015160e085015260e085015161385e828601826001600160a01b03169052565b5090949350505050565b60408152825160408201526000602084015161388f60608401826001600160a01b03169052565b5060408401516001600160a01b03166080830152606084015161016060a084018190526138c06101a08501836136d6565b9150608086015160c085015260a086015160e085015260c08601516101006138f2818701836001600160a01b03169052565b60e08801519150610120613910818801846001600160a01b03169052565b9088015191506101409061392e878301846001600160a01b03169052565b880151928601929092525090940151610180830152506020015290565b600082601f83011261395c57600080fd5b815161396a6134d18261348b565b81815284602083860101111561397f57600080fd5b6127598260208301602087016136b2565b6000602082840312156139a257600080fd5b81516001600160401b03808211156139b957600080fd5b90830190604082860312156139cd57600080fd5b6139d56133ad565b8251600581106139e457600080fd5b81526020830151828111156139f857600080fd5b613a048782860161394b565b60208301525095945050505050565b80516128708161313b565b600060408284031215613a3057600080fd5b613a386133ad565b8251613a438161313b565b81526020928301519281019290925250919050565b600060208284031215613a6a57600080fd5b81516001600160401b0380821115613a8157600080fd5b908301906101008286031215613a9657600080fd5b613a9e613361565b82518152613aae60208401613a13565b6020820152613abf60408401613a13565b6040820152613ad060608401613a13565b60608201526080830151608082015260a083015182811115613af157600080fd5b613afd8782860161394b565b60a08301525060c083015160c0820152613b1960e08401613a13565b60e082015295945050505050565b600060e08284031215613b3957600080fd5b60405160e081018181106001600160401b0382111715613b5b57613b5b61334b565b604052825181526020830151613b708161313b565b60208201526040830151613b838161313b565b80604083015250606083015160608201526080830151608082015260a083015160a082015260c0830151613bb68161313b565b60c08201529392505050565b600060208284031215613bd457600080fd5b81516001600160401b0380821115613beb57600080fd5b908301906101608286031215613c0057600080fd5b613c0861338a565b613c1183613a13565b815260208301516020820152613c2960408401613a13565b6040820152613c3a60608401613a13565b60608201526080830151608082015260a083015182811115613c5b57600080fd5b613c678782860161394b565b60a08301525060c083015160c0820152613c8360e08401613a13565b60e082015261010083810151908201526101209150613ca3828401613a13565b9181019190915261014091820151918101919091529392505050565b805160ff8116811461287057600080fd5b600060608284031215613ce257600080fd5b604051606081018181106001600160401b0382111715613d0457613d0461334b565b60405282518152613d1760208401613cbf565b6020820152613d2860408401613cbf565b60408201529392505050565b6020808252818101527f546f6b656e486f6d653a2072656d6f7465206e6f742072656769737465726564604082015260600190565b634e487b7160e01b600052601160045260246000fd5b818103818111156125db576125db613d69565b60208082526022908201527f546f6b656e486f6d653a207a65726f20726571756972656420676173206c696d6040820152611a5d60f21b606082015260800190565b60208082526021908201527f546f6b656e486f6d653a206e6f6e2d7a65726f207365636f6e646172792066656040820152606560f81b606082015260800190565b808201808211156125db576125db613d69565b6020808252825182820152828101516001600160a01b039081166040808501919091528401518051821660608501528083015160808501526000929161010085019190606087015160a0870152608087015160e060c0880152805193849052840192600092506101208701905b80841015613eb757845183168252938501936001939093019290850190613e95565b5060a0880151878203601f190160e08901529450613ed581866136d6565b98975050505050505050565b8681526001600160a01b0386811660208301528581166040830152841660608201526080810183905260c060a08201819052600090613ed5908301846136d6565b634e487b7160e01b600052601260045260246000fd5b600082613f4757613f47613f22565b500690565b600060208284031215613f5e57600080fd5b815161187d8161313b565b60ff82811682821603908111156125db576125db613d69565b600181815b80851115613fbd578160001904821115613fa357613fa3613d69565b80851615613fb057918102915b93841c9390800290613f87565b509250929050565b600082613fd4575060016125db565b81613fe1575060006125db565b8160018114613ff757600281146140015761401d565b60019150506125db565b60ff84111561401257614012613d69565b50506001821b6125db565b5060208310610133831016604e8410600b8410161715614040575081810a6125db565b61404a8383613f82565b806000190482111561405e5761405e613d69565b029392505050565b600061187d8383613fc5565b80820281158282048414176125db576125db613d69565b60008261409857614098613f22565b500490565b6000602082840312156140af57600080fd5b8151801515811461187d57600080fd5b600082516140d18184602087016136b2565b9190910192915050565b60208152600061187d60208301846136d656fea264697066735822122027ee2c8edf5a85411d98a486f46b1b0d4271954ef0a1055ad3cd05958c4db41f64736f6c63430008120033000000000000000000000000f86cb19ad8405aefa7d09c778215d2cb6ebfb228000000000000000000000000efe9e34988a5054907fc125aef5e85aaaf7f928c0000000000000000000000003f964a0630b429ffb1797f2ce9fd8e5a45c5ad180000000000000000000000000000000000000000000000000000000000000012

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106101215760003560e01c80638da5cb5b116100ad578063d2cc7a7011610071578063d2cc7a70146102f3578063d536ec33146102fb578063f2fde38b14610364578063fc0c546a14610377578063fd6582681461039e57600080fd5b80638da5cb5b1461024557806397314297146102565780639d76ea5814610292578063c868efaa146102b9578063d127dc9b146102cc57600080fd5b80634511243e116100f45780634511243e146101f15780635d16225d146102045780635eb9951414610217578063656900381461022a578063715018a61461023d57600080fd5b80631a7f5bec146101265780632b0d8f181461016a5780633b97e8561461017f5780633ca563e3146101b8575b600080fd5b61014d7f000000000000000000000000f86cb19ad8405aefa7d09c778215d2cb6ebfb22881565b6040516001600160a01b0390911681526020015b60405180910390f35b61017d61017836600461315b565b6103b1565b005b6101a67f000000000000000000000000000000000000000000000000000000000000001281565b60405160ff9091168152602001610161565b6101e36101c6366004613178565b600660209081526000928352604080842090915290825290205481565b604051908152602001610161565b61017d6101ff36600461315b565b6104b6565b61017d6102123660046131a8565b6105b3565b61017d6102253660046131da565b6105cf565b61017d6102383660046131f3565b6105e3565b61017d610618565b6003546001600160a01b031661014d565b61028261026436600461315b565b6001600160a01b031660009081526001602052604090205460ff1690565b6040519015158152602001610161565b61014d7f0000000000000000000000003f964a0630b429ffb1797f2ce9fd8e5a45c5ad1881565b61017d6102c736600461323d565b61062c565b6101e37f7fc93d85c6d62c5b2ac0b519c87010ea5294012d1e407030d6acd0021cac10d581565b6002546101e3565b610340610309366004613178565b6005602090815260009283526040808420909152908252902080546001820154600283015460039093015460ff9283169391921684565b60408051941515855260208501939093529183015215156060820152608001610161565b61017d61037236600461315b565b6107f6565b61014d7f0000000000000000000000003f964a0630b429ffb1797f2ce9fd8e5a45c5ad1881565b61017d6103ac3660046132c5565b61086c565b6103b961087c565b6001600160a01b0381166103e85760405162461bcd60e51b81526004016103df906132fd565b60405180910390fd5b6001600160a01b03811660009081526001602052604090205460ff16156104675760405162461bcd60e51b815260206004820152602d60248201527f54656c65706f727465725570677261646561626c653a2061646472657373206160448201526c1b1c9958591e481c185d5cd959609a1b60648201526084016103df565b6001600160a01b0381166000818152600160208190526040808320805460ff1916909217909155517f933f93e57a222e6330362af8b376d0a8725b6901e9a2fb86d00f169702b28a4c9190a250565b6104be61087c565b6001600160a01b0381166104e45760405162461bcd60e51b81526004016103df906132fd565b6001600160a01b03811660009081526001602052604090205460ff1661055e5760405162461bcd60e51b815260206004820152602960248201527f54656c65706f727465725570677261646561626c653a2061646472657373206e6044820152681bdd081c185d5cd95960ba1b60648201526084016103df565b6040516001600160a01b038216907f844e2f3154214672229235858fd029d1dfd543901c6d05931f0bc2480a2d72c390600090a26001600160a01b03166000908152600160205260409020805460ff19169055565b6105cb6105c5368490038401846133ff565b82610884565b5050565b6105d761087c565b6105e081610a8f565b50565b6105cb7f7fc93d85c6d62c5b2ac0b519c87010ea5294012d1e407030d6acd0021cac10d5303361061286613508565b85610c2f565b610620610e35565b61062a6000610e8f565b565b610634610ee1565b6002546001600160a01b037f000000000000000000000000f86cb19ad8405aefa7d09c778215d2cb6ebfb22816634c1f08ce336040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602401602060405180830381865afa1580156106ab573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106cf91906135d9565b10156107365760405162461bcd60e51b815260206004820152603060248201527f54656c65706f727465725570677261646561626c653a20696e76616c6964205460448201526f32b632b837b93a32b91039b2b73232b960811b60648201526084016103df565b61073f33610264565b156107a55760405162461bcd60e51b815260206004820152603060248201527f54656c65706f727465725570677261646561626c653a2054656c65706f72746560448201526f1c881859191c995cdcc81c185d5cd95960821b60648201526084016103df565b6107e6848484848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610f3a92505050565b6107f06001600055565b50505050565b6107fe610e35565b6001600160a01b0381166108635760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016103df565b6105e081610e8f565b61087783838361133d565b505050565b61062a610e35565b6001600454146108a65760405162461bcd60e51b81526004016103df906135f2565b60026004556108b48261150a565b60e08201516001600160a01b0316156108df5760405162461bcd60e51b81526004016103df90613636565b6000806108ff8460000151856020015185876060015188608001516115b4565b9150915060006040518060400160405280600160048111156109235761092361367c565b8152602001604051806040016040528088604001516001600160a01b031681526020018681525060405160200161095a9190613692565b60405160208183030381529060405281525090506000610a3c6040518060c001604052808860000151815260200188602001516001600160a01b0316815260200160405180604001604052808a606001516001600160a01b031681526020018781525081526020018860c00151815260200160006001600160401b038111156109e5576109e561334b565b604051908082528060200260200182016040528015610a0e578160200160208202803683370190505b50815260200184604051602001610a259190613702565b60405160208183030381529060405281525061175e565b9050336001600160a01b0316817f93f19bf1ec58a15dc643b37e7e18a1c13e85e06cd11929e283154691ace9fb528887604051610a7a929190613747565b60405180910390a35050600160045550505050565b60007f000000000000000000000000f86cb19ad8405aefa7d09c778215d2cb6ebfb2286001600160a01b031663c07f47d46040518163ffffffff1660e01b8152600401602060405180830381865afa158015610aef573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b1391906135d9565b60025490915081831115610b835760405162461bcd60e51b815260206004820152603160248201527f54656c65706f727465725570677261646561626c653a20696e76616c6964205460448201527032b632b837b93a32b9103b32b939b4b7b760791b60648201526084016103df565b808311610bf85760405162461bcd60e51b815260206004820152603f60248201527f54656c65706f727465725570677261646561626c653a206e6f7420677265617460448201527f6572207468616e2063757272656e74206d696e696d756d2076657273696f6e0060648201526084016103df565b6002839055604051839082907fa9a7ef57e41f05b4c15480842f5f0c27edfcbb553fed281f7c4068452cc1c02d90600090a3505050565b600160045414610c515760405162461bcd60e51b81526004016103df906135f2565b6002600455610c5f82611884565b60c08201516001600160a01b031615610c8a5760405162461bcd60e51b81526004016103df90613636565b600080610cac84600001518560200151858761010001518861012001516115b4565b915091506000604051806040016040528060026004811115610cd057610cd061367c565b81526020016040518061010001604052808b81526020018a6001600160a01b03168152602001896001600160a01b0316815260200188604001516001600160a01b03168152602001868152602001886060015181526020018860a0015181526020018860e001516001600160a01b0316815250604051602001610d5391906137c9565b60405160208183030381529060405281525090506000610ddf6040518060c001604052808860000151815260200188602001516001600160a01b0316815260200160405180604001604052808a61010001516001600160a01b031681526020018781525081526020018860800151815260200160006001600160401b038111156109e5576109e561334b565b9050866001600160a01b0316817f5d76dff81bf773b908b050fa113d39f7d8135bb4175398f313ea19cd3a1a0b168887604051610e1d929190613868565b60405180910390a35050600160045550505050505050565b6003546001600160a01b0316331461062a5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103df565b600380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600260005403610f335760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064016103df565b6002600055565b600081806020019051810190610f509190613990565b9050600181516004811115610f6757610f6761367c565b03610fb15760008160200151806020019051810190610f869190613a1e565b90506000610f9986868460200151611a6b565b9050610fa9826000015182611adb565b505050505050565b600281516004811115610fc657610fc661367c565b036110e15760008160200151806020019051810190610fe59190613a58565b90506000610ff886868460800151611a6b565b8251909150861461105e5760405162461bcd60e51b815260206004820152602a60248201527f546f6b656e486f6d653a206d69736d61746368656420736f7572636520626c6f60448201526918dad8da185a5b88125160b21b60648201526084016103df565b846001600160a01b031682602001516001600160a01b0316146110d75760405162461bcd60e51b815260206004820152602b60248201527f546f6b656e486f6d653a206d69736d617463686564206f726967696e2073656e60448201526a646572206164647265737360a81b60648201526084016103df565b610fa98282611b52565b6003815160048111156110f6576110f661367c565b036111d757600081602001518060200190518101906111159190613b27565b905060008061112e878785606001518660800151611dbc565b915091506111ce6040518061010001604052808560000151815260200185602001516001600160a01b0316815260200185604001516001600160a01b031681526020017f0000000000000000000000003f964a0630b429ffb1797f2ce9fd8e5a45c5ad186001600160a01b03168152602001838152602001600081526020018560a0015181526020018560c001516001600160a01b031681525083611e4d565b50505050505050565b6004815160048111156111ec576111ec61367c565b036112f5576000816020015180602001905181019061120b9190613bc2565b905060008061122587878560800151866101400151611dbc565b915091506111ce878785600001516040518061016001604052808860200151815260200188604001516001600160a01b0316815260200188606001516001600160a01b031681526020018860a00151815260200188610100015181526020018860c0015181526020018861012001516001600160a01b031681526020018860e001516001600160a01b031681526020017f0000000000000000000000003f964a0630b429ffb1797f2ce9fd8e5a45c5ad186001600160a01b03168152602001868152602001600081525086611fd4565b60008151600481111561130a5761130a61367c565b036107f057600081602001518060200190518101906113299190613cd0565b90506113368585836121ad565b5050505050565b60016004541461135f5760405162461bcd60e51b81526004016103df906135f2565b6002600481905560008481526005602090815260408083206001600160a01b03871684528252918290208251608081018452815460ff908116151580835260018401549483019490945294820154938101939093526003015490921615156060820152906113df5760405162461bcd60e51b81526004016103df90613d34565b600081602001511161143d5760405162461bcd60e51b815260206004820152602160248201527f546f6b656e486f6d653a207a65726f20636f6c6c61746572616c206e656564656044820152601960fa1b60648201526084016103df565b611446826125ae565b915060008082602001518410611477576020830151600092506114699085613d7f565b90508260200151935061148a565b8383602001516114879190613d7f565b91505b60008681526005602090815260408083206001600160a01b03891680855290835292819020600101859055805187815291820185905288917f6769a5f9bfc8b6e0db839ab981cbf9239274ae72d2d035081a9157d43bd33cb6910160405180910390a380156114fd576114fd3382611adb565b5050600160045550505050565b60408101516001600160a01b031661156e5760405162461bcd60e51b815260206004820152602160248201527f546f6b656e486f6d653a207a65726f20726563697069656e74206164647265736044820152607360f81b60648201526084016103df565b60008160c00151116115925760405162461bcd60e51b81526004016103df90613d92565b60a0810151156105e05760405162461bcd60e51b81526004016103df90613dd4565b60008581526005602090815260408083206001600160a01b038816845282528083208151608081018352815460ff90811615158083526001840154958301959095526002830154938201939093526003909101549091161515606082015282916116305760405162461bcd60e51b81526004016103df90613d34565b6020810151156116925760405162461bcd60e51b815260206004820152602760248201527f546f6b656e486f6d653a20636f6c6c61746572616c206e656564656420666f726044820152662072656d6f746560c81b60648201526084016103df565b61169b866125ae565b955083156116b1576116ae8533866125e1565b93505b60006116c6826040015183606001518961274a565b9050600081116117185760405162461bcd60e51b815260206004820152601d60248201527f546f6b656e486f6d653a207a65726f207363616c656420616d6f756e7400000060448201526064016103df565b60008981526006602090815260408083206001600160a01b038c1684529091528120805483929061174a908490613e15565b909155509099949850939650505050505050565b600080611769612761565b6040840151602001519091501561180e576040830151516001600160a01b03166117eb5760405162461bcd60e51b815260206004820152602d60248201527f54656c65706f727465725570677261646561626c653a207a65726f206665652060448201526c746f6b656e206164647265737360981b60648201526084016103df565b60408301516020810151905161180e916001600160a01b03909116908390612875565b604051630624488560e41b81526001600160a01b0382169063624488509061183a908690600401613e28565b6020604051808303816000875af1158015611859573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061187d91906135d9565b9392505050565b60408101516001600160a01b03166118f15760405162461bcd60e51b815260206004820152602a60248201527f546f6b656e486f6d653a207a65726f20726563697069656e7420636f6e7472616044820152696374206164647265737360b01b60648201526084016103df565b60008160800151116119155760405162461bcd60e51b81526004016103df90613d92565b60008160a00151116119755760405162461bcd60e51b815260206004820152602360248201527f546f6b656e486f6d653a207a65726f20726563697069656e7420676173206c696044820152621b5a5d60ea1b60648201526084016103df565b80608001518160a00151106119db5760405162461bcd60e51b815260206004820152602660248201527f546f6b656e486f6d653a20696e76616c696420726563697069656e7420676173604482015265081b1a5b5a5d60d21b60648201526084016103df565b60e08101516001600160a01b0316611a485760405162461bcd60e51b815260206004820152602a60248201527f546f6b656e486f6d653a207a65726f2066616c6c6261636b20726563697069656044820152696e74206164647265737360b01b60648201526084016103df565b610140810151156105e05760405162461bcd60e51b81526004016103df90613dd4565b60008381526005602090815260408083206001600160a01b038616845282528083208151608081018352815460ff908116151582526001830154948201949094526002820154928101929092526003015490911615156060820152611ad28186868661295a565b95945050505050565b816001600160a01b03167f6352c5382c4a4578e712449ca65e83cdb392d045dfcf1cad9615189db2da244b82604051611b1691815260200190565b60405180910390a26105cb6001600160a01b037f0000000000000000000000003f964a0630b429ffb1797f2ce9fd8e5a45c5ad18168383612a4b565b611b817f0000000000000000000000003f964a0630b429ffb1797f2ce9fd8e5a45c5ad18836060015183612875565b60008260000151836020015184604001517f0000000000000000000000003f964a0630b429ffb1797f2ce9fd8e5a45c5ad18858760a00151604051602401611bce96959493929190613ee1565b60408051601f198184030181529190526020810180516001600160e01b03166394395edd60e01b17905260c08401516060850151919250600091611c13919084612a7b565b6060850151604051636eb1769f60e11b81523060048201526001600160a01b0391821660248201529192506000917f0000000000000000000000003f964a0630b429ffb1797f2ce9fd8e5a45c5ad189091169063dd62ed3e90604401602060405180830381865afa158015611c8c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cb091906135d9565b9050611ce27f0000000000000000000000003f964a0630b429ffb1797f2ce9fd8e5a45c5ad1886606001516000612a8a565b8115611d345784606001516001600160a01b03167f104deb555f67e63782bb817bc26c39050894645f9b9f29c4be8ae68d0e8b7ff485604051611d2791815260200190565b60405180910390a2611d7c565b84606001516001600160a01b03167fb9eaeae386d339f8115782f297a9e5f0e13fb587cd6b0d502f113cb8dd4d6cb085604051611d7391815260200190565b60405180910390a25b80156113365760e0850151611336906001600160a01b037f0000000000000000000000003f964a0630b429ffb1797f2ce9fd8e5a45c5ad18169083612a4b565b60008481526005602090815260408083206001600160a01b038716845282528083208151608081018352815460ff908116151582526001830154948201949094526002820154928101929092526003015490911615156060820152819081611e268289898961295a565b90506000611e3d8360400151846060015188612b9f565b9199919850909650505050505050565b600160045414611e6f5760405162461bcd60e51b81526004016103df906135f2565b6002600455611e7d8261150a565b6000611e9783600001518460200151848660800151612bae565b905080600003611eb557611eaf8360e0015183611adb565b50611fcb565b604080518082019091526000908060018152602001604051806040016040528087604001516001600160a01b0316815260200185815250604051602001611efc9190613692565b60405160208183030381529060405281525090506000611f8b6040518060c001604052808760000151815260200187602001516001600160a01b03168152602001604051806040016040528089606001516001600160a01b03168152602001896080015181525081526020018760c00151815260200160006001600160401b038111156109e5576109e561334b565b9050807f825080857c76cef4a1629c0705a7f8b4ef0282ddcafde0b6715c4fb34b68aaf08685604051611fbf929190613747565b60405180910390a25050505b50506001600455565b600160045414611ff65760405162461bcd60e51b81526004016103df906135f2565b600260045561200482611884565b600061201f8360000151846020015184866101200151612bae565b90508060000361203d576120378360c0015183611adb565b506121a1565b6040805180820190915260009080600281526020016040518061010001604052808a8152602001896001600160a01b03168152602001886001600160a01b0316815260200187604001516001600160a01b03168152602001858152602001876060015181526020018760a0015181526020018760e001516001600160a01b03168152506040516020016120d091906137c9565b604051602081830303815290604052815250905060006121616040518060c001604052808760000151815260200187602001516001600160a01b0316815260200160405180604001604052808961010001516001600160a01b0316815260200189610120015181525081526020018760800151815260200160006001600160401b038111156109e5576109e561334b565b9050807f42eff9005856e3c586b096d67211a566dc926052119fd7cc08023c70937ecb308685604051612195929190613868565b60405180910390a25050505b50506001600455505050565b826122065760405162461bcd60e51b8152602060048201526024808201527f546f6b656e486f6d653a207a65726f2072656d6f746520626c6f636b636861696044820152631b88125160e21b60648201526084016103df565b7f7fc93d85c6d62c5b2ac0b519c87010ea5294012d1e407030d6acd0021cac10d5830361228d5760405162461bcd60e51b815260206004820152602f60248201527f546f6b656e486f6d653a2063616e6e6f742072656769737465722072656d6f7460448201526e329037b71039b0b6b29031b430b4b760891b60648201526084016103df565b6001600160a01b0382166122fc5760405162461bcd60e51b815260206004820152603060248201527f546f6b656e486f6d653a207a65726f2072656d6f746520746f6b656e2074726160448201526f6e73666572726572206164647265737360801b60648201526084016103df565b60008381526005602090815260408083206001600160a01b038616845290915290205460ff161561237b5760405162461bcd60e51b8152602060048201526024808201527f546f6b656e486f6d653a2072656d6f746520616c726561647920726567697374604482015263195c995960e21b60648201526084016103df565b6012816040015160ff1611156123e55760405162461bcd60e51b815260206004820152602960248201527f546f6b656e486f6d653a2072656d6f746520746f6b656e20646563696d616c73604482015268040e8dede40d0d2ced60bb1b60648201526084016103df565b7f000000000000000000000000000000000000000000000000000000000000001260ff16816020015160ff161461246d5760405162461bcd60e51b815260206004820152602660248201527f546f6b656e486f6d653a20696e76616c696420686f6d6520746f6b656e20646560448201526563696d616c7360d01b60648201526084016103df565b60008061249e7f00000000000000000000000000000000000000000000000000000000000000128460400151612d07565b9150915060006124b383838660000151612b9f565b90508180156124cd575083516124ca908490613f38565b15155b156124e0576124dd600182613e15565b90505b60408051608081018252600180825260208083018581528385018881528715156060860190815260008d8152600585528781206001600160a01b038e1680835295528790209551865490151560ff1991821617875592519486019490945551600285015591516003909301805493151593909216929092179055858201519151909188917ff229b02a51a4c8d5ef03a096ae0dd727d7b48b710d21b50ebebb560eef739b909161259e9186919091825260ff16602082015260400190565b60405180910390a3505050505050565b60006125db7f0000000000000000000000003f964a0630b429ffb1797f2ce9fd8e5a45c5ad1833846125e1565b92915050565b6040516370a0823160e01b815230600482015260009081906001600160a01b038616906370a0823190602401602060405180830381865afa15801561262a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061264e91906135d9565b90506126656001600160a01b038616853086612d50565b6040516370a0823160e01b81523060048201526000906001600160a01b038716906370a0823190602401602060405180830381865afa1580156126ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126d091906135d9565b90508181116127365760405162461bcd60e51b815260206004820152602c60248201527f5361666545524332305472616e7366657246726f6d3a2062616c616e6365206e60448201526b1bdd081a5b98dc99585cd95960a21b60648201526084016103df565b6127408282613d7f565b9695505050505050565b60006127598484846001612d88565b949350505050565b6000807f000000000000000000000000f86cb19ad8405aefa7d09c778215d2cb6ebfb2286001600160a01b031663d820e64f6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156127c2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127e69190613f4c565b905061280a816001600160a01b031660009081526001602052604090205460ff1690565b156128705760405162461bcd60e51b815260206004820152603060248201527f54656c65706f727465725570677261646561626c653a2054656c65706f72746560448201526f1c881cd95b991a5b99c81c185d5cd95960821b60648201526084016103df565b919050565b604051636eb1769f60e11b81523060048201526001600160a01b038381166024830152600091839186169063dd62ed3e90604401602060405180830381865afa1580156128c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128ea91906135d9565b6128f49190613e15565b6040516001600160a01b0385166024820152604481018290529091506107f090859063095ea7b360e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152612db0565b835160009061297b5760405162461bcd60e51b81526004016103df90613d34565b6020850151156129d95760405162461bcd60e51b8152602060048201526024808201527f546f6b656e486f6d653a2072656d6f7465206e6f7420636f6c6c61746572616c6044820152631a5e995960e21b60648201526084016103df565b6129e4848484612e82565b60006129f98660400151876060015185612b9f565b905060008111611ad25760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e486f6d653a207a65726f20746f6b656e20616d6f756e740000000060448201526064016103df565b6040516001600160a01b03831660248201526044810182905261087790849063a9059cbb60e01b90606401612923565b60006127598460008585612f43565b801580612b045750604051636eb1769f60e11b81523060048201526001600160a01b03838116602483015284169063dd62ed3e90604401602060405180830381865afa158015612ade573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b0291906135d9565b155b612b6f5760405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b60648201526084016103df565b6040516001600160a01b03831660248201526044810182905261087790849063095ea7b360e01b90606401612923565b60006127598484846000612d88565b60008481526005602090815260408083206001600160a01b038716845282528083208151608081018352815460ff9081161580158352600184015495830195909552600283015493820193909352600390910154909116151560608201529080612c1c575060008160200151115b15612c2b576000915050612759565b828411612c8f5760405162461bcd60e51b815260206004820152602c60248201527f546f6b656e486f6d653a20696e73756666696369656e7420616d6f756e74207460448201526b6f20636f766572206665657360a01b60648201526084016103df565b612c998385613d7f565b93506000612cb0826040015183606001518761274a565b905080600003612cc557600092505050612759565b60008781526006602090815260408083206001600160a01b038a16845290915281208054839290612cf7908490613e15565b9091555090979650505050505050565b60008060ff808516908416118181612d2b57612d238587613f69565b60ff16612d39565b612d358686613f69565b60ff165b612d4490600a614066565b96919550909350505050565b6040516001600160a01b03808516602483015283166044820152606481018290526107f09085906323b872dd60e01b90608401612923565b600081151584151503612da657612d9f8584614072565b9050612759565b611ad28584614089565b6000612e05826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166130189092919063ffffffff16565b8051909150156108775780806020019051810190612e23919061409d565b6108775760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016103df565b60008381526006602090815260408083206001600160a01b038616845290915290205481811015612f0c5760405162461bcd60e51b815260206004820152602e60248201527f546f6b656e486f6d653a20696e73756666696369656e7420746f6b656e20747260448201526d616e736665722062616c616e636560901b60648201526084016103df565b612f168282613d7f565b60009485526006602090815260408087206001600160a01b03909616875294905292909320919091555050565b6000845a1015612f955760405162461bcd60e51b815260206004820152601b60248201527f43616c6c5574696c733a20696e73756666696369656e7420676173000000000060448201526064016103df565b83471015612fe55760405162461bcd60e51b815260206004820152601d60248201527f43616c6c5574696c733a20696e73756666696369656e742076616c756500000060448201526064016103df565b826001600160a01b03163b600003612fff57506000612759565b600080600084516020860188888bf19695505050505050565b6060612759848460008585600080866001600160a01b0316858760405161303f91906140bf565b60006040518083038185875af1925050503d806000811461307c576040519150601f19603f3d011682016040523d82523d6000602084013e613081565b606091505b50915091506130928783838761309d565b979650505050505050565b6060831561310c578251600003613105576001600160a01b0385163b6131055760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016103df565b5081612759565b61275983838151156131215781518083602001fd5b8060405162461bcd60e51b81526004016103df91906140db565b6001600160a01b03811681146105e057600080fd5b80356128708161313b565b60006020828403121561316d57600080fd5b813561187d8161313b565b6000806040838503121561318b57600080fd5b82359150602083013561319d8161313b565b809150509250929050565b6000808284036101208112156131bd57600080fd5b610100808212156131cd57600080fd5b9395938601359450505050565b6000602082840312156131ec57600080fd5b5035919050565b6000806040838503121561320657600080fd5b82356001600160401b0381111561321c57600080fd5b8301610160818603121561322f57600080fd5b946020939093013593505050565b6000806000806060858703121561325357600080fd5b8435935060208501356132658161313b565b925060408501356001600160401b038082111561328157600080fd5b818701915087601f83011261329557600080fd5b8135818111156132a457600080fd5b8860208285010111156132b657600080fd5b95989497505060200194505050565b6000806000606084860312156132da57600080fd5b8335925060208401356132ec8161313b565b929592945050506040919091013590565b6020808252602e908201527f54656c65706f727465725570677261646561626c653a207a65726f2054656c6560408201526d706f72746572206164647265737360901b606082015260800190565b634e487b7160e01b600052604160045260246000fd5b60405161010081016001600160401b03811182821017156133845761338461334b565b60405290565b60405161016081016001600160401b03811182821017156133845761338461334b565b604080519081016001600160401b03811182821017156133845761338461334b565b604051601f8201601f191681016001600160401b03811182821017156133f7576133f761334b565b604052919050565b6000610100828403121561341257600080fd5b61341a613361565b82358152602083013561342c8161313b565b6020820152604083013561343f8161313b565b604082015261345060608401613150565b60608201526080830135608082015260a083013560a082015260c083013560c082015261347f60e08401613150565b60e08201529392505050565b60006001600160401b038211156134a4576134a461334b565b50601f01601f191660200190565b600082601f8301126134c357600080fd5b81356134d66134d18261348b565b6133cf565b8181528460208386010111156134eb57600080fd5b816020850160208301376000918101602001919091529392505050565b6000610160823603121561351b57600080fd5b61352361338a565b8235815261353360208401613150565b602082015261354460408401613150565b604082015260608301356001600160401b0381111561356257600080fd5b61356e368286016134b2565b6060830152506080830135608082015260a083013560a082015261359460c08401613150565b60c08201526135a560e08401613150565b60e08201526101006135b8818501613150565b90820152610120838101359082015261014092830135928101929092525090565b6000602082840312156135eb57600080fd5b5051919050565b60208082526024908201527f53656e645265656e7472616e637947756172643a2073656e64207265656e7472604082015263616e637960e01b606082015260800190565b60208082526026908201527f546f6b656e486f6d653a206e6f6e2d7a65726f206d756c74692d686f702066616040820152656c6c6261636b60d01b606082015260800190565b634e487b7160e01b600052602160045260246000fd5b81516001600160a01b0316815260208083015190820152604081016125db565b60005b838110156136cd5781810151838201526020016136b5565b50506000910152565b600081518084526136ee8160208601602086016136b2565b601f01601f19169290920160200192915050565b60208152600082516005811061372857634e487b7160e01b600052602160045260246000fd5b80602084015250602083015160408084015261275960608401826136d6565b60006101208201905083518252602084015160018060a01b03808216602085015280604087015116604085015280606087015116606085015250506080840151608083015260a084015160a083015260c084015160c083015260e08401516137ba60e08401826001600160a01b03169052565b50826101008301529392505050565b60208152815160208201526000602083015160018060a01b0380821660408501528060408601511660608501525050606083015161381260808401826001600160a01b03169052565b50608083015160a083015260a08301516101008060c08501526138396101208501836136d6565b915060c085015160e085015260e085015161385e828601826001600160a01b03169052565b5090949350505050565b60408152825160408201526000602084015161388f60608401826001600160a01b03169052565b5060408401516001600160a01b03166080830152606084015161016060a084018190526138c06101a08501836136d6565b9150608086015160c085015260a086015160e085015260c08601516101006138f2818701836001600160a01b03169052565b60e08801519150610120613910818801846001600160a01b03169052565b9088015191506101409061392e878301846001600160a01b03169052565b880151928601929092525090940151610180830152506020015290565b600082601f83011261395c57600080fd5b815161396a6134d18261348b565b81815284602083860101111561397f57600080fd5b6127598260208301602087016136b2565b6000602082840312156139a257600080fd5b81516001600160401b03808211156139b957600080fd5b90830190604082860312156139cd57600080fd5b6139d56133ad565b8251600581106139e457600080fd5b81526020830151828111156139f857600080fd5b613a048782860161394b565b60208301525095945050505050565b80516128708161313b565b600060408284031215613a3057600080fd5b613a386133ad565b8251613a438161313b565b81526020928301519281019290925250919050565b600060208284031215613a6a57600080fd5b81516001600160401b0380821115613a8157600080fd5b908301906101008286031215613a9657600080fd5b613a9e613361565b82518152613aae60208401613a13565b6020820152613abf60408401613a13565b6040820152613ad060608401613a13565b60608201526080830151608082015260a083015182811115613af157600080fd5b613afd8782860161394b565b60a08301525060c083015160c0820152613b1960e08401613a13565b60e082015295945050505050565b600060e08284031215613b3957600080fd5b60405160e081018181106001600160401b0382111715613b5b57613b5b61334b565b604052825181526020830151613b708161313b565b60208201526040830151613b838161313b565b80604083015250606083015160608201526080830151608082015260a083015160a082015260c0830151613bb68161313b565b60c08201529392505050565b600060208284031215613bd457600080fd5b81516001600160401b0380821115613beb57600080fd5b908301906101608286031215613c0057600080fd5b613c0861338a565b613c1183613a13565b815260208301516020820152613c2960408401613a13565b6040820152613c3a60608401613a13565b60608201526080830151608082015260a083015182811115613c5b57600080fd5b613c678782860161394b565b60a08301525060c083015160c0820152613c8360e08401613a13565b60e082015261010083810151908201526101209150613ca3828401613a13565b9181019190915261014091820151918101919091529392505050565b805160ff8116811461287057600080fd5b600060608284031215613ce257600080fd5b604051606081018181106001600160401b0382111715613d0457613d0461334b565b60405282518152613d1760208401613cbf565b6020820152613d2860408401613cbf565b60408201529392505050565b6020808252818101527f546f6b656e486f6d653a2072656d6f7465206e6f742072656769737465726564604082015260600190565b634e487b7160e01b600052601160045260246000fd5b818103818111156125db576125db613d69565b60208082526022908201527f546f6b656e486f6d653a207a65726f20726571756972656420676173206c696d6040820152611a5d60f21b606082015260800190565b60208082526021908201527f546f6b656e486f6d653a206e6f6e2d7a65726f207365636f6e646172792066656040820152606560f81b606082015260800190565b808201808211156125db576125db613d69565b6020808252825182820152828101516001600160a01b039081166040808501919091528401518051821660608501528083015160808501526000929161010085019190606087015160a0870152608087015160e060c0880152805193849052840192600092506101208701905b80841015613eb757845183168252938501936001939093019290850190613e95565b5060a0880151878203601f190160e08901529450613ed581866136d6565b98975050505050505050565b8681526001600160a01b0386811660208301528581166040830152841660608201526080810183905260c060a08201819052600090613ed5908301846136d6565b634e487b7160e01b600052601260045260246000fd5b600082613f4757613f47613f22565b500690565b600060208284031215613f5e57600080fd5b815161187d8161313b565b60ff82811682821603908111156125db576125db613d69565b600181815b80851115613fbd578160001904821115613fa357613fa3613d69565b80851615613fb057918102915b93841c9390800290613f87565b509250929050565b600082613fd4575060016125db565b81613fe1575060006125db565b8160018114613ff757600281146140015761401d565b60019150506125db565b60ff84111561401257614012613d69565b50506001821b6125db565b5060208310610133831016604e8410600b8410161715614040575081810a6125db565b61404a8383613f82565b806000190482111561405e5761405e613d69565b029392505050565b600061187d8383613fc5565b80820281158282048414176125db576125db613d69565b60008261409857614098613f22565b500490565b6000602082840312156140af57600080fd5b8151801515811461187d57600080fd5b600082516140d18184602087016136b2565b9190910192915050565b60208152600061187d60208301846136d656fea264697066735822122027ee2c8edf5a85411d98a486f46b1b0d4271954ef0a1055ad3cd05958c4db41f64736f6c63430008120033

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

000000000000000000000000f86cb19ad8405aefa7d09c778215d2cb6ebfb228000000000000000000000000efe9e34988a5054907fc125aef5e85aaaf7f928c0000000000000000000000003f964a0630b429ffb1797f2ce9fd8e5a45c5ad180000000000000000000000000000000000000000000000000000000000000012

-----Decoded View---------------
Arg [0] : teleporterRegistryAddress (address): 0xF86Cb19Ad8405AEFa7d09C778215D2Cb6eBfB228
Arg [1] : teleporterManager (address): 0xEfe9e34988A5054907fc125AEf5e85Aaaf7f928C
Arg [2] : tokenAddress_ (address): 0x3F964A0630b429FFB1797f2CE9fd8e5a45C5AD18
Arg [3] : tokenDecimals_ (uint8): 18

-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 000000000000000000000000f86cb19ad8405aefa7d09c778215d2cb6ebfb228
Arg [1] : 000000000000000000000000efe9e34988a5054907fc125aef5e85aaaf7f928c
Arg [2] : 0000000000000000000000003f964a0630b429ffb1797f2ce9fd8e5a45c5ad18
Arg [3] : 0000000000000000000000000000000000000000000000000000000000000012


Block Transaction 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
0x43B2A45ac6D463902826e65Aaa31Ae0Ef846C6Ec
Loading...
Loading
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.