ETH Price: $2,238.95 (-8.61%)

Contract

0x4d32F25CbDb1829A0110Cf1d1e374F578dF8ABE3

Overview

ETH Balance

0 ETH

ETH Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To
Liquidate Asset4275744932026-02-01 17:57:355 hrs ago1769968655IN
0x4d32F25C...78dF8ABE3
0 ETH0.000003210.020026
Publish Statemen...4275743352026-02-01 17:56:565 hrs ago1769968616IN
0x4d32F25C...78dF8ABE3
0 ETH0.000004860.02
Liquidate Asset4275738362026-02-01 17:54:515 hrs ago1769968491IN
0x4d32F25C...78dF8ABE3
0 ETH0.00000320.02
Publish Statemen...4275736702026-02-01 17:54:095 hrs ago1769968449IN
0x4d32F25C...78dF8ABE3
0 ETH0.000004910.02019
Liquidate Asset4275733642026-02-01 17:52:535 hrs ago1769968373IN
0x4d32F25C...78dF8ABE3
0 ETH0.000003210.020054
Publish Statemen...4275731962026-02-01 17:52:135 hrs ago1769968333IN
0x4d32F25C...78dF8ABE3
0 ETH0.000004890.020114
Liquidate Asset4275707122026-02-01 17:41:525 hrs ago1769967712IN
0x4d32F25C...78dF8ABE3
0 ETH0.000003210.020072
Publish Statemen...4275705542026-02-01 17:41:125 hrs ago1769967672IN
0x4d32F25C...78dF8ABE3
0 ETH0.000004920.020234
Liquidate Asset4275673352026-02-01 17:27:495 hrs ago1769966869IN
0x4d32F25C...78dF8ABE3
0 ETH0.000003210.020046
Publish Statemen...4275671792026-02-01 17:27:105 hrs ago1769966830IN
0x4d32F25C...78dF8ABE3
0 ETH0.000004870.020038
Liquidate Asset4275621252026-02-01 17:06:126 hrs ago1769965572IN
0x4d32F25C...78dF8ABE3
0 ETH0.00000320.02002
Publish Statemen...4275619662026-02-01 17:05:326 hrs ago1769965532IN
0x4d32F25C...78dF8ABE3
0 ETH0.000004860.020016
Liquidate Asset4275614382026-02-01 17:03:206 hrs ago1769965400IN
0x4d32F25C...78dF8ABE3
0 ETH0.00000320.02
Publish Statemen...4275612782026-02-01 17:02:406 hrs ago1769965360IN
0x4d32F25C...78dF8ABE3
0 ETH0.000004860.02
Liquidate Asset4275570382026-02-01 16:45:046 hrs ago1769964304IN
0x4d32F25C...78dF8ABE3
0 ETH0.000003210.020032
Publish Statemen...4275568812026-02-01 16:44:256 hrs ago1769964265IN
0x4d32F25C...78dF8ABE3
0 ETH0.000004860.02
Liquidate Asset4275554682026-02-01 16:38:336 hrs ago1769963913IN
0x4d32F25C...78dF8ABE3
0 ETH0.000003210.020044
Publish Statemen...4275553122026-02-01 16:37:546 hrs ago1769963874IN
0x4d32F25C...78dF8ABE3
0 ETH0.000004870.020054
Liquidate Asset4275538792026-02-01 16:31:586 hrs ago1769963518IN
0x4d32F25C...78dF8ABE3
0 ETH0.00000320.02
Publish Statemen...4275537222026-02-01 16:31:186 hrs ago1769963478IN
0x4d32F25C...78dF8ABE3
0 ETH0.000004910.020208
Liquidate Asset4275477882026-02-01 16:06:467 hrs ago1769962006IN
0x4d32F25C...78dF8ABE3
0 ETH0.000003210.020068
Publish Statemen...4275476302026-02-01 16:06:077 hrs ago1769961967IN
0x4d32F25C...78dF8ABE3
0 ETH0.000004890.02011
Liquidate Asset4275448422026-02-01 15:54:327 hrs ago1769961272IN
0x4d32F25C...78dF8ABE3
0 ETH0.000003220.020092
Publish Statemen...4275446862026-02-01 15:53:537 hrs ago1769961233IN
0x4d32F25C...78dF8ABE3
0 ETH0.000004870.020038
Liquidate Asset4275411932026-02-01 15:39:267 hrs ago1769960366IN
0x4d32F25C...78dF8ABE3
0 ETH0.000003210.020034
View all transactions

Latest 1 internal transaction

Parent Transaction Hash Block From To
3821969362025-09-23 14:16:52131 days ago1758637012  Contract Creation0 ETH

Cross-Chain Transactions
Loading...
Loading

Similar Match Source Code
This contract matches the deployed Bytecode of the Source Code for Contract 0xC43D0Cc9...e6fb44358
The constructor portion of the code might be different and could alter the actual behaviour of the contract

Contract Name:
Coordinator

Compiler Version
v0.8.24+commit.e11b9ed9

Optimization Enabled:
Yes with 200 runs

Other Settings:
paris EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

// SPDX-FileCopyrightText: Copyright (C) 2024 Signify Holdings, Inc.
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.24;

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
import {ICollateral} from "./interfaces/ICollateral.sol";
import {IERC20Decimals} from "./interfaces/IERC20Decimals.sol";

/**
 * @title Coordinator Contract
 * @notice Smart contract for managing funds in collateral contracts: due balance tracking, payments, and liquidation
 *
 * @dev Core functionalities:
 * 1. Statement Management
 *    - Publishers create expense statements for teams
 *    - Each statement tracks total owed amount and payments
 *    - Statements have liquidation timelock
 *
 * 2. Payment Processing
 *    - Users can pay from their account or collateral contract
 *    - Supports both native chain assets (ETH/AVAX) and ERC20 assets
 *    - Two payment routes:
 *      a) Direct payment from user account
 *      b) Payment from collateral contract
 *    - Automatic fee calculation and collection
 *    - Price conversion using Chainlink oracles
 *    - Payment can be made for a statement or not
 *
 * 3. Liquidation Processing
 *    - Liquidation can be made for unpaid statements amount only
 *    - Liquidation can be made by authorized executors only
 *    - Multi-asset support for collateral
 *
 * 4. Fee Collection
 *    - Fee is collected from payment amount
 *    - Fee is paid to treasury
 *    - feeBps is in basis points (1/100th of 1%)
 *    - Example: 30 basis points = 0.3%
 *
 * 5. Collateral Management
 *    - Signature-based withdrawals (EIP-712)
 *    - Liquidation of unpaid statements
 *    - Multi-asset support for collateral
 *
 * 6. Role-Based Access
 *    - Publishers: Create/update statements
 *    - Executors: Process liquidations
 *    - Admins: Manage collateral contracts
 *    - Owner: System configuration
 *
 * 6. Asset Handling
 *    - Native assets (ETH/AVAX)
 *    - ERC20 assets
 *    - Stablecoins (1:1 USD peg)
 *    - Price-volatile assets (via Chainlink)
 *
 * 7. Security Features
 *    - Reentrancy protection
 *    - EIP-712 signatures
 *    - Nonce-based replay protection
 *    - Price staleness checks
 *    - Safe math operations
 *
 * @dev Technical Details:
 * - Version: 2 (EIP712_DOMAIN_VERSION)
 * - Required OpenZeppelin: Ownable, ReentrancyGuard, ECDSA, SafeERC20
 * - External Dependencies: Chainlink Price Feeds
 * - All amounts stored in cents (USD)
 * - Withdraw and payment are processed in asset's native amount
 * - Fees configured in basis points (1/100th of 1%)
 *
 * @dev Key State Variables:
 * - treasury: Recipient of all payments and fees
 * - statements: Maps statement IDs to their details
 * - supportedAssets: Configuration for supported payment assets
 * - publishers[]: Authorized statement creators
 * - executors[]: Authorized liquidation processors
 * - nonce: Prevents signature replay attacks
 *
 * @dev Main Workflows:
 * 1. Statement Creation:
 *    Publisher -> createStatement -> Statement stored
 *
 * 2. Payment Process:
 *    User wallet -> makePayment -> Treasury
 *    OR
 *    Collateral -> signedPayment -> Treasury
 *
 * 3. Liquidation Process:
 *    Executor -> liquidateAsset -> Treasury
 *    (Only after liquidatableAfter timestamp)
 *
 * @notice Important Constraints:
 * - Payments cannot exceed remaining statement balance
 * - Excessive payments are returned to the user/collateral
 * - Assets must be pre-configured before use
 * - Price feeds must be fresh for non-stablecoin assets
 * - Ownership cannot be renounced
 */

contract Coordinator is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20Decimals;

    /// @dev Elliptic Curve Digital Signature Algorithm to validate signature
    using ECDSA for bytes32;

    /// @dev closingAccountBalanceCents - paidAmountCents = amount represents the outstanding balance that can be liquidated by the executor
    /// @dev Statement represents a financial record for a team with the following properties:
    /// All amounts are stored in USD cents (1/100th of a dollar)
    struct Statement {
        // Unique identifier for this statement, used as key in statements mapping
        string id;
        // Identifier for the team this statement belongs to, used for tracking and events
        string teamId;
        // Total amount owed by the team in USD cents, must be greater than 0
        uint256 closingAccountBalanceCents;
        // Running total of all payments made against this statement in USD cents
        // Must always be <= closingAccountBalanceCents
        uint256 paidAmountCents;
        // Smart contract holding the team's collateral, must be non-zero address
        // Used for liquidation and payment processing
        address collateralProxy;
        // Unix timestamp after which unpaid amounts can be liquidated
        // Liquidation can only occur if block.timestamp >= liquidatableAfter
        uint256 liquidatableAfter;
    }

    /// @dev Supported asset struct, supporting both ERC-20 and native assets with or without Chainlink oracle
    struct SupportedAsset {
        // Asset ERC-20 contract address. Zero address for native asset such as ETH and AVAX
        // Must be non-zero for ERC20 tokens
        address asset;
        // Chainlink oracle address to get asset to USD exchange rate
        // If not set (address(0)), asset is treated as a stablecoin with 1:1 USD peg
        // Must be set for native assets and volatile tokens
        address oracle;
        // Threshold in seconds to consider the price feed as stale
        // If 0, staleness check is disabled
        // If >0, price updates older than threshold will revert
        uint256 staleThreshold;
        // Minimum price to consider the price feed as valid
        // Price lower than min price will revert
        int256 minPrice;
        // Maximum price to consider the price feed as valid
        // Price higher than max price will revert
        int256 maxPrice;
        // Fee in basis points (1/100th of 1%)
        // Example: 30 = 0.3%, 100 = 1%
        // Must be <= 10000 (100%)
        uint16 feeBps;
    }

    /// @dev User readable name of signing domain
    string public constant EIP712_DOMAIN_NAME = "Coordinator";

    /// @dev Current major version of signing domain
    string public constant EIP712_DOMAIN_VERSION = "2";

    /// @dev Type hash to check EIP712 domain separator validity in signature
    bytes32 public constant EIP712_DOMAIN_TYPE_HASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)");

    /// @dev Type hash to check pay signature validity
    bytes32 public constant PAY_TYPE_HASH =
        keccak256(
            "Pay(address user,address collateral,address asset,uint256 amount,uint256 nonce,uint256 expiresAt,string teamId,string statementId)"
        );

    /// @dev Type hash to check withdraw signature validity
    bytes32 public constant WITHDRAW_TYPE_HASH =
        keccak256(
            "Withdraw(address user,address collateral,address asset,uint256 amount,address recipient,uint256 nonce,uint256 expiresAt)"
        );

    /// @dev Treasury contract address for the service provider.
    ///      Payment and liqudation moves assets to this address.
    address public treasury;

    /// @dev A counter to prevent duplicate transaction with same signature.
    ///      Using single nonce for all type of transactions to ensure their order.
    ///      key: address of Collateral contract
    ///      Value: increasing counter
    mapping(address => uint256) public nonce;

    /// @dev Addresses that can publish statements
    address[] public publishers;

    /// @dev Addresses that can liquidate
    address[] public executors;

    /// @dev Statement mapping
    ///      Key: statement's uuid
    ///      Value: a statement data of a team
    mapping(string => Statement) public statements;

    /// @dev Keeping track of supported assets
    mapping(address => SupportedAsset) public supportedAssets;

    event Withdrawal(address indexed collateralProxy, address asset, address recipient, uint256 amountNative);

    event PaymentFromCollateral(
        address user,
        address indexed collateralProxy,
        address asset,
        uint256 amountsNative,
        uint256 amountsCents,
        uint256 feeNative,
        uint8 assetDecimals,
        uint256 nonce,
        uint256 expiresAt,
        string indexed indexedTeamId,
        string teamId
    );

    event PaymentFromCollateralForStatement(
        address user,
        address indexed collateralProxy,
        address asset,
        uint256 amountsNative,
        uint256 amountsCents,
        uint256 feeNative,
        uint8 assetDecimals,
        uint256 nonce,
        uint256 expiresAt,
        string indexed indexedTeamId,
        string teamId,
        string indexed indexedStatementId,
        string statementId
    );

    event PaymentFromUserAccount(
        address user,
        address asset,
        uint256 amountNative,
        uint256 amountCents,
        uint256 feeNative,
        uint8 assetDecimals,
        string indexed indexedTeamId,
        string teamId
    );

    event PaymentFromUserAccountForStatement(
        address user,
        address asset,
        uint256 amountNative,
        uint256 amountCents,
        uint256 feeNative,
        uint8 assetDecimals,
        string indexed indexedTeamId,
        string teamId,
        string indexed indexedStatementId,
        string statementId
    );

    event Liquidation(
        address indexed collateralProxy,
        address[] assets,
        uint256[] amountsNative,
        uint256[] amountsCents,
        uint8[] assetDecimals,
        uint256 netAmountCents,
        uint256[] feeNative,
        string indexed indexedTeamId,
        string teamId,
        string indexed indexedStatementId,
        string statementId
    );

    event PublisherAdded(address indexed publisher);
    event PublisherRemoved(address indexed publisher);

    event ExecutorAdded(address indexed executor);
    event ExecutorRemoved(address indexed executor);

    event NewTreasury(address indexed newTreasury, address oldTreasury);

    event StatementPublished(
        string indexed indexedStatementId,
        string statementId,
        string indexed indexedTeamId,
        string teamId,
        uint256 closingAccountBalanceCents,
        address collateralProxy,
        uint256 liquidatableAfter
    );

    event StatementUpdated(
        string indexed indexedStatementId,
        string statementId,
        string indexed indexedTeamId,
        string teamId,
        uint256 closingAccountBalanceCents,
        address collateralProxy,
        uint256 liquidatableAfter
    );

    event SupportedAssetAdded(
        address indexed asset,
        address oracle,
        uint256 staleThreshold,
        int256 minPrice,
        int256 maxPrice,
        uint16 feeBps
    );

    event SupportedAssetRemoved(
        address indexed asset,
        address oracle,
        uint256 staleThreshold,
        int256 minPrice,
        int256 maxPrice,
        uint16 feeBps
    );

    event NonceIncreased(address indexed collateralProxy, uint256 newNonce);

    event StatementMarkedPaid(string indexed indexedStatementId, string statementId, uint256 amountCents);

    event ExtraNativeAssetReturned(address indexed recipient, uint256 amountsNative, uint8 assetDecimals);

    error Unauthorized(address unauthorizedAddress);
    error UnsupportedAsset(address asset);
    error InconsistentInputLength();
    error InvalidTeamId();
    error DisabledOwnershipRenouncement();
    error BalanceMustBeGreaterOrEqualToZero();
    error BalanceAlreadyPaid();
    error NoAssets();
    error StatementNotFound();
    error CannotOverwriteStatement();
    error AmountTooBig();
    error AmountTooSmall();
    error InvalidSignature();
    error ZeroAddress();
    error SupportedAssetAlreadyExists();
    error TooEarlyToLiquidate();
    error InvalidPrice(int256 price);
    error StalePrice();
    error CannotSendNativeAsset();
    error InvalidFeeBps();
    error PublisherAlreadyExists(address publisher);
    error ExecutorAlreadyExists(address executor);
    error LastPublisher(address publisher);
    error LastExecutor(address executor);
    error PublisherNotFound(address publisher);
    error ExecutorNotFound(address executor);
    error TeamIdMismatch();

    /// @dev Revert if caller is not the publisher
    modifier onlyPublisher() {
        (bool found, ) = _getAddressIndex(publishers, msg.sender);
        if (!found) {
            revert Unauthorized(msg.sender);
        }
        _;
    }

    /// @dev Revert if caller is not the executor
    modifier onlyExecutor() {
        (bool found, ) = _getAddressIndex(executors, msg.sender);
        if (!found) {
            revert Unauthorized(msg.sender);
        }
        _;
    }

    /// @dev Revert if the signature is expired
    modifier activeSignature(uint256 _expiresAt) {
        if (block.timestamp > _expiresAt) {
            revert InvalidSignature();
        }
        _;
    }

    /// @dev Revert if asset is not supported
    modifier onlySupportedAsset(address _asset) {
        if (!isSupportedAsset(_asset)) {
            revert UnsupportedAsset(_asset);
        }
        _;
    }

    /**
     * @notice Initialize Coordinator contract
     * @param _owner Owner address
     * @param _publisher Publisher address
     * @param _executor Executor address
     * @param _treasury Treasury address
     */
    constructor(address _owner, address _publisher, address _executor, address _treasury) {
        transferOwnership(_owner);

        // Check if publisher is zero address
        if (_publisher == address(0)) {
            revert ZeroAddress();
        }
        publishers.push(_publisher);

        // Check if executor is zero address
        if (_executor == address(0)) {
            revert ZeroAddress();
        }
        executors.push(_executor);

        // Check if treasury is zero address
        if (_treasury == address(0)) {
            revert ZeroAddress();
        }
        treasury = _treasury;
    }

    /**
     * @notice Add a publisher
     * @dev only owner can call this function
     * @param _publisher publisher address to add
     */
    function addPublisher(address _publisher) external onlyOwner {
        if (_publisher == address(0)) {
            revert ZeroAddress();
        }

        // Check if publisher already exists
        (bool found, ) = _getAddressIndex(publishers, _publisher);
        if (found) {
            revert PublisherAlreadyExists(_publisher);
        }

        publishers.push(_publisher);

        emit PublisherAdded(_publisher);
    }

    /**
     * @notice Remove a publisher
     * @dev only owner can call this function
     * @param _publisher publisher address to remove
     */
    function removePublisher(address _publisher) external onlyOwner {
        // Minimum one publisher is required
        if (publishers.length == 1) {
            revert LastPublisher(_publisher);
        }

        // Check if publisher exists
        (bool found, uint256 index) = _getAddressIndex(publishers, _publisher);

        if (!found) {
            revert PublisherNotFound(_publisher);
        }

        // Remove publisher by moving last element to removed index then pop the last element
        publishers[index] = publishers[publishers.length - 1];
        publishers.pop();

        emit PublisherRemoved(_publisher);
    }

    /**
     * @notice Add an executor
     * @dev only owner can call this function
     * @param _executor executor address to add
     */
    function addExecutor(address _executor) external onlyOwner {
        // Check if executor is zero address
        if (_executor == address(0)) {
            revert ZeroAddress();
        }

        // Check if executor already exists
        (bool found, ) = _getAddressIndex(executors, _executor);
        if (found) {
            revert ExecutorAlreadyExists(_executor);
        }

        executors.push(_executor);

        emit ExecutorAdded(_executor);
    }

    /**
     * @notice Remove an executor
     * @dev only owner can call this function
     * @param _executor executor address to remove
     */
    function removeExecutor(address _executor) external onlyOwner {
        // Minimum one executor is required
        if (executors.length == 1) {
            revert LastExecutor(_executor);
        }

        // Check if executor exists
        (bool found, uint256 index) = _getAddressIndex(executors, _executor);
        if (!found) {
            revert ExecutorNotFound(_executor);
        }

        // Remove executor by moving last element to removed index then pop the last element
        executors[index] = executors[executors.length - 1];
        executors.pop();

        emit ExecutorRemoved(_executor);
    }

    /**
     * @notice Get an index of an address in an array
     * @param _array array to search
     * @param _address address to find
     */
    function _getAddressIndex(address[] memory _array, address _address) private pure returns (bool, uint256) {
        // It is expected that the number of admins would be around 1~5
        for (uint256 i = 0; i < _array.length; i++) {
            if (_array[i] == _address) {
                return (true, i);
            }
        }
        // Index value 0 is meaningless here as not found
        return (false, 0);
    }

    /**
     * @notice Update treasury contract address
     * @dev only owner can call this function
     * @param _treasury new treasury contract address
     * Requirements:
     * - `_newAddress` should not be NullAddress.
     */
    function updateTreasury(address _treasury) external onlyOwner {
        // Check if treasury is zero address
        if (_treasury == address(0)) {
            revert ZeroAddress();
        }
        address oldTreasury = treasury;
        treasury = _treasury;

        emit NewTreasury(_treasury, oldTreasury);
    }

    /**
     * @notice Add a supported asset
     * @dev only owner can call this function
     * Requirements:
     * - ERC20 tokens must have their contract address
     * - Native chain asset (address(0)) must have price oracle
     * - Stablecoins can be configured without price oracle
     * - Price-volatile assets must have valid Chainlink oracle
     * @param _asset asset's contract address, zero address for native asset such as ETH and AVAX
     * @param _oracle Chainlink price oracle address to get asset to USD exchange rate
     *                If not set, the asset is considered to be stable.
     * @param _staleThreshold threshold in seconds to consider the price feed as stale
     * @param _feeBps fee in basis points
     */
    function addSupportedAsset(
        address _asset,
        address _oracle,
        uint256 _staleThreshold,
        int256 _minPrice,
        int256 _maxPrice,
        uint16 _feeBps
    ) external onlyOwner {
        // Check if asset already exists
        if (isSupportedAsset(_asset)) {
            revert SupportedAssetAlreadyExists();
        }

        // Native asset should be set with price oracle
        if (_asset == address(0) && _oracle == address(0)) {
            revert ZeroAddress();
        }

        // Check if feeBps is valid
        if (_feeBps > 10000) {
            revert InvalidFeeBps();
        }

        supportedAssets[_asset] = SupportedAsset(_asset, _oracle, _staleThreshold, _minPrice, _maxPrice, _feeBps);

        emit SupportedAssetAdded(_asset, _oracle, _staleThreshold, _minPrice, _maxPrice, _feeBps);
    }

    /**
     * @notice Remove a supported asset
     * @dev only owner can call this function
     * @param _asset asset's contract address
     */
    function removeSupportedAsset(address _asset) external onlyOwner {
        // Check if asset is supported
        if (!isSupportedAsset(_asset)) {
            revert UnsupportedAsset(_asset);
        }

        SupportedAsset memory asset = supportedAssets[_asset];
        delete supportedAssets[_asset];

        emit SupportedAssetRemoved(
            asset.asset,
            asset.oracle,
            asset.staleThreshold,
            asset.minPrice,
            asset.maxPrice,
            asset.feeBps
        );
    }

    /**
     * @notice Check if an asset is supported
     * @param _asset asset's contract address, zero address for native asset
     * @return true if asset is supported
     */
    function isSupportedAsset(address _asset) public view returns (bool) {
        SupportedAsset memory asset = supportedAssets[_asset];
        // Asset is supported if it is not zero address
        // or zero address with a price oracle(native asset)
        return asset.asset != address(0) || asset.oracle != address(0);
    }

    /**
     * @notice Convert amounts in cents to native
     * @param _asset asset's contract address
     * @param _amountCents asset amount in cents
     * @return amountNative converted amount in native asset
     * @return assetDecimals asset's smallest unit
     */
    function getAssetAmountNative(
        address _asset,
        uint256 _amountCents
    ) public view returns (uint256 amountNative, uint8 assetDecimals) {
        // Check if asset is supported
        if (!isSupportedAsset(_asset)) {
            revert UnsupportedAsset(_asset);
        }

        SupportedAsset memory asset = supportedAssets[_asset];
        assetDecimals = _asset == address(0)
            ? 18 // All EVM chains should implement 18 decimals in its native asset
            : IERC20Decimals(_asset).decimals();

        if (asset.oracle == address(0)) {
            // asset is considered to be stable to USD
            amountNative = (_amountCents * (10 ** (assetDecimals - 2)));
        } else {
            (int256 price, uint8 priceDecimals) = _getCurrentAssetPrice(asset);
            amountNative = (_amountCents * (10 ** (uint256(priceDecimals) - 2 + assetDecimals))) / uint256(price);
        }
    }

    /**
     * @notice Convert amounts in native to cents
     * @param _asset asset's contract address
     * @param _amountNative asset amount in native decimals
     * @return amountCents converted amount in cents
     */
    function getAssetAmountCents(
        address _asset,
        uint256 _amountNative
    ) public view returns (uint256 amountCents, uint8 assetDecimals) {
        // Check if asset is supported
        if (!isSupportedAsset(_asset)) {
            revert UnsupportedAsset(_asset);
        }

        SupportedAsset memory asset = supportedAssets[_asset];
        assetDecimals = _asset == address(0)
            ? 18 // All EVM chains should implement 18 decimals in its native asset
            : IERC20Decimals(_asset).decimals();

        if (asset.oracle == address(0)) {
            // asset is considered to be stable to USD
            uint256 divisor = 10 ** (assetDecimals - 2);
            // Floor cents amount
            amountCents = _amountNative / divisor;
        } else {
            (int256 price, uint8 priceDecimals) = _getCurrentAssetPrice(asset);
            uint256 divisor = 10 ** (uint256(priceDecimals) - 2 + assetDecimals);
            // Floor cents amount
            amountCents = (_amountNative * uint256(price)) / divisor;
        }
    }

    /**
     * @notice Get current price of an asset
     * @dev Price validation:
     * 1. Price must be > 0 (reverts on zero/negative)
     * 2. If staleThreshold > 0, price timestamp must be recent
     * 3. Decimals are retrieved from oracle for proper scaling
     * @param _asset asset information
     * @return price current price of the asset
     * @return decimals price's decimals
     */
    function _getCurrentAssetPrice(SupportedAsset memory _asset) private view returns (int256 price, uint8 decimals) {
        // Get price from Chainlink oracle
        AggregatorV3Interface priceOracle = AggregatorV3Interface(_asset.oracle);

        // Get price and updated at from Chainlink oracle
        uint256 updatedAt;
        (, price, , updatedAt, ) = priceOracle.latestRoundData();
        // 0 or negative price is invalid
        if (price <= 0) {
            revert InvalidPrice(price);
        }
        // Check if price is within the allowed range
        if (price < _asset.minPrice) {
            revert InvalidPrice(price);
        }
        if (price > _asset.maxPrice) {
            revert InvalidPrice(price);
        }

        // Get price decimals from Chainlink oracle
        decimals = priceOracle.decimals();

        // Check if price from oracle is stale
        if (_asset.staleThreshold != 0 && updatedAt + _asset.staleThreshold < block.timestamp) {
            revert StalePrice();
        }
    }

    /**
     * @notice Increase nonce of a collateral proxy by owner
     *         It can be used to invalidate a signature issued by executor or publisher
     * @dev only executor can call this function
     * @param _collateralProxy collateral proxy address
     */
    function increaseNonce(address _collateralProxy) external onlyExecutor {
        nonce[_collateralProxy]++;

        emit NonceIncreased(_collateralProxy, nonce[_collateralProxy]);
    }

    /**
     * @notice Publish closing account balance of multiple teams
     * @param _statementIds array of statement's uuid
     * @param _teamIds array of team's uuid in the same order as _statementIds
     * @param _closingAccountBalanceCents array of closing account balance in cents in the same order as _statementIds
     * @param _collateralProxies array of Collateral contract addresses in the same order as _statementIds
     */
    function publishStatements(
        string[] calldata _statementIds,
        string[] calldata _teamIds,
        uint256[] calldata _closingAccountBalanceCents,
        address[] calldata _collateralProxies,
        uint256[] calldata _liquidatableAfters
    ) public onlyPublisher {
        // Check if input lengths are consistent
        if (
            _teamIds.length != _statementIds.length ||
            _teamIds.length != _closingAccountBalanceCents.length ||
            _teamIds.length != _collateralProxies.length ||
            _teamIds.length != _liquidatableAfters.length
        ) {
            revert InconsistentInputLength();
        }

        for (uint256 i = 0; i < _teamIds.length; i++) {
            // Check if teamId is valid
            if (bytes(_teamIds[i]).length == 0) {
                revert InvalidTeamId();
            }

            // Closing account balance must be greater or equal to zero
            if (_closingAccountBalanceCents[i] <= 0) {
                revert BalanceMustBeGreaterOrEqualToZero();
            }

            // Collateral proxy address must be valid
            if (_collateralProxies[i] == address(0)) {
                revert ZeroAddress();
            }

            Statement storage statement = statements[_statementIds[i]];

            // Statement should not be published yet
            if (statement.closingAccountBalanceCents > 0) {
                revert CannotOverwriteStatement();
            }

            // Create a new statement
            statements[_statementIds[i]] = Statement(
                _statementIds[i],
                _teamIds[i],
                _closingAccountBalanceCents[i],
                0,
                _collateralProxies[i],
                _liquidatableAfters[i]
            );

            emit StatementPublished(
                _statementIds[i],
                _statementIds[i],
                _teamIds[i],
                _teamIds[i],
                _closingAccountBalanceCents[i],
                _collateralProxies[i],
                _liquidatableAfters[i]
            );
        }
    }

    /**
     * @notice Update statement after publishing
     * @param _statementId statement's uuid
     * @param _teamId team's uuid
     * @param _closingAccountBalanceCent closing account balance in cents
     * @param _collateralProxy Collateral contract address
     */
    function updateStatement(
        string calldata _statementId,
        string calldata _teamId,
        uint256 _closingAccountBalanceCent,
        address _collateralProxy,
        uint256 _liquidatableAfter
    ) public onlyPublisher {
        Statement storage statement = statements[_statementId];

        // Statement should be published
        if (bytes(statement.teamId).length == 0) {
            revert StatementNotFound();
        }

        // Closing account balance must be greater than or equal to paid amount
        if (_closingAccountBalanceCent < statement.paidAmountCents) {
            revert BalanceAlreadyPaid();
        }

        // Collateral proxy address must be valid
        if (_collateralProxy == address(0)) {
            revert ZeroAddress();
        }

        // Update statement
        statement.teamId = _teamId;
        statement.closingAccountBalanceCents = _closingAccountBalanceCent;
        statement.collateralProxy = _collateralProxy;
        statement.liquidatableAfter = _liquidatableAfter;

        emit StatementUpdated(
            _statementId,
            _statementId,
            _teamId,
            _teamId,
            _closingAccountBalanceCent,
            _collateralProxy,
            _liquidatableAfter
        );
    }

    /**
     * @notice Mark a statement as paid in case it was paid outside the chain
     * @dev Validation checks:
     * 1. Verify statement exists
     * 2. Check that new payment won't exceed remaining balance
     * 3. Safely add payment to running total
     * All amounts are in cents to maintain consistency
     * @param _statementId statement's uuid
     * @param _amountCents amount in cents
     */
    function markStatementPaid(string calldata _statementId, uint256 _amountCents) public onlyExecutor {
        _markStatementPaid(statements[_statementId], _amountCents);
    }

    /**
     * @notice Liquidate assets of multiple teams
     * @dev Liquidation flow:
     * 1. Verify statement exists and liquidation timelock has passed
     * 2. Calculate outstanding balance (closing - paid amounts)
     * 3. For each asset:
     *    - Convert desired cents to native asset amount
     *    - Transfer from collateral to treasury
     *    - Track running total of liquidated value
     * 4. Stop when full amount is covered or assets exhausted
     * 5. Update statement's paid amount
     * @param _statementIds array of statement's uuid in the same order as _teamIds
     * @param _assets array of assets' contract addresses in the same order as _teamIds
     */
    function liquidateAsset(string[] calldata _statementIds, address[][] calldata _assets) public onlyExecutor {
        // Check if input lengths are consistent
        if (_statementIds.length != _assets.length) {
            revert InconsistentInputLength();
        }

        for (uint256 i = 0; i < _statementIds.length; i++) {
            // Check if assets array is not empty
            if (_assets[i].length == 0) {
                revert NoAssets();
            }

            Statement storage statement = statements[_statementIds[i]];

            // Statement should be published
            if (statement.closingAccountBalanceCents == 0) {
                revert StatementNotFound();
            }

            // Statement should be liquidatable after the specified time
            if (block.timestamp < statement.liquidatableAfter) {
                revert TooEarlyToLiquidate();
            }

            // Calculate pending amount and net amount
            uint256 pendingAmountCents = statement.closingAccountBalanceCents - statement.paidAmountCents;
            uint256 netAmountCents = 0; // Sum of cents amount without fee
            uint256[] memory amountsNativeFinal = new uint256[](_assets[i].length);
            uint256[] memory amountsCentsFinal = new uint256[](_assets[i].length);
            uint8[] memory assetDecimals = new uint8[](_assets[i].length);
            uint256[] memory feeNative = new uint256[](_assets[i].length);

            for (uint256 j = 0; j < _assets[i].length; j++) {
                // Stop if pending amount is already liquidated
                if (netAmountCents >= pendingAmountCents) {
                    break;
                }

                // Check if asset is supported
                if (!isSupportedAsset(_assets[i][j])) {
                    revert UnsupportedAsset(_assets[i][j]);
                }

                // Transfer asset from collateral to treasury
                (
                    amountsNativeFinal[j],
                    amountsCentsFinal[j],
                    feeNative[j],
                    assetDecimals[j]
                ) = _transferCollateralCents(
                    statement.collateralProxy,
                    _assets[i][j],
                    pendingAmountCents - netAmountCents,
                    treasury,
                    supportedAssets[_assets[i][j]].feeBps,
                    true
                );

                netAmountCents += amountsCentsFinal[j];
            }

            // Update paid amount
            statement.paidAmountCents += netAmountCents;

            string memory teamId = statements[_statementIds[i]].teamId;

            emit Liquidation(
                statement.collateralProxy,
                _assets[i],
                amountsNativeFinal,
                amountsCentsFinal,
                assetDecimals,
                netAmountCents,
                feeNative,
                teamId,
                teamId,
                _statementIds[i],
                _statementIds[i]
            );
        }
    }

    /**
     * @notice Withdraw assets from a Collateral contract with multi-signature verification
     * @dev Requires signatures from both an executor/publisher AND collateral admins
     * @dev Withdrawal flow:
     * 1. Verify signature from executor/publisher
     * 2. Verify signatures from collateral admins
     * 3. Transfer assets from collateral to recipient
     * @param _collateralProxy The Collateral contract to withdraw from
     * @param _asset Asset address (or zero address for native asset)
     * @param _amountNative Amount to withdraw in asset's native decimals
     * @param _recipient Address to receive the withdrawn assets
     * @param _expiresAt Timestamp after which the signatures become invalid
     * @param _executorPublisherSalt Salt for executor/publisher signature domain separator
     * @param _executorPublisherSignature Signature from either an executor or publisher
     * @param _adminSalts Array of salts for admin signatures
     * @param _adminSignatures Array of signatures from collateral admins
     * @param _directTransfer If true, transfers directly to recipient. If false, routes through coordinator
     */
    function withdrawAsset(
        address _collateralProxy,
        address _asset,
        uint256 _amountNative,
        address _recipient,
        uint256 _expiresAt,
        bytes32 _executorPublisherSalt,
        bytes calldata _executorPublisherSignature,
        bytes32[] calldata _adminSalts,
        bytes[] calldata _adminSignatures,
        bool _directTransfer
    ) external activeSignature(_expiresAt) {
        // Create message hash for executor/publisher signature verification
        // Includes user address, collateral details, asset info, and withdrawal params
        bytes32 messageHash = keccak256(
            bytes.concat(
                abi.encode(WITHDRAW_TYPE_HASH, msg.sender, _collateralProxy, _asset, _amountNative),
                abi.encode(_recipient, nonce[_collateralProxy], _expiresAt)
            )
        );

        // Verify signature from executor or publisher
        _verifySignature(_collateralProxy, messageHash, _executorPublisherSalt, _executorPublisherSignature);

        // Verify signatures from collateral admins
        ICollateral collateral = ICollateral(_collateralProxy);
        collateral.verifyWithdrawAdminSignatures(
            msg.sender,
            _asset,
            _amountNative,
            _recipient,
            _adminSalts,
            _adminSignatures
        );

        // Transfer assets from collateral to recipient
        // No fee is charged for withdrawals
        _transferCollateralCommon(_collateralProxy, _asset, _recipient, _amountNative, 0, _directTransfer);

        // Emit withdrawal event with final details
        emit Withdrawal(_collateralProxy, _asset, _recipient, _amountNative);
    }

    /**
     * @notice Make payment from Collateral contract
     *         The payment is not tied to any statement
     * @dev Payment flow:
     * 1. Get current nonce for the collateral proxy
     * 2. Verify signature from executor or publisher
     * 3. Transfer assets from collateral to treasury
     * @param _collateralProxy targeting Collateral proxy address
     * @param _asset asset's contract address
     * @param _amountNative asset amount to pay in native decimals
     * @param _expiresAt expiry time of the signature
     * @param _salt disambiguating salt for signature
     * @param _signature signature generated by either executor or publisher
     * @param _teamId team's uuid
     */
    function makePaymentFromCollateral(
        address _collateralProxy,
        address _asset,
        uint256 _amountNative,
        uint256 _expiresAt,
        bytes32 _salt,
        bytes calldata _signature,
        string calldata _teamId
    ) external {
        uint256 _nonce = nonce[_collateralProxy];

        // Transfer asset from collateral to treasury
        (
            uint256 amountNativeFinal,
            uint256 amountCentsFinal,
            uint256 feeNative,
            uint8 assetDecimals
        ) = _makePaymentFromCollateral(
                _collateralProxy,
                _asset,
                _amountNative,
                _nonce,
                _expiresAt,
                _teamId,
                "",
                _salt,
                _signature
            );

        emit PaymentFromCollateral(
            msg.sender,
            _collateralProxy,
            _asset,
            amountNativeFinal,
            amountCentsFinal,
            feeNative,
            assetDecimals,
            _nonce,
            _expiresAt,
            _teamId,
            _teamId
        );
    }

    /**
     * @notice Make payment from Collateral contract and mark statement as paid
     * @dev Payment flow:
     * 1. Get current nonce for the collateral proxy
     * 2. Verify signature from executor or publisher
     * 3. Transfer assets from collateral to treasury
     * 4. Mark statement as paid
     * @param _collateralProxy targeting Collateral proxy address
     * @param _asset asset's contract address
     * @param _amountNative asset amount to pay in native decimals
     * @param _expiresAt expiry time of the signature
     * @param _salt disambiguating salt for signature
     * @param _signature signature generated by either executor or publisher
     * @param _statementId statement's uuid
     */
    function makePaymentFromCollateralForStatement(
        address _collateralProxy,
        address _asset,
        uint256 _amountNative,
        uint256 _expiresAt,
        bytes32 _salt,
        bytes calldata _signature,
        string calldata _teamId,
        string calldata _statementId
    ) external {
        uint256 _nonce = nonce[_collateralProxy];
        Statement storage statement = statements[_statementId];

        // Check if the teamId of the statement matches the teamId of the payment
        if (keccak256(bytes(statement.teamId)) != keccak256(bytes(_teamId))) {
            revert TeamIdMismatch();
        }

        // Transfer asset from collateral to treasury
        (
            uint256 amountNativeFinal,
            uint256 amountCentsFinal,
            uint256 feeNative,
            uint8 assetDecimals
        ) = _makePaymentFromCollateral(
                _collateralProxy,
                _asset,
                _amountNative,
                _nonce,
                _expiresAt,
                _teamId,
                _statementId,
                _salt,
                _signature
            );

        // Mark statement as paid
        _markStatementPaid(statement, amountCentsFinal);

        emit PaymentFromCollateralForStatement(
            msg.sender,
            _collateralProxy,
            _asset,
            amountNativeFinal,
            amountCentsFinal,
            feeNative,
            assetDecimals,
            _nonce,
            _expiresAt,
            statement.teamId,
            statement.teamId,
            _statementId,
            _statementId
        );
    }

    /**
     * @notice Sub function to verify signature and transfer asset from Collateral contract to treasury
     * @dev Payment flow:
     * 1. Verify signature is valid and from authorized signer
     * 2. Create EIP-712 compliant message hash
     * 3. Verify signature matches message and increment nonce
     * 4. Calculate payment amount and fee in native decimals
     * 5. Transfer asset from collateral to treasury:
     *    - For native assets (ETH/AVAX): Direct transfer
     *    - For ERC20: Use safe transfer after approval
     * 6. Return final amounts in native and cents
     * @param _collateralProxy targeting Collateral proxy address
     * @param _asset asset's contract address
     * @param _amountNative asset amount to pay in native decimals
     * @param _nonce nonce of the transaction
     * @param _expiresAt expiry time of the signature
     * @param _teamId team's uuid
     * @param _statementId statement's uuid. If empty, the payment is not tied to any statement
     * @param _salt disambiguating salt for signature
     * @param _signature signature generated by either executor or publisher
     * @return amountNativeFinal amount that was actually transferred in native decimals including fee
     * @return amountCentsFinal amount that was actually transferred in cents
     * @return feeNative fee paid in native decimals
     * @return assetDecimals asset's smallest unit
     */
    function _makePaymentFromCollateral(
        address _collateralProxy,
        address _asset,
        uint256 _amountNative,
        uint256 _nonce,
        uint256 _expiresAt,
        string calldata _teamId,
        string memory _statementId,
        bytes32 _salt,
        bytes calldata _signature
    )
        internal
        onlySupportedAsset(_asset)
        activeSignature(_expiresAt)
        returns (uint256 amountNativeFinal, uint256 amountCentsFinal, uint256 feeNative, uint8 assetDecimals)
    {
        // Create message hash for signature verification
        bytes32 messageHash = keccak256(
            bytes.concat(
                abi.encode(PAY_TYPE_HASH, msg.sender, _collateralProxy, _asset, _amountNative),
                abi.encode(_nonce, _expiresAt, keccak256(bytes(_teamId)), keccak256(bytes(_statementId)))
            )
        );
        _verifySignature(_collateralProxy, messageHash, _salt, _signature);

        // Transfer asset from collateral to treasury
        (amountNativeFinal, amountCentsFinal, feeNative, assetDecimals) = _transferCollateralNative(
            _collateralProxy,
            _asset,
            _amountNative,
            treasury,
            supportedAssets[_asset].feeBps,
            true
        );
    }

    /**
     * @notice Make payment from user account without marking statement as paid
     * @dev Payment flow:
     * 1. Calculate fee based on configured basis points
     * 2. For native assets:
     *    - Verify sufficient msg.value
     *    - Transfer to treasury
     * 3. For ERC20:
     *    - Transfer from user to coordinator
     *    - Transfer from coordinator to treasury
     * @param _asset asset's contract address
     * @param _amountNative asset amount to pay in native decimals
     * @param _teamId team's uuid
     */
    function makePaymentFromUserAccount(
        address _asset,
        uint256 _amountNative,
        string calldata _teamId
    ) external payable {
        // Transfer asset from user account to treasury
        (uint256 amountCents, uint256 feeNative, uint8 assetDecimals) = _makePaymentFromUserAccount(
            _asset,
            _amountNative
        );

        emit PaymentFromUserAccount(
            msg.sender,
            _asset,
            _amountNative,
            amountCents,
            feeNative,
            assetDecimals,
            _teamId,
            _teamId
        );
    }

    /**
     * @notice Make payment from user account and mark statement as paid
     * @dev Payment flow:
     * 1. Transfer asset from user account to treasury:
     *    - For native assets: verify msg.value and transfer to treasury
     *    - For ERC20: transfer from user to coordinator then to treasury
     * 2. Calculate payment amount in cents
     * 3. Mark statement as paid with the cents amount
     * @param _asset asset's contract address
     * @param _amountNative asset amount to pay in native decimals
     * @param _statementId statement's uuid
     */
    function makePaymentFromUserAccountForStatement(
        address _asset,
        uint256 _amountNative,
        string calldata _statementId
    ) external payable {
        // Transfer asset from user account to treasury
        (uint256 amountCents, uint256 feeNative, uint8 assetDecimals) = _makePaymentFromUserAccount(
            _asset,
            _amountNative
        );
        _markStatementPaid(statements[_statementId], amountCents);

        emit PaymentFromUserAccountForStatement(
            msg.sender,
            _asset,
            _amountNative,
            amountCents,
            feeNative,
            assetDecimals,
            statements[_statementId].teamId,
            statements[_statementId].teamId,
            _statementId,
            _statementId
        );
    }

    /**
     * @notice Sub function to transfer asset from user account to treasury
     * @dev Payment flow:
     * 1. Calculate fee based on configured basis points (feeBps)
     * 2. Calculate total amount including fee
     * 3. Convert payment amount to cents for statement tracking
     * 4. For native assets (ETH/AVAX):
     *    - Verify msg.value covers amount + fee
     *    - Transfer to treasury
     *    - Return any excess ETH to sender
     * 5. For ERC20 tokens:
     *    - Transfer tokens from user to coordinator (requires prior approval)
     *    - Transfer tokens from coordinator to treasury
     * @param _asset asset's contract address
     * @param _amountNative asset amount to pay in native decimals
     * @return amountCents converted amount in cents
     * @return feeNative fee paid in native decimals
     * @return assetDecimals asset's smallest unit
     */
    function _makePaymentFromUserAccount(
        address _asset,
        uint256 _amountNative
    )
        internal
        nonReentrant
        onlySupportedAsset(_asset)
        returns (uint256 amountCents, uint256 feeNative, uint8 assetDecimals)
    {
        // Calculate fee by flooring
        feeNative = (_amountNative * supportedAssets[_asset].feeBps) / 10000;
        uint256 amountWithFeeNative = _amountNative + feeNative;

        // Convert amount to cents
        (amountCents, assetDecimals) = getAssetAmountCents(_asset, _amountNative);

        // Transfer native asset from user account to treasury
        if (_asset == address(0)) {
            if (msg.value < amountWithFeeNative) {
                revert AmountTooSmall();
            }

            // Send native asset to treasury
            (bool sentToTreasury, ) = treasury.call{value: amountWithFeeNative}("");
            // Check if the transfer was successful
            if (!sentToTreasury) {
                revert CannotSendNativeAsset();
            }

            // Return excessive amount to sender
            uint256 extraAsset = msg.value - amountWithFeeNative;
            if (extraAsset > 0) {
                (bool sentBackToSender, ) = msg.sender.call{value: extraAsset}("");
                // Check if the transfer was successful
                if (!sentBackToSender) {
                    revert CannotSendNativeAsset();
                }
                emit ExtraNativeAssetReturned(msg.sender, extraAsset, assetDecimals);
            }
        } else {
            IERC20Decimals asset = IERC20Decimals(_asset);

            // Using two steps expecting user to approve coordinator
            // to transfer asset before calling this function.
            asset.safeTransferFrom(msg.sender, address(this), amountWithFeeNative);
            asset.safeTransfer(treasury, amountWithFeeNative);
        }
    }

    /**
     * @notice Sub function mark statement as paid
     * @param _statement statement to mark as paid
     * @param _amountCents asset amount to pay in cents
     */
    function _markStatementPaid(Statement storage _statement, uint256 _amountCents) internal {
        if (bytes(_statement.teamId).length == 0) {
            revert StatementNotFound();
        }

        // Check if the amount to be paid is greater than the remaining balance
        if (_amountCents > _statement.closingAccountBalanceCents - _statement.paidAmountCents) {
            revert AmountTooBig();
        }

        // Update paid amount
        _statement.paidAmountCents += _amountCents;

        emit StatementMarkedPaid(_statement.id, _statement.id, _amountCents);
    }

    /**
     * @notice Transfer native amount of assets from Collateral contract to recipient
     * @param _collateralProxy targeting Collateral proxy address
     * @param _asset asset's contract address
     * @param _amountNative asset amount to transfer in native decimals
     * @param _recipient address to receive asset
     * @param _feeBps fee in basis points
     * @param _directTransfer if false, transfer asset to coordinator first then to recipient
     * @return amountNativeFinal amount that was actually transferred in native decimals
     * @return amountCentsFinal amount that was actually transferred in cents
     * @return feeNative fee in native decimals
     * @return assetDecimals asset's smallest unit
     */
    function _transferCollateralNative(
        address _collateralProxy,
        address _asset,
        uint256 _amountNative,
        address _recipient,
        uint16 _feeBps,
        bool _directTransfer
    ) internal returns (uint256 amountNativeFinal, uint256 amountCentsFinal, uint256 feeNative, uint8 assetDecimals) {
        // Get current collateral balance
        uint256 currentCollateralBalanceNative = _asset == address(0)
            ? _collateralProxy.balance
            : IERC20Decimals(_asset).balanceOf(_collateralProxy);

        // Calculate fee by flooring
        feeNative = (_amountNative * _feeBps) / 10000;

        // Check if the amount to be transferred is greater than the collateral balance
        if (currentCollateralBalanceNative < _amountNative + feeNative) {
            // Calculate amount and fee with the remaining collateral balance
            amountNativeFinal = (currentCollateralBalanceNative * 10000) / (10000 + _feeBps);
            // Convert amount to cents
            (amountCentsFinal, assetDecimals) = getAssetAmountCents(_asset, amountNativeFinal);
            // Calculate fee with the remaining collateral balance
            feeNative = currentCollateralBalanceNative - amountNativeFinal;
        } else {
            // Transfer full amount in parameter
            amountNativeFinal = _amountNative;
            // Convert amount to cents
            (amountCentsFinal, assetDecimals) = getAssetAmountCents(_asset, _amountNative);
        }

        _transferCollateralCommon(_collateralProxy, _asset, _recipient, amountNativeFinal, feeNative, _directTransfer);
    }

    /**
     * @notice Transfer cents amount of assets from Collateral contract to recipient
     * @param _collateralProxy targeting Collateral proxy address
     * @param _asset asset's contract address
     * @param _amountCents asset amount to transfer in cents
     * @param _recipient address to receive asset
     * @param _feeBps fee in basis points
     * @param _directTransfer if false, transfer asset to coordinator first then to recipient
     * @return amountNativeFinal amount that was actually transferred in native asset
     * @return amountCentsFinal amount that was actually transferred in cents
     * @return feeNative fee in native asset
     * @return assetDecimals asset's smallest unit
     */
    function _transferCollateralCents(
        address _collateralProxy,
        address _asset,
        uint256 _amountCents,
        address _recipient,
        uint16 _feeBps,
        bool _directTransfer
    ) internal returns (uint256 amountNativeFinal, uint256 amountCentsFinal, uint256 feeNative, uint8 assetDecimals) {
        // Convert amount to native
        uint256 amountNative;
        (amountNative, assetDecimals) = getAssetAmountNative(_asset, _amountCents);

        // Calculate fee by flooring
        feeNative = (amountNative * _feeBps) / 10000;

        // Get current collateral balance
        uint256 currentCollateralBalanceNative = _asset == address(0)
            ? _collateralProxy.balance
            : IERC20Decimals(_asset).balanceOf(_collateralProxy);

        // Check if the amount to be transferred is greater than the collateral balance
        if (currentCollateralBalanceNative < amountNative + feeNative) {
            // Calculate amount and fee with the remaining collateral balance
            amountNativeFinal = (currentCollateralBalanceNative * 10000) / (10000 + _feeBps);
            // Convert amount to cents
            (amountCentsFinal, assetDecimals) = getAssetAmountCents(_asset, amountNativeFinal);
            // Calculate fee with the remaining collateral balance
            feeNative = currentCollateralBalanceNative - amountNativeFinal;
        } else {
            // Transfer full amount in parameter
            amountNativeFinal = amountNative;
            amountCentsFinal = _amountCents;
        }

        _transferCollateralCommon(_collateralProxy, _asset, _recipient, amountNativeFinal, feeNative, _directTransfer);
    }

    /**
     * @notice Sub function to transfer asset from Collateral contract to recipient
     * @dev Transfer flows:
     * 1. Direct transfer (_directTransfer = true):
     *    collateral -> recipient
     * 2. Indirect transfer (_directTransfer = false):
     *    collateral -> coordinator -> recipient
     * The indirect flow allows the coordinator to handle ERC20
     * approvals and native asset conversions if needed
     * @param _collateralProxy targeting Collateral proxy address
     * @param _asset asset's contract address
     * @param _recipient address to receive asset
     * @param _amountNativeFinal amount that was actually transferred in native decimals
     * @param _feeNative fee in native decimals
     * @param _directTransfer if true, transfer asset to recipient directly.
     *                        if false, transfer asset to coordinator first then to recipient
     *                        so that coordinator address can be allowed to withdraw asset by ERC-20 allowance function
     */
    function _transferCollateralCommon(
        address _collateralProxy,
        address _asset,
        address _recipient,
        uint256 _amountNativeFinal,
        uint256 _feeNative,
        bool _directTransfer
    ) internal {
        // Transfer native asset(ETH, AVAX, etc.)
        if (_asset == address(0)) {
            if (_directTransfer) {
                // Transfer asset to recipient directly
                ICollateral(_collateralProxy).withdrawNativeAsset(_recipient, _amountNativeFinal + _feeNative);
            } else {
                // Transfer asset to coordinator first then to
                ICollateral(_collateralProxy).withdrawNativeAsset(address(this), _amountNativeFinal + _feeNative);
                // Transfer asset to recipient
                (bool sentToRecipient, ) = _recipient.call{value: _amountNativeFinal + _feeNative}("");
                if (!sentToRecipient) {
                    revert CannotSendNativeAsset();
                }
            }
        } else {
            // Transfer ERC-20 asset
            if (_directTransfer) {
                // Transfer asset to recipient directly
                ICollateral(_collateralProxy).withdrawERC20Asset(_asset, _recipient, _amountNativeFinal + _feeNative);
            } else {
                // Transfer asset to coordinator first then to recipient
                ICollateral(_collateralProxy).withdrawERC20Asset(
                    _asset,
                    address(this),
                    _amountNativeFinal + _feeNative
                );
                IERC20Decimals asset = IERC20Decimals(_asset);
                asset.safeTransfer(_recipient, _amountNativeFinal + _feeNative);
            }
        }
    }

    /**
     * @notice Sub function of _verifyPaymentSignature and _verifyWithdrawal
     *         used to verify signature is from either executor or publisher
     * @dev Verification flow:
     * 1. Create domain separator with contract-specific data
     * 2. Hash the typed data according to EIP-712 standard
     * 3. Recover signer address from signature
     * 4. Verify signer is authorized (executor or publisher)
     * 5. Increment nonce to prevent signature reuse
     * @param _collateralProxy targeting Collateral proxy address
     * @param _messageHash keccak256 hashed message
     * @param _salt disambiguating salt for signature
     * @param _signature signature generated by either executor or publisher
     */
    function _verifySignature(
        address _collateralProxy,
        bytes32 _messageHash,
        bytes32 _salt,
        bytes calldata _signature
    ) internal {
        // Create domain separator
        bytes32 domainSeparator = keccak256(
            abi.encode(
                EIP712_DOMAIN_TYPE_HASH,
                keccak256(bytes(EIP712_DOMAIN_NAME)),
                keccak256(bytes(EIP712_DOMAIN_VERSION)),
                block.chainid,
                address(this),
                _salt
            )
        );

        // Create digest
        bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, _messageHash));

        // Verify that the signature was generated by either executor or publisher
        address recoveredAddress = digest.recover(_signature);
        (bool isExecutor, ) = _getAddressIndex(executors, recoveredAddress);
        (bool isPublisher, ) = _getAddressIndex(publishers, recoveredAddress);
        if (!isExecutor && !isPublisher) {
            revert InvalidSignature();
        }

        // Update nonce
        nonce[_collateralProxy] += 1;
    }

    /**
     * @notice Block renounceOwnership
     */
    function renounceOwnership() public virtual override onlyOwner {
        revert DisabledOwnershipRenouncement();
    }

    /**
     * @notice Receive native asset(ETH, AVAX, etc.)
     */
    receive() external payable {}
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// solhint-disable-next-line interface-starts-with-i
interface AggregatorV3Interface {
  function decimals() external view returns (uint8);

  function description() external view returns (string memory);

  function version() external view returns (uint256);

  function getRoundData(
    uint80 _roundId
  ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

  function latestRoundData()
    external
    view
    returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        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.9.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;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }
}

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

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

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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 (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

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

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.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));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    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");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    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");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation 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).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [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://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/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 (last updated v4.9.4) (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;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.0;

import "../Strings.sol";

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS,
        InvalidSignatureV // Deprecated in v4.8
    }

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature` or error string. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, signature);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
        bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
        uint8 v = uint8((uint256(vs) >> 255) + 27);
        return tryRecover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     *
     * _Available since v4.2._
     */
    function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, r, vs);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature);
        }

        return (signer, RecoverError.NoError);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, "\x19Ethereum Signed Message:\n32")
            mstore(0x1c, hash)
            message := keccak256(0x00, 0x3c)
        }
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from `s`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, "\x19\x01")
            mstore(add(ptr, 0x02), domainSeparator)
            mstore(add(ptr, 0x22), structHash)
            data := keccak256(ptr, 0x42)
        }
    }

    /**
     * @dev Returns an Ethereum Signed Data with intended validator, created from a
     * `validator` and `data` according to the version 0 of EIP-191.
     *
     * See {recover}.
     */
    function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x00", validator, data));
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";
import "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toString(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

// SPDX-FileCopyrightText: Copyright (C) 2024 Signify Holdings, Inc.
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.24;

/// @title ICollateral Interface
/// @notice Interface for managing collateral assets and admin-controlled withdrawals
interface ICollateral {
    /// @notice Checks if an address has admin privileges
    /// @param _admin The address to check
    /// @return bool True if the address is an admin, false otherwise
    function isAdmin(address _admin) external view returns (bool);

    /// @notice Withdraws ERC20 tokens from the contract
    /// @param _token The address of the ERC20 token to withdraw
    /// @param _recipient The address that will receive the tokens
    /// @param _amount The amount of tokens to withdraw
    function withdrawERC20Asset(
        address _token,
        address _recipient,
        uint256 _amount
    ) external;

    /// @notice Withdraws native blockchain asset (e.g., ETH) from the contract
    /// @param _recipient The address that will receive the native tokens
    /// @param _amount The amount of native tokens to withdraw
    function withdrawNativeAsset(address _recipient, uint256 _amount) external;

    /// @notice Verifies admin signatures for a withdrawal request
    /// @param _user The user requesting the withdrawal
    /// @param _asset The asset address (zero address for native token)
    /// @param _amount The amount to withdraw
    /// @param _recipient The address that will receive the assets
    /// @param _salts Unique identifiers to prevent signature reuse
    /// @param _signatures Array of admin signatures authorizing the withdrawal
    function verifyWithdrawAdminSignatures(
        address _user,
        address _asset,
        uint256 _amount,
        address _recipient,
        bytes32[] calldata _salts,
        bytes[] calldata _signatures
    ) external;
}

// SPDX-FileCopyrightText: Copyright (C) 2024 Signify Holdings, Inc.
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.24;

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

/// @title IERC20Decimals Interface
/// @notice Extension of the IERC20 interface that adds the decimals() function
/// @dev This interface is commonly used to get the number of decimals for an ERC20 token
interface IERC20Decimals is IERC20 {
    /// @notice Returns the number of decimals used by the token
    /// @return The number of decimals places the token uses
    /// @dev Most tokens use 18 decimals, following ETH itself, but this is not required
    function decimals() external view returns (uint8);
}

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

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_publisher","type":"address"},{"internalType":"address","name":"_executor","type":"address"},{"internalType":"address","name":"_treasury","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AmountTooBig","type":"error"},{"inputs":[],"name":"AmountTooSmall","type":"error"},{"inputs":[],"name":"BalanceAlreadyPaid","type":"error"},{"inputs":[],"name":"BalanceMustBeGreaterOrEqualToZero","type":"error"},{"inputs":[],"name":"CannotOverwriteStatement","type":"error"},{"inputs":[],"name":"CannotSendNativeAsset","type":"error"},{"inputs":[],"name":"DisabledOwnershipRenouncement","type":"error"},{"inputs":[{"internalType":"address","name":"executor","type":"address"}],"name":"ExecutorAlreadyExists","type":"error"},{"inputs":[{"internalType":"address","name":"executor","type":"address"}],"name":"ExecutorNotFound","type":"error"},{"inputs":[],"name":"InconsistentInputLength","type":"error"},{"inputs":[],"name":"InvalidFeeBps","type":"error"},{"inputs":[{"internalType":"int256","name":"price","type":"int256"}],"name":"InvalidPrice","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"InvalidTeamId","type":"error"},{"inputs":[{"internalType":"address","name":"executor","type":"address"}],"name":"LastExecutor","type":"error"},{"inputs":[{"internalType":"address","name":"publisher","type":"address"}],"name":"LastPublisher","type":"error"},{"inputs":[],"name":"NoAssets","type":"error"},{"inputs":[{"internalType":"address","name":"publisher","type":"address"}],"name":"PublisherAlreadyExists","type":"error"},{"inputs":[{"internalType":"address","name":"publisher","type":"address"}],"name":"PublisherNotFound","type":"error"},{"inputs":[],"name":"StalePrice","type":"error"},{"inputs":[],"name":"StatementNotFound","type":"error"},{"inputs":[],"name":"SupportedAssetAlreadyExists","type":"error"},{"inputs":[],"name":"TeamIdMismatch","type":"error"},{"inputs":[],"name":"TooEarlyToLiquidate","type":"error"},{"inputs":[{"internalType":"address","name":"unauthorizedAddress","type":"address"}],"name":"Unauthorized","type":"error"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"UnsupportedAsset","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"executor","type":"address"}],"name":"ExecutorAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"executor","type":"address"}],"name":"ExecutorRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountsNative","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"assetDecimals","type":"uint8"}],"name":"ExtraNativeAssetReturned","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"collateralProxy","type":"address"},{"indexed":false,"internalType":"address[]","name":"assets","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"amountsNative","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amountsCents","type":"uint256[]"},{"indexed":false,"internalType":"uint8[]","name":"assetDecimals","type":"uint8[]"},{"indexed":false,"internalType":"uint256","name":"netAmountCents","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"feeNative","type":"uint256[]"},{"indexed":true,"internalType":"string","name":"indexedTeamId","type":"string"},{"indexed":false,"internalType":"string","name":"teamId","type":"string"},{"indexed":true,"internalType":"string","name":"indexedStatementId","type":"string"},{"indexed":false,"internalType":"string","name":"statementId","type":"string"}],"name":"Liquidation","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newTreasury","type":"address"},{"indexed":false,"internalType":"address","name":"oldTreasury","type":"address"}],"name":"NewTreasury","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"collateralProxy","type":"address"},{"indexed":false,"internalType":"uint256","name":"newNonce","type":"uint256"}],"name":"NonceIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"collateralProxy","type":"address"},{"indexed":false,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountsNative","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountsCents","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeNative","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"assetDecimals","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"expiresAt","type":"uint256"},{"indexed":true,"internalType":"string","name":"indexedTeamId","type":"string"},{"indexed":false,"internalType":"string","name":"teamId","type":"string"}],"name":"PaymentFromCollateral","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"collateralProxy","type":"address"},{"indexed":false,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountsNative","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountsCents","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeNative","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"assetDecimals","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"expiresAt","type":"uint256"},{"indexed":true,"internalType":"string","name":"indexedTeamId","type":"string"},{"indexed":false,"internalType":"string","name":"teamId","type":"string"},{"indexed":true,"internalType":"string","name":"indexedStatementId","type":"string"},{"indexed":false,"internalType":"string","name":"statementId","type":"string"}],"name":"PaymentFromCollateralForStatement","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountNative","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountCents","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeNative","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"assetDecimals","type":"uint8"},{"indexed":true,"internalType":"string","name":"indexedTeamId","type":"string"},{"indexed":false,"internalType":"string","name":"teamId","type":"string"}],"name":"PaymentFromUserAccount","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountNative","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountCents","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeNative","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"assetDecimals","type":"uint8"},{"indexed":true,"internalType":"string","name":"indexedTeamId","type":"string"},{"indexed":false,"internalType":"string","name":"teamId","type":"string"},{"indexed":true,"internalType":"string","name":"indexedStatementId","type":"string"},{"indexed":false,"internalType":"string","name":"statementId","type":"string"}],"name":"PaymentFromUserAccountForStatement","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"publisher","type":"address"}],"name":"PublisherAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"publisher","type":"address"}],"name":"PublisherRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"string","name":"indexedStatementId","type":"string"},{"indexed":false,"internalType":"string","name":"statementId","type":"string"},{"indexed":false,"internalType":"uint256","name":"amountCents","type":"uint256"}],"name":"StatementMarkedPaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"string","name":"indexedStatementId","type":"string"},{"indexed":false,"internalType":"string","name":"statementId","type":"string"},{"indexed":true,"internalType":"string","name":"indexedTeamId","type":"string"},{"indexed":false,"internalType":"string","name":"teamId","type":"string"},{"indexed":false,"internalType":"uint256","name":"closingAccountBalanceCents","type":"uint256"},{"indexed":false,"internalType":"address","name":"collateralProxy","type":"address"},{"indexed":false,"internalType":"uint256","name":"liquidatableAfter","type":"uint256"}],"name":"StatementPublished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"string","name":"indexedStatementId","type":"string"},{"indexed":false,"internalType":"string","name":"statementId","type":"string"},{"indexed":true,"internalType":"string","name":"indexedTeamId","type":"string"},{"indexed":false,"internalType":"string","name":"teamId","type":"string"},{"indexed":false,"internalType":"uint256","name":"closingAccountBalanceCents","type":"uint256"},{"indexed":false,"internalType":"address","name":"collateralProxy","type":"address"},{"indexed":false,"internalType":"uint256","name":"liquidatableAfter","type":"uint256"}],"name":"StatementUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"address","name":"oracle","type":"address"},{"indexed":false,"internalType":"uint256","name":"staleThreshold","type":"uint256"},{"indexed":false,"internalType":"int256","name":"minPrice","type":"int256"},{"indexed":false,"internalType":"int256","name":"maxPrice","type":"int256"},{"indexed":false,"internalType":"uint16","name":"feeBps","type":"uint16"}],"name":"SupportedAssetAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"address","name":"oracle","type":"address"},{"indexed":false,"internalType":"uint256","name":"staleThreshold","type":"uint256"},{"indexed":false,"internalType":"int256","name":"minPrice","type":"int256"},{"indexed":false,"internalType":"int256","name":"maxPrice","type":"int256"},{"indexed":false,"internalType":"uint16","name":"feeBps","type":"uint16"}],"name":"SupportedAssetRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"collateralProxy","type":"address"},{"indexed":false,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountNative","type":"uint256"}],"name":"Withdrawal","type":"event"},{"inputs":[],"name":"EIP712_DOMAIN_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EIP712_DOMAIN_TYPE_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EIP712_DOMAIN_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAY_TYPE_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_TYPE_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_executor","type":"address"}],"name":"addExecutor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_publisher","type":"address"}],"name":"addPublisher","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"},{"internalType":"address","name":"_oracle","type":"address"},{"internalType":"uint256","name":"_staleThreshold","type":"uint256"},{"internalType":"int256","name":"_minPrice","type":"int256"},{"internalType":"int256","name":"_maxPrice","type":"int256"},{"internalType":"uint16","name":"_feeBps","type":"uint16"}],"name":"addSupportedAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"executors","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"},{"internalType":"uint256","name":"_amountNative","type":"uint256"}],"name":"getAssetAmountCents","outputs":[{"internalType":"uint256","name":"amountCents","type":"uint256"},{"internalType":"uint8","name":"assetDecimals","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"},{"internalType":"uint256","name":"_amountCents","type":"uint256"}],"name":"getAssetAmountNative","outputs":[{"internalType":"uint256","name":"amountNative","type":"uint256"},{"internalType":"uint8","name":"assetDecimals","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_collateralProxy","type":"address"}],"name":"increaseNonce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"}],"name":"isSupportedAsset","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string[]","name":"_statementIds","type":"string[]"},{"internalType":"address[][]","name":"_assets","type":"address[][]"}],"name":"liquidateAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collateralProxy","type":"address"},{"internalType":"address","name":"_asset","type":"address"},{"internalType":"uint256","name":"_amountNative","type":"uint256"},{"internalType":"uint256","name":"_expiresAt","type":"uint256"},{"internalType":"bytes32","name":"_salt","type":"bytes32"},{"internalType":"bytes","name":"_signature","type":"bytes"},{"internalType":"string","name":"_teamId","type":"string"}],"name":"makePaymentFromCollateral","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collateralProxy","type":"address"},{"internalType":"address","name":"_asset","type":"address"},{"internalType":"uint256","name":"_amountNative","type":"uint256"},{"internalType":"uint256","name":"_expiresAt","type":"uint256"},{"internalType":"bytes32","name":"_salt","type":"bytes32"},{"internalType":"bytes","name":"_signature","type":"bytes"},{"internalType":"string","name":"_teamId","type":"string"},{"internalType":"string","name":"_statementId","type":"string"}],"name":"makePaymentFromCollateralForStatement","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"},{"internalType":"uint256","name":"_amountNative","type":"uint256"},{"internalType":"string","name":"_teamId","type":"string"}],"name":"makePaymentFromUserAccount","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"},{"internalType":"uint256","name":"_amountNative","type":"uint256"},{"internalType":"string","name":"_statementId","type":"string"}],"name":"makePaymentFromUserAccountForStatement","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"_statementId","type":"string"},{"internalType":"uint256","name":"_amountCents","type":"uint256"}],"name":"markStatementPaid","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string[]","name":"_statementIds","type":"string[]"},{"internalType":"string[]","name":"_teamIds","type":"string[]"},{"internalType":"uint256[]","name":"_closingAccountBalanceCents","type":"uint256[]"},{"internalType":"address[]","name":"_collateralProxies","type":"address[]"},{"internalType":"uint256[]","name":"_liquidatableAfters","type":"uint256[]"}],"name":"publishStatements","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"publishers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_executor","type":"address"}],"name":"removeExecutor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_publisher","type":"address"}],"name":"removePublisher","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"}],"name":"removeSupportedAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"statements","outputs":[{"internalType":"string","name":"id","type":"string"},{"internalType":"string","name":"teamId","type":"string"},{"internalType":"uint256","name":"closingAccountBalanceCents","type":"uint256"},{"internalType":"uint256","name":"paidAmountCents","type":"uint256"},{"internalType":"address","name":"collateralProxy","type":"address"},{"internalType":"uint256","name":"liquidatableAfter","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"supportedAssets","outputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"oracle","type":"address"},{"internalType":"uint256","name":"staleThreshold","type":"uint256"},{"internalType":"int256","name":"minPrice","type":"int256"},{"internalType":"int256","name":"maxPrice","type":"int256"},{"internalType":"uint16","name":"feeBps","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"treasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_statementId","type":"string"},{"internalType":"string","name":"_teamId","type":"string"},{"internalType":"uint256","name":"_closingAccountBalanceCent","type":"uint256"},{"internalType":"address","name":"_collateralProxy","type":"address"},{"internalType":"uint256","name":"_liquidatableAfter","type":"uint256"}],"name":"updateStatement","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_treasury","type":"address"}],"name":"updateTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collateralProxy","type":"address"},{"internalType":"address","name":"_asset","type":"address"},{"internalType":"uint256","name":"_amountNative","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_expiresAt","type":"uint256"},{"internalType":"bytes32","name":"_executorPublisherSalt","type":"bytes32"},{"internalType":"bytes","name":"_executorPublisherSignature","type":"bytes"},{"internalType":"bytes32[]","name":"_adminSalts","type":"bytes32[]"},{"internalType":"bytes[]","name":"_adminSignatures","type":"bytes[]"},{"internalType":"bool","name":"_directTransfer","type":"bool"}],"name":"withdrawAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

0x6080346200026e57601f620049eb38819003918201601f191683019291906001600160401b03841183851017620002735781608092849260409687528339810103126200026e57620000518162000289565b906020916200006283830162000289565b906200007e60606200007687860162000289565b940162000289565b906200008a336200029e565b60018055600080546001600160a01b039491929033908616036200022c5784811615620001d95790620000be85926200029e565b16938415620001c857600495865491680100000000000000009283811015620001b55760018101808a55811015620001a2578885528285200180546001600160a01b0319908116909817905585169283156200019257600554928310156200017f5760018301806005558310156200016c576005815220019084825416179055169182156200015e57506002541617600255516147059081620002e68239f35b835163d92e233d60e01b8152fd5b634e487b7160e01b815260328852602490fd5b634e487b7160e01b815260418852602490fd5b885163d92e233d60e01b81528890fd5b634e487b7160e01b855260328952602485fd5b634e487b7160e01b855260418952602485fd5b865163d92e233d60e01b8152600490fd5b875162461bcd60e51b815260048101889052602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608490fd5b60648789519062461bcd60e51b825280600483015260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b600080fd5b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036200026e57565b600080546001600160a01b039283166001600160a01b03198216811783559216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a356fe61044080604052600436101561001e575b50361561001c57600080fd5b005b600061040052610400513560e01c90816305c6eb6e14613148575080631c46bc6f14612fce5780631f5a0bbe14612f1e5780632026583014612817578063213bc218146127da57806324788429146126be57806324969d76146123f55780632ad273e0146123d05780632e6dfcd814612396578063348011ae14611d775780634312642c14611afe5780634517c99414611a9257806346518700146119a35780634b268241146114335780635cc333211461141557806361d027b3146113ea57806370ae92d2146113ac578063715018a61461137f578063763f323d146112b25780637892ca1414610ee75780637f51bb1f14610e6b5780638247a97c14610dba5780638da5cb5b14610d8d5780639be918e614610d60578063a6f0b38e146109cf578063ae61c5ae1461085f578063b727b57914610822578063bd81579e146107a5578063cd61aec7146104d1578063cdf1613914610323578063f2fde38b14610251578063f97a05df14610228578063fd070296146101f25763ff060fbd146101a95738610010565b346101eb5760203660031901126101eb576004356004548110156101eb576101d26020916134dc565b905460405160039290921b1c6001600160a01b03168152f35b6104005180fd5b346101eb57610400513660031901126101eb57610224610210613513565b60405191829160208352602083019061344d565b0390f35b346101eb5760203660031901126101eb576004356005548110156101eb576101d260209161348f565b346101eb5760203660031901126101eb5761026a613183565b610272613d97565b6001600160a01b039081169081156102cf576104005154826001600160601b0360a01b8216176104005155167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e06104005161040051a36104005180f35b60405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608490fd5b346101eb5760c03660031901126101eb5761033c613183565b6103446131af565b90604435606435926084359260a43561ffff94858216968783036101eb5761036a613d97565b61037384613d1f565b6104bf576001600160a01b03938416978815806104b5575b6104a3576127108111610491577fa4e0e512ee3c2f782d86871b7ed121041119b7db37de7962053d865d8eb2d2df976005610487968b604051916103ce83613268565b81835260208301818c16815260408401908d8252606085019289845260808601948b865260a08701998a52610400515260076020528060406104005120965116906001600160601b0360a01b918288541617875560018701925116908254161790555160028401555160038301555160048201550191511661ffff19825416179055604051958695869360809361ffff939796929760a087019860018060a01b0316875260208701526040860152606085015216910152565b0390a26104005180f35b604051638bff87cf60e01b8152600490fd5b60405163d92e233d60e01b8152600490fd5b508486161561038b565b6040516347d3451560e01b8152600490fd5b6104da36613222565b600260019493929454146107605760026001556104f683613d1f565b1561073f5760018060a01b039384841691826104005152600760205261271061052d61ffff600560406104005120015416866138dc565b046105428561053c83826137ca565b97613933565b96909785156000146106b7578134106106a5576104005160025490918291829182918691165af1610571613e4b565b5015610693576105819034613726565b806105fe575b509060ff7f0932fd3714db79d275b23965abc0db663088581d46e84624450d865031d9f5279661048794935b6001805560405184868237808581016000815203902099604051988998338a5260208a01526040890152606088015260808701521660a085015260e060c085015260e084019161380b565b610400519193929180808084335af1610615613e4b565b5015610693577f0932fd3714db79d275b23965abc0db663088581d46e84624450d865031d9f527966104879460ff927f5e8ab9aefcc768e9e44526b6fa078340387a33e72ad445b98cd87434af93a336604051806106858633958390929160ff6020916040840195845216910152565b0390a2929394509650610587565b604051636a51e8d560e11b8152600490fd5b60405163617ab12d60e11b8152600490fd5b6040516323b872dd60e01b6020820152336024820152306044820152606481018390527f0932fd3714db79d275b23965abc0db663088581d46e84624450d865031d9f5279861048796959460ff949193909261073a9291906107309061072a81608481015b03601f1981018352826132e7565b8b614340565b6002541689614304565b6105b3565b60405163ee84f40b60e01b81526001600160a01b0384166004820152602490fd5b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b346101eb5760203660031901126101eb5760c06001600160a01b03806107c9613183565b166104005152600760205260406104005120818154169160018201541690600281015460038201549061ffff600560048501549401541693604051958652602086015260408501526060840152608083015260a0820152f35b346101eb57610400513660031901126101eb5760206040517fd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac564728152f35b346101eb5760203660031901126101eb57610878613183565b610880613d97565b60045490600182146109ad5761089d8161089861362a565b613def565b92901561098c5760001990808201908111610972576108d8936108c26108fc926134dc565b90546001600160a01b03969192909187916134dc565b92909360031b1c169060018060a01b038084549260031b9316831b921b1916179055565b600454801561095c5701610927610912826134dc565b81549060018060a01b039060031b1b19169055565b600455167f215ef528757ca84646fb5c401012a31bcbf1f99487a51e57a57a0c0afa0d3dd26104005161040051a26104005180f35b634e487b7160e01b600052603160045260246000fd5b634e487b7160e01b61040051526011600452602461040051fd5b604051634b84d63b60e11b81526001600160a01b0383166004820152602490fd5b604051635ec51d0360e01b81526001600160a01b039091166004820152602490fd5b346101eb5760e03660031901126101eb576109e8613183565b6109f06131af565b6001600160401b0360a4358181116101eb57610a109036906004016131f5565b919060c4358281116101eb57610a2a9036906004016131f5565b92909360018060a01b038716610400515260036020526040610400512054926040519283602081011090602085011117610d4a5760208301604052610400518352610a7487613d1f565b15610d29576064354211610d1757610aeb610afc93604051610aa38161071c8d8d604435913360208601613c8d565b610aae36898b613323565b6020815191012091602081519101206040519288602085015260643560408501526060840152608083015260808252610ae6826132b1565b613cde565b60843590602081519101208961408b565b60025461040080516001600160a01b0387811691829052600760205291516040902060050154929091169661ffff929092169490610c8f578131935b612710610b47876044356138dc565b04610b54816044356137ca565b861015610c455750612710850292858404612710148615171561097257610bac610c3b9461ffff610ba57fef6f8329f0b365435f68fe50cb6d81a6f381cf67ed3c7568158dbb0dabd6f7f89a613e37565b16906138ef565b9460ff86610bd48b82610bca610bc28284613933565b95909d613726565b9e8f925b88614234565b6040518587823780868101600081520390209b604051998a996101209d338c5260018060a01b031660208c015260408b015260608a015260808901521660a087015260c086015260643560e08601528761010086015260018060a01b03169684019161380b565b0390a36104005180f35b977fef6f8329f0b365435f68fe50cb6d81a6f381cf67ed3c7568158dbb0dabd6f7f8965087955060443594610c3b949160ff90610bd48c89610c87818d613933565b94909c610bce565b6040516370a0823160e01b81526001600160a01b038381166004830152602090829060249082908b165afa908115610d09576104005191610cd2575b5093610b38565b90506020813d602011610d01575b81610ced602093836132e7565b81010312610cfc575188610ccb565b600080fd5b3d9150610ce0565b6040513d61040051823e3d90fd5b604051638baa579f60e01b8152600490fd5b60405163ee84f40b60e01b81526001600160a01b0388166004820152602490fd5b634e487b7160e01b600052604160045260246000fd5b346101eb5760203660031901126101eb576020610d83610d7e613183565b613d1f565b6040519015158152f35b346101eb57610400513660031901126101eb5761040051546040516001600160a01b039091168152602090f35b346101eb5760203660031901126101eb57610dd3613183565b610ddf3361089861353a565b5015610e535760018060a01b03168061040051526003602052604061040051208054600019811461097257600101905580610400515260036020527ffc69110dd11eb791755e4abd6b7d281bae236de95736d38a23782814be5e10db60206040610400512054604051908152a26104005180f35b60405163472511eb60e11b8152336004820152602490fd5b346101eb5760203660031901126101eb57610e84613183565b610e8c613d97565b6001600160a01b039081169081156104a35760207f567657fa3f286518b318f4a29870674f433f622fdfc819691acb13105b2282259160025490846001600160601b0360a01b83161760025560405191168152a26104005180f35b346101eb576101003660031901126101eb57610f01613183565b610f096131af565b6001600160401b0360a4358181116101eb57610f299036906004016131f5565b92909360c4358381116101eb57610f449036906004016131f5565b94909360e4359081116101eb57610f5f9036906004016131f5565b94909160018060a01b03841661040051526003602052604061040051205497604051878582376020818981016006815203019020976001610fb18a610faa60405180948193016133b7565b03826132e7565b60208151910120610fc3368387613323565b60208151910120036112a057610fda368987613323565b610fe388613d1f565b1561127f576064354211610d1757604051611075956110649361102c9190611025846110176044358f8f3360208601613c8d565b03601f1981018652856132e7565b3691613323565b602081519101209160208151910120604051928d602085015260643560408501526060840152608083015260808252610ae6826132b1565b60843590602081519101208661408b565b60025461040080516001600160a01b038681169182905260076020529151604090206005015461ffff169791909216939161120a578131945b6127106110bd896044356138dc565b046110ca816044356137ca565b8710156111e9575061271086029786890461271014871517156109725761111f9861ffff610ba56110fa93613e37565b988998899661111461110c8986613933565b9c909a613726565b9b8c915b8588614234565b6111298784613e7b565b6111356001840161382c565b9860405189878237808a8101600081520390209a60405198899861014095338b52600160a01b600190031660208b015260408a01526060890152608088015260ff1660a087015260c086015260643560e086015280610100860152840190600101906111a0916133b7565b8381036101208501526001600160a01b03909116946111be9261380b565b037fabd54055439057097b39d314db5c38dc4b70c245934d3552fa3038a29be3674d91a46104005180f35b986044359850949550879461111f8a6112028885613933565b9b9099611118565b6040516370a0823160e01b81526001600160a01b038381166004830152602090829060249082908a165afa908115610d0957610400519161124d575b50946110ae565b90506020813d602011611277575b81611268602093836132e7565b81010312610cfc575189611246565b3d915061125b565b60405163ee84f40b60e01b81526001600160a01b0389166004820152602490fd5b604051631aae564160e01b8152600490fd5b346101eb5760203660031901126101eb576112cb613183565b6112d3613d97565b6001600160a01b0381169081156104a3576112f0816108986135b3565b506113675760045490600160401b821015610d4a5761131882600161133694016004556134dc565b90919060018060a01b038084549260031b9316831b921b1916179055565b7f466d40d3c5afb07f32aa87fa68d587f144c6fee2dfb883b3363937d5427e04426104005161040051a26104005180f35b60405162c40a5760e21b815260048101839052602490fd5b346101eb57610400513660031901126101eb5761139a613d97565b60405163796d957f60e01b8152600490fd5b346101eb5760203660031901126101eb576001600160a01b036113cd613183565b166104005152600360205260206040610400512054604051908152f35b346101eb57610400513660031901126101eb576002546040516001600160a01b039091168152602090f35b346101eb57610400513660031901126101eb57610224610210613472565b346101eb576101403660031901126101eb5761144d613183565b6114556131af565b61145d613199565b9060c4356001600160401b0381116101eb5761147d9036906004016131f5565b60e4356001600160401b0381116101eb5761149c9036906004016131c5565b92610104356001600160401b0381116101eb576114bd9036906004016131c5565b91909361012435151561012435036101eb576084354211610d1757611598916115876040517f947f5e3b95089db2a80a00e383947991b50e131febda16f519522edd0af91cc8602082015233604082015260018060a01b038c16606082015260018060a01b038a16608082015260443560a082015260a0815261153f81613268565b60018060a01b038c16610400515260036020526040610400512054906040519160018060a01b038d1660208401526040830152608435606083015260608252610ae682613296565b60a43590602081519101208b61408b565b6001600160a01b0387163b156101eb57604051638e0a39f560e01b81523360048201526001600160a01b03868116602483015260448035908301528716606482015260c0608482015260c48101859052936001600160fb1b0381116101eb5760051b9291839060e486013783830184810360e00160a486015260e4810182905261040051600583901b8201610104908101959201929184905b838310611942575050505050508180610400519203816104005160018060a01b0389165af18015610d0957611933575b506000926001600160a01b0382166117f457610124351561174357611688846044356137ca565b846001600160a01b0383163b1561174057604051631520322360e01b81526001600160a01b038616600482015260248101929092528180604481010381836001600160a01b0387165af180156117355761171d575b506000805160206146b08339815191529293505b604080516001600160a01b03938416815294831660208601526044359085015216918060608101610487565b6117278591613283565b61173157836116dd565b8380fd5b6040513d87823e3d90fd5b80fd5b9192611751816044356137ca565b6001600160a01b0384163b156117f057604051631520322360e01b815230600482015260248101919091528181604481836001600160a01b0389165af180156117e5579082916117d1575b5080806117ab816044356137ca565b875af16117b6613e4b565b5015610693576000805160206146b0833981519152916116f1565b6117da90613283565b61174057808561179c565b6040513d84823e3d90fd5b5080fd5b610124351561189257611809846044356137ca565b846001600160a01b0383163b1561174057604051634e14d37160e01b81526001600160a01b0380861660048301528616602482015260448101929092528180606481010381836001600160a01b0387165af180156117355761187e575b506000805160206146b08339815191529293506116f1565b6118888591613283565b6117315783611866565b6001600160a01b0381163b1561173157604051634e14d37160e01b81526001600160a01b03831660048201523060248201526044803590820152848180606481010381836001600160a01b0387165af18015611735576000805160206146b0833981519152949561191f9261190f92611924575b506044356137ca565b856001600160a01b038516614304565b6116f1565b61192d90613283565b87611906565b61193c90613283565b83611661565b9091929394956101031982890182030186528635601e19843603018112156101eb57830190602082359201916001600160401b0381116101eb5780360383136101eb57611995602092839260019561380b565b980196019493019190611631565b346101eb5760203660031901126101eb576004356001600160401b0381116101eb57366023820112156101eb576119ff60206119ec611a6b933690602481600401359101613323565b816040519382858094519384920161335a565b8101600681520301902060405190611a2282611a1b81846133b7565b03836132e7565b604051611a3681610faa81600186016133b7565b6002820154916003810154611a79600560018060a01b036004850154169301549360405197889760c0895260c089019061344d565b90878203602089015261344d565b9360408601526060850152608084015260a08301520390f35b346101eb5760403660031901126101eb576004356001600160401b0381116101eb57611ac29036906004016131f5565b90611acf3361089861353a565b5015610e535781611af792604051928337810190600682526020816024359303019020613e7b565b6104005180f35b346101eb5760a03660031901126101eb576001600160401b036004358181116101eb57611b2f9036906004016131f5565b6024358381116101eb57611b479036906004016131f5565b90611b50613199565b92611b5d336108986135b3565b5015610e535760405181868237602081838101600681520301902095600187015490611b888261337d565b15611d6557600388015460443510611d53576001600160a01b038616156104a3578411611d395783611bbc611bc59261337d565b60018901613bf1565b6104005195601f8411600114611c9e5790610c3b91611c2185807f34974c232c070c964b4e7ff79c7d9009ed9b6a4029aa166f60d2311d52fb749998999a6104005191611c93575b508160011b916000199060031b1c19161790565b60018201555b60443560028201556004810180546001600160a01b0319166001600160a01b038916179055608435600590910155604051818882378082810161040051815203902095604051858582378086810161040051815203902097604051958695608435946044359388613c47565b90508601358b611c0d565b60018101610400515260206104005120610400515b601f1986168110611d21575090610c3b92917f34974c232c070c964b4e7ff79c7d9009ed9b6a4029aa166f60d2311d52fb749996979886601f19811610611d07575b5050600185811b016001820155611c27565b850135600019600388901b60f8161c191690558880611cf5565b84890135825560209889019860019092019101611cb3565b634e487b7160e01b61040051526041600452602461040051fd5b6040516348e726ad60e01b8152600490fd5b60405163d69134e160e01b8152600490fd5b346101eb5760a03660031901126101eb576004356001600160401b0381116101eb57611da79036906004016131c5565b61018052610100526024356001600160401b0381116101eb57611dce9036906004016131c5565b60a0526080526044356001600160401b0381116101eb57611df39036906004016131c5565b6101e052610140526064356001600160401b0381116101eb57611e1a9036906004016131c5565b60c052610120526084356001600160401b0381116101eb57611e409036906004016131c5565b60e05261016052611e53336108986135b3565b5015610e53576101805160a05114801590612387575b8015612379575b801561236b575b61235957610400516101c0525b60a0516101c05110611e97576104005180f35b611ea96101c05160a0516080516136e5565b90501561234757611ec46101c0516101e05161014051613792565b3515612335576101c05160c051610120516001600160a01b0392611ef092611eeb92613792565b6137a2565b16156104a35760026020611f0e6101c05161018051610100516136e5565b919082604051938492833781016006815203019020015461232357611f3d6101c05161018051610100516136e5565b90611fb9611f536101c05160a0516080516136e5565b9190611f696101c0516101e05161014051613792565b3592611fad611f84611eeb6101c05160c05161012051613792565b95611f986101c05160e05161016051613792565b35976040516101a0526110256101a051613268565b6101a051523691613323565b60206101a051015260406101a05101526104005160606101a051015260018060a01b031660806101a051015260a06101a051015260206120036101c05161018051610100516136e5565b9190826040519384928337810160068152030190206101a051518051906001600160401b038211611d39576120428261203c855461337d565b85613bf1565b6020906001601f8411146122b657918091612076936104005192612233575b50508160011b916000199060031b1c19161790565b81555b60206101a05101518051906001600160401b038211611d39576120ac826120a3600186015461337d565b60018601613bf1565b6020906001601f84111461223e579180916120df9361040051926122335750508160011b916000199060031b1c19161790565b60018201555b60406101a0510151600282015560606101a051015160038201556004810160018060a01b0360806101a0510151166001600160601b0360a01b825416179055600560a06101a05101519101556121456101c05161018051610100516136e5565b7f81b3d17879f50f75656c1abbaa17b9e3f8476269565cf5e324f6ce031694328c61217a6101c05161018051610100516136e5565b906122206121906101c05160a0516080516136e5565b9690956121a56101c05160a0516080516136e5565b6121bc6101c09a929a516101e05161014051613792565b35916121d4611eeb6101c05160c05161012051613792565b936121e86101c05160e05161016051613792565b359581604051928392833781016104005181520390209981604051928392833781016104005181520390209960405197889788613c47565b0390a360016101c051016101c052611e84565b015190508480612061565b9190600184016104005152602061040051209061040051935b601f198416851061229b576001945083601f19811610612282575b505050811b0160018201556120e5565b015160001960f88460031b161c19169055838080612272565b81810151835560209485019460019093019290910190612257565b908361040051526020610400512091610400515b601f198516811061230b575083601f198116106122f2575b505050600190811b018155612079565b015160001960f88460031b161c191690558280806122e2565b919260206001819286850151815501940192016122ca565b604051630226ac6f60e31b8152600490fd5b604051637f7d194160e11b8152600490fd5b604051632a982bb360e01b8152600490fd5b6040516313cab10f60e11b8152600490fd5b5060e05160a0511415611e77565b5060c05160a0511415611e70565b506101e05160a0511415611e69565b346101eb5760403660031901126101eb576123bb6123b2613183565b60243590613ac7565b6040805192835260ff91909116602083015290f35b346101eb5760403660031901126101eb576123bb6123ec613183565b60243590613933565b6123fe36613222565b9092916001916002835414610760576002835561241a84613d1f565b1561269d5760018060a01b0394858516918261040051526020946007865261271061245361ffff600560406104005120015416876138dc565b0496612469866124638a826137ca565b92613933565b9190998615600014612619578134106106a5576104005160025490918291829182918691165af1612498613e4b565b5015610693576124a89034613726565b8061258b575b50610c3b9392917fd0b1fc3e37b30b21f09bb6584ae8eaf906e539f6ce3298a9bd78b87d374b27ed9760ff61257d935b8380556124fe8d604051888a823785818a81016006815203019020613e7b565b61253384604051888a823785818a810160068152030190200194604051888a823785818a81016006815203019020019461382c565b9b60405187898237808881016104005181520390209d6040519b8c9b610100968d3390528d015260408c015260608b015260808a01521660a08801528060c08801528601906133b7565b9184830360e086015261380b565b61040051909493929080808088335af16125a3613e4b565b5015610693576040805195865260ff83811660208801527fd0b1fc3e37b30b21f09bb6584ae8eaf906e539f6ce3298a9bd78b87d374b27ed99610c3b9761257d95919333917f5e8ab9aefcc768e9e44526b6fa078340387a33e72ad445b98cd87434af93a3369190a293505097509192936124ae565b6040516323b872dd60e01b818b0152336024820152306044820152606481018390527fd0b1fc3e37b30b21f09bb6584ae8eaf906e539f6ce3298a9bd78b87d374b27ed99610c3b97969561257d959094919360ff9391926126989290919061268e90612688816084810161071c565b8d614340565b600254168b614304565b6124de565b60405163ee84f40b60e01b81526001600160a01b0385166004820152602490fd5b346101eb5760203660031901126101eb576126d7613183565b6126df613d97565b60055490600182146127b8576126f78161089861353a565b9290156127975760001990808201908111610972576108d89361271c6127329261348f565b90546001600160a01b039691929091879161348f565b600554801561277d57016127486109128261348f565b600555167f4a2cf608bfb427f53279ec7f0eadf48913b9346ccefc3af138dbdec14ea0907d6104005161040051a26104005180f35b634e487b7160e01b61040051526031600452602461040051fd5b6040516302cf3fb360e51b81526001600160a01b0383166004820152602490fd5b60405163b0a503db60e01b81526001600160a01b039091166004820152602490fd5b346101eb57610400513660031901126101eb5760206040517f947f5e3b95089db2a80a00e383947991b50e131febda16f519522edd0af91cc88152f35b346101eb5760403660031901126101eb576001600160401b036004358181116101eb576128489036906004016131c5565b61022052610200526024359081116101eb576128689036906004016131c5565b610300526103e05261287c3361089861353a565b5015610e535761030051610220510361235957610400516103c0525b610220516103c051106128ac576104005180f35b6128c06103c051610300516103e0516136a1565b905015612f0c5760206128dd6103c05161022051610200516136e5565b9190826040519384928337810160068152030190206102c05260026102c05101548015611d655760056102c05101544210612efa576102c0516003015461292391613726565b6102e052610400516103405261294d6129466103c051610300516103e0516136a1565b9050613760565b610320526129686129466103c051610300516103e0516136a1565b610380526129ac6129836103c051610300516103e0516136a1565b90506129a161299182613749565b60405161042052610420516132e7565b806104205152613749565b601f19013660206104205101376129d06129466103c051610300516103e0516136a1565b61036052610400516103a0525b6129f16103c051610300516103e0516136a1565b90506103a0511015612ef5576102e051610340511015612cc957612a32610d7e611eeb612a286103c051610300516103e0516136a1565b6103a05191613792565b15612c8e576102c051600401546001600160a01b0316610280526103c051610300516103e051612a6992611eeb92612a28926136a1565b6102a052612a7d610340516102e051613726565b60018060a01b036002541690611eeb612ab9612aa36103c051610300516103e0516136a1565b6103a0516001600160a01b039492909190613792565b166104005152600760205261ffff600560406104005120015416916104005150610400515061040051506104005150612af5826102a051613ac7565b929092612710612b0586866138dc565b6102a05191900491906001600160a01b031680612c2357506102805131945b612b2e83826137ca565b861015612c10575050505081612710810204612710148215171561097257612b6961ffff612b5e612b9695613e37565b1661271084026138ef565b80938192612b85612b7d846102a051613933565b949096613726565b9384915b6102a05161028051614234565b60ff612ba86103a051610420516137b6565b91169052612bbc6103a051610360516137b6565b52612bcd6103a051610380516137b6565b52612bde6103a051610320516137b6565b52612bfc612bf26103a051610380516137b6565b51610340516137ca565b6103405260016103a051016103a0526129dd565b955091935091612b969083908690612b89565b6020602491604051928380926370a0823160e01b82526102805160048301525afa908115610d09576104005191612c5c575b5094612b24565b90506020813d602011612c86575b81612c77602093836132e7565b810103126101eb575187612c55565b3d9150612c6a565b6024612caa611eeb612a286103c051610300516103e0516136a1565b60405163ee84f40b60e01b81526001600160a01b039091166004820152fd5b612cdd6103405160036102c05101546137ca565b60036102c0510155610faa612d2560016020612d036103c05161022051610200516136e5565b91908260405193849283378101600681520301902001604051928380926133b7565b60018060a01b0360046102c05101541690612d4a6103c051610300516103e0516136a1565b612d616103c09492945161022051610200516136e5565b9093612d776103c05161022051610200516136e5565b919092604051808351612d8e81836020880161335a565b8101039020968160405192839283378101610400518152039020966040519480610100808801908852526101208601919061040051905b808210612ecb57505050612de78186612df893036020880152610320516137d7565b8581036040870152610380516137d7565b84810360608601528061026052602061042051519182815201610260526020610420510161024052610400515b818110612e9f57505083927f8ffbf5b0226477a4783d04864504c4387123a733c097c607511ad62050434a66949261257d612e8c9361034051608087015285610260510360a0870152612e7e61026051610360516137d7565b9086820360c088015261344d565b0390a460016103c051016103c052612898565b60019060ff61024051511661026051526020610260510161026052602061024051016102405201612e25565b90919283359060018060a01b0382168092036101eb57602081600193829352019401920190612dc5565b612cc9565b60405163e792cf8360e01b8152600490fd5b604051635373815f60e01b8152600490fd5b346101eb5760203660031901126101eb57612f37613183565b612f3f613d97565b6001600160a01b0381169081156104a357612f5c8161089861353a565b50612fb55760055490600160401b821015611d3957611318826001612f84940160055561348f565b7fae5b7c3b000f575c241001dc9bcb3d8778376889353b07121115574eceff78c56104005161040051a26104005180f35b604051633aec0c9d60e11b815260048101839052602490fd5b346101eb5760203660031901126101eb57612fe7613183565b612fef613d97565b612ff881613d1f565b15613126577f7dfcb1d4b5c069c3ad4d9c994ca81ed8f889cfe898eca01d3e6f50e73af6e82660018060a01b0380921691826104005152600760205260406104005120906104876040519261304c84613268565b82815416845282600182015416956020850196875260056002830154946040870195865280600385015497606081019889526004860154956080820196875261ffff9485910154169460a082019586526104005152600760205260406104005120610400518155610400516001820155610400516002820155610400516003820155610400516004820155600561040051910155511698511694519551925191511691604051958695869360809361ffff939796929760a087019860018060a01b0316875260208701526040860152606085015216910152565b60405163ee84f40b60e01b81526001600160a01b039091166004820152602490fd5b346101eb57610400513660031901126101eb57807f5d26a7f4630b011524e20c7a46022d61d8ba8ea0e861887303530b0f3362609760209252f35b600435906001600160a01b0382168203610cfc57565b606435906001600160a01b0382168203610cfc57565b602435906001600160a01b0382168203610cfc57565b9181601f84011215610cfc578235916001600160401b038311610cfc576020808501948460051b010111610cfc57565b9181601f84011215610cfc578235916001600160401b038311610cfc5760208381860195010111610cfc57565b6060600319820112610cfc576004356001600160a01b0381168103610cfc579160243591604435906001600160401b038211610cfc57613264916004016131f5565b9091565b60c081019081106001600160401b03821117610d4a57604052565b6001600160401b038111610d4a57604052565b608081019081106001600160401b03821117610d4a57604052565b60a081019081106001600160401b03821117610d4a57604052565b604081019081106001600160401b03821117610d4a57604052565b90601f801991011681019081106001600160401b03821117610d4a57604052565b6001600160401b038111610d4a57601f01601f191660200190565b92919261332f82613308565b9161333d60405193846132e7565b829481845281830111610cfc578281602093846000960137010152565b60005b83811061336d5750506000910152565b818101518382015260200161335d565b90600182811c921680156133ad575b602083101461339757565b634e487b7160e01b600052602260045260246000fd5b91607f169161338c565b8054600093926133c68261337d565b9182825260209360019160018116908160001461342e57506001146133ed575b5050505050565b90939495506000929192528360002092846000945b83861061341a575050505001019038808080806133e6565b805485870183015294019385908201613402565b60ff19168685015250505090151560051b0101915038808080806133e6565b906020916134668151809281855285808601910161335a565b601f01601f1916010190565b6040519061347f826132cc565b60018252601960f91b6020830152565b6005548110156134c65760056000527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00190600090565b634e487b7160e01b600052603260045260246000fd5b6004548110156134c65760046000527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b0190600090565b60405190613520826132cc565b600b82526a21b7b7b93234b730ba37b960a91b6020830152565b6040519060055480835282602091602082019060056000527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0936000905b8282106135905750505061358e925003836132e7565b565b85546001600160a01b031684526001958601958895509381019390910190613578565b6040519060045480835282602091602082019060046000527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b936000905b8282106136075750505061358e925003836132e7565b85546001600160a01b0316845260019586019588955093810193909101906135f1565b6040519060045480835282602091602082019060046000527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b936000905b82821061367e5750505061358e925003836132e7565b85546001600160a01b031684526001958601958895509381019390910190613668565b91908110156134c65760051b81013590601e1981360301821215610cfc5701908135916001600160401b038311610cfc576020018260051b36038113610cfc579190565b91908110156134c65760051b81013590601e1981360301821215610cfc5701908135916001600160401b038311610cfc576020018236038113610cfc579190565b9190820391821161373357565b634e487b7160e01b600052601160045260246000fd5b6001600160401b038111610d4a5760051b60200190565b9061376a82613749565b61377760405191826132e7565b8281528092613788601f1991613749565b0190602036910137565b91908110156134c65760051b0190565b356001600160a01b0381168103610cfc5790565b80518210156134c65760209160051b010190565b9190820180921161373357565b90815180825260208080930193019160005b8281106137f7575050505090565b8351855293810193928101926001016137e9565b908060209392818452848401376000828201840152601f01601f1916010190565b6040518091600090805461383f8161337d565b9160019180831690811561389c575060011461385f575b50505003902090565b909192506000526020906020600020906000915b84831061388857505050508101388080613856565b805487840152869550918301918101613873565b60ff191686525050508015150282019050388080613856565b90816020910312610cfc575160ff81168103610cfc5790565b604d811161373357600a0a90565b8181029291811591840414171561373357565b81156138f9570490565b634e487b7160e01b600052601260045260246000fd5b60ff6001199116019060ff821161373357565b60ff16604d811161373357600a0a90565b9061393d82613d1f565b15613aa6576001600160a01b03918216600081815260076020526040808220905190949261396a82613268565b83865416825261ffff600585600189015416976020850198895260028101546040860152600381015460608601526004810154608086015201541660a08301528015600014613a3f57506012925b83955116156000146139e4575050906139db6139d66139e19361390f565b613922565b906138ef565b91565b916139f060ff93613f2c565b93909316916001198301928311613a2b575091613a20613a1b613a269360ff6139e1979616906137ca565b6138ce565b926138dc565b6138ef565b634e487b7160e01b81526011600452602490fd5b60206004916040519283809263313ce56760e01b82525afa908115613a9b578391613a6c575b50926139b8565b613a8e915060203d602011613a94575b613a8681836132e7565b8101906138b5565b38613a65565b503d613a7c565b6040513d85823e3d90fd5b60405163ee84f40b60e01b81526001600160a01b0383166004820152602490fd5b613ad081613d1f565b15613126576001600160a01b039081166000818152600760205260408082209051909492613afd82613268565b84865416825261ffff600586600189015416976020850198895260028101546040860152600381015460608601526004810154608086015201541660a08301528015600014613ba557506012935b8495511615600014613b6e575050613b686139d66139e19361390f565b906138dc565b92613b7a60ff94613f2c565b94909416916001198301928311613a2b575091613b68613a1b613a269360ff6139e1979616906137ca565b60206004916040519283809263313ce56760e01b82525afa908115613a9b578391613bd2575b5093613b4b565b613beb915060203d602011613a9457613a8681836132e7565b38613bcb565b90601f8111613bff57505050565b6000916000526020600020906020601f850160051c83019410613c3d575b601f0160051c01915b828110613c3257505050565b818155600101613c26565b9092508290613c1d565b93613c656080969998979492613c739460a0885260a088019161380b565b91858303602087015261380b565b60408301969096526001600160a01b031660608201520152565b7f5d26a7f4630b011524e20c7a46022d61d8ba8ea0e861887303530b0f3362609781526001600160a01b03918216602082015291811660408301529091166060820152608081019190915260a00190565b602061358e919392936040519481613cff879351809286808701910161335a565b8201613d138251809386808501910161335a565b010380855201836132e7565b60018060a01b038091166000526007602052604060002090604051613d4381613268565b60a061ffff60058486541694858552600187015416958660208601526002810154604086015260038101546060860152600481015460808601520154169101521590811591613d90575090565b9050151590565b6000546001600160a01b03163303613dab57565b606460405162461bcd60e51b815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b919060005b8351811015613e2b576001600160a01b0380613e1083876137b6565b511690831614613e2257600101613df4565b92505060019190565b50509050600090600090565b9061ffff8092166127100191821161373357565b3d15613e76573d90613e5c82613308565b91613e6a60405193846132e7565b82523d6000602084013e565b606090565b613e88600182015461337d565b15611d655760028101546003820191613ea383548093613726565b8411613f03577f6780b8ab4bdf8711431f9c15f7ad7c23c380b76753be71377acf881c64fcba6b92613ed885613ef8946137ca565b9055613ee38161382c565b936040519283926040845260408401906133b7565b9060208301520390a2565b604051636b2f218360e01b8152600490fd5b519069ffffffffffffffffffff82168203610cfc57565b602081015160408051633fabe5a360e21b81529391600491906001600160a01b031660a0868481845afa908115614080576000968792614029575b508660008113156140135760608701518112614013576080870151811361401357506020849184519283809263313ce56760e01b82525afa90811561400857908391600091613fe9575b509501518015159182613fd6575b5050613fc9575050565b51630cd5fa0760e11b8152fd5b613fe092506137ca565b42113880613fbf565b614002915060203d602011613a9457613a8681836132e7565b38613fb1565b83513d6000823e3d90fd5b846024918551916338ee04a760e01b8352820152fd5b9690915060a0873d60a011614078575b8161404660a093836132e7565b81010312611740575061405886613f15565b50602086015161406f608060608901519801613f15565b50959038613f67565b3d9150614039565b82513d6000823e3d90fd5b9093919293614098613513565b9283516020809501206140a9613472565b85815191012096604096875198878a01937fd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac564728552898b015260608a01524660808a01523060a08a015260c089015260c0885260e08801978089106001600160401b038a1117610d4a578861415d95610122614157956141659c8c52845190209361010081019461190160f01b865261010282015201526042815261414c81613296565b519020923691613323565b90614435565b949094614469565b8251938481846005549788815201600097600589527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db090895b88828210614214575050505090826108986141c8946141be9403826132e7565b509161089861362a565b509015908161420b575b506141fa576001600160a01b0316835260039052812080546001810192908310613a2b575055565b8251638baa579f60e01b8152600490fd5b905015386141d2565b83546001600160a01b03168552879550909301926001928301920161419e565b929391926001600160a01b03918083166142b25750169261425591906137ca565b823b15610cfc57604051631520322360e01b81526001600160a01b0390921660048301526024820152906000908290818381604481015b03925af180156142a65761429d5750565b61358e90613283565b6040513d6000823e3d90fd5b93949116916142c0916137ca565b90803b15610cfc57604051634e14d37160e01b81526001600160a01b039384166004820152939092166024840152604483015260009082908183816064810161428c565b60405163a9059cbb60e01b60208201526001600160a01b0392909216602483015260448083019390935291815261358e916143406064836132e7565b60018060a01b03169061439f604051614358816132cc565b6020938482527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564858301526000808587829751910182855af1614399613e4b565b91614612565b805191821591848315614411575b5050509050156143ba5750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152fd5b9193818094500103126117f0578201519081151582036117405750803880846143ad565b90604181511460001461445f57613264916020820151906060604084015193015160001a90614583565b5050600090600290565b600581101561456d578061447a5750565b600181036144c75760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e617475726500000000000000006044820152606490fd5b600281036145145760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606490fd5b60031461451d57565b60405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608490fd5b634e487b7160e01b600052602160045260246000fd5b9291907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a083116146065791608094939160ff602094604051948552168484015260408301526060820152600093849182805260015afa156145f95781516001600160a01b038116156145f3579190565b50600190565b50604051903d90823e3d90fd5b50505050600090600390565b919290156146745750815115614626575090565b3b1561462f5790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b8251909150156146875750805190602001fd5b60405162461bcd60e51b8152602060048201529081906146ab90602483019061344d565b0390fdfe342e7ff505a8a0364cd0dc2ff195c315e43bce86b204846ecd36913e117b109ea2646970667358221220af3ed99a170e2c3351404f84427375e982c6ef8496af4adb5266df3d2a38360a64736f6c6343000818003300000000000000000000000021ebc2f23a91fd7eb8406cdce2fd653de280b5fc000000000000000000000000cbd8eae56e5d51c82e9b901ff535906077c5ab32000000000000000000000000b92949bdf09f4193599ae7700211751ab5f74acd000000000000000000000000ae51564961886aee70941d69a4f5968c96007200

Deployed Bytecode

0x61044080604052600436101561001e575b50361561001c57600080fd5b005b600061040052610400513560e01c90816305c6eb6e14613148575080631c46bc6f14612fce5780631f5a0bbe14612f1e5780632026583014612817578063213bc218146127da57806324788429146126be57806324969d76146123f55780632ad273e0146123d05780632e6dfcd814612396578063348011ae14611d775780634312642c14611afe5780634517c99414611a9257806346518700146119a35780634b268241146114335780635cc333211461141557806361d027b3146113ea57806370ae92d2146113ac578063715018a61461137f578063763f323d146112b25780637892ca1414610ee75780637f51bb1f14610e6b5780638247a97c14610dba5780638da5cb5b14610d8d5780639be918e614610d60578063a6f0b38e146109cf578063ae61c5ae1461085f578063b727b57914610822578063bd81579e146107a5578063cd61aec7146104d1578063cdf1613914610323578063f2fde38b14610251578063f97a05df14610228578063fd070296146101f25763ff060fbd146101a95738610010565b346101eb5760203660031901126101eb576004356004548110156101eb576101d26020916134dc565b905460405160039290921b1c6001600160a01b03168152f35b6104005180fd5b346101eb57610400513660031901126101eb57610224610210613513565b60405191829160208352602083019061344d565b0390f35b346101eb5760203660031901126101eb576004356005548110156101eb576101d260209161348f565b346101eb5760203660031901126101eb5761026a613183565b610272613d97565b6001600160a01b039081169081156102cf576104005154826001600160601b0360a01b8216176104005155167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e06104005161040051a36104005180f35b60405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608490fd5b346101eb5760c03660031901126101eb5761033c613183565b6103446131af565b90604435606435926084359260a43561ffff94858216968783036101eb5761036a613d97565b61037384613d1f565b6104bf576001600160a01b03938416978815806104b5575b6104a3576127108111610491577fa4e0e512ee3c2f782d86871b7ed121041119b7db37de7962053d865d8eb2d2df976005610487968b604051916103ce83613268565b81835260208301818c16815260408401908d8252606085019289845260808601948b865260a08701998a52610400515260076020528060406104005120965116906001600160601b0360a01b918288541617875560018701925116908254161790555160028401555160038301555160048201550191511661ffff19825416179055604051958695869360809361ffff939796929760a087019860018060a01b0316875260208701526040860152606085015216910152565b0390a26104005180f35b604051638bff87cf60e01b8152600490fd5b60405163d92e233d60e01b8152600490fd5b508486161561038b565b6040516347d3451560e01b8152600490fd5b6104da36613222565b600260019493929454146107605760026001556104f683613d1f565b1561073f5760018060a01b039384841691826104005152600760205261271061052d61ffff600560406104005120015416866138dc565b046105428561053c83826137ca565b97613933565b96909785156000146106b7578134106106a5576104005160025490918291829182918691165af1610571613e4b565b5015610693576105819034613726565b806105fe575b509060ff7f0932fd3714db79d275b23965abc0db663088581d46e84624450d865031d9f5279661048794935b6001805560405184868237808581016000815203902099604051988998338a5260208a01526040890152606088015260808701521660a085015260e060c085015260e084019161380b565b610400519193929180808084335af1610615613e4b565b5015610693577f0932fd3714db79d275b23965abc0db663088581d46e84624450d865031d9f527966104879460ff927f5e8ab9aefcc768e9e44526b6fa078340387a33e72ad445b98cd87434af93a336604051806106858633958390929160ff6020916040840195845216910152565b0390a2929394509650610587565b604051636a51e8d560e11b8152600490fd5b60405163617ab12d60e11b8152600490fd5b6040516323b872dd60e01b6020820152336024820152306044820152606481018390527f0932fd3714db79d275b23965abc0db663088581d46e84624450d865031d9f5279861048796959460ff949193909261073a9291906107309061072a81608481015b03601f1981018352826132e7565b8b614340565b6002541689614304565b6105b3565b60405163ee84f40b60e01b81526001600160a01b0384166004820152602490fd5b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b346101eb5760203660031901126101eb5760c06001600160a01b03806107c9613183565b166104005152600760205260406104005120818154169160018201541690600281015460038201549061ffff600560048501549401541693604051958652602086015260408501526060840152608083015260a0820152f35b346101eb57610400513660031901126101eb5760206040517fd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac564728152f35b346101eb5760203660031901126101eb57610878613183565b610880613d97565b60045490600182146109ad5761089d8161089861362a565b613def565b92901561098c5760001990808201908111610972576108d8936108c26108fc926134dc565b90546001600160a01b03969192909187916134dc565b92909360031b1c169060018060a01b038084549260031b9316831b921b1916179055565b600454801561095c5701610927610912826134dc565b81549060018060a01b039060031b1b19169055565b600455167f215ef528757ca84646fb5c401012a31bcbf1f99487a51e57a57a0c0afa0d3dd26104005161040051a26104005180f35b634e487b7160e01b600052603160045260246000fd5b634e487b7160e01b61040051526011600452602461040051fd5b604051634b84d63b60e11b81526001600160a01b0383166004820152602490fd5b604051635ec51d0360e01b81526001600160a01b039091166004820152602490fd5b346101eb5760e03660031901126101eb576109e8613183565b6109f06131af565b6001600160401b0360a4358181116101eb57610a109036906004016131f5565b919060c4358281116101eb57610a2a9036906004016131f5565b92909360018060a01b038716610400515260036020526040610400512054926040519283602081011090602085011117610d4a5760208301604052610400518352610a7487613d1f565b15610d29576064354211610d1757610aeb610afc93604051610aa38161071c8d8d604435913360208601613c8d565b610aae36898b613323565b6020815191012091602081519101206040519288602085015260643560408501526060840152608083015260808252610ae6826132b1565b613cde565b60843590602081519101208961408b565b60025461040080516001600160a01b0387811691829052600760205291516040902060050154929091169661ffff929092169490610c8f578131935b612710610b47876044356138dc565b04610b54816044356137ca565b861015610c455750612710850292858404612710148615171561097257610bac610c3b9461ffff610ba57fef6f8329f0b365435f68fe50cb6d81a6f381cf67ed3c7568158dbb0dabd6f7f89a613e37565b16906138ef565b9460ff86610bd48b82610bca610bc28284613933565b95909d613726565b9e8f925b88614234565b6040518587823780868101600081520390209b604051998a996101209d338c5260018060a01b031660208c015260408b015260608a015260808901521660a087015260c086015260643560e08601528761010086015260018060a01b03169684019161380b565b0390a36104005180f35b977fef6f8329f0b365435f68fe50cb6d81a6f381cf67ed3c7568158dbb0dabd6f7f8965087955060443594610c3b949160ff90610bd48c89610c87818d613933565b94909c610bce565b6040516370a0823160e01b81526001600160a01b038381166004830152602090829060249082908b165afa908115610d09576104005191610cd2575b5093610b38565b90506020813d602011610d01575b81610ced602093836132e7565b81010312610cfc575188610ccb565b600080fd5b3d9150610ce0565b6040513d61040051823e3d90fd5b604051638baa579f60e01b8152600490fd5b60405163ee84f40b60e01b81526001600160a01b0388166004820152602490fd5b634e487b7160e01b600052604160045260246000fd5b346101eb5760203660031901126101eb576020610d83610d7e613183565b613d1f565b6040519015158152f35b346101eb57610400513660031901126101eb5761040051546040516001600160a01b039091168152602090f35b346101eb5760203660031901126101eb57610dd3613183565b610ddf3361089861353a565b5015610e535760018060a01b03168061040051526003602052604061040051208054600019811461097257600101905580610400515260036020527ffc69110dd11eb791755e4abd6b7d281bae236de95736d38a23782814be5e10db60206040610400512054604051908152a26104005180f35b60405163472511eb60e11b8152336004820152602490fd5b346101eb5760203660031901126101eb57610e84613183565b610e8c613d97565b6001600160a01b039081169081156104a35760207f567657fa3f286518b318f4a29870674f433f622fdfc819691acb13105b2282259160025490846001600160601b0360a01b83161760025560405191168152a26104005180f35b346101eb576101003660031901126101eb57610f01613183565b610f096131af565b6001600160401b0360a4358181116101eb57610f299036906004016131f5565b92909360c4358381116101eb57610f449036906004016131f5565b94909360e4359081116101eb57610f5f9036906004016131f5565b94909160018060a01b03841661040051526003602052604061040051205497604051878582376020818981016006815203019020976001610fb18a610faa60405180948193016133b7565b03826132e7565b60208151910120610fc3368387613323565b60208151910120036112a057610fda368987613323565b610fe388613d1f565b1561127f576064354211610d1757604051611075956110649361102c9190611025846110176044358f8f3360208601613c8d565b03601f1981018652856132e7565b3691613323565b602081519101209160208151910120604051928d602085015260643560408501526060840152608083015260808252610ae6826132b1565b60843590602081519101208661408b565b60025461040080516001600160a01b038681169182905260076020529151604090206005015461ffff169791909216939161120a578131945b6127106110bd896044356138dc565b046110ca816044356137ca565b8710156111e9575061271086029786890461271014871517156109725761111f9861ffff610ba56110fa93613e37565b988998899661111461110c8986613933565b9c909a613726565b9b8c915b8588614234565b6111298784613e7b565b6111356001840161382c565b9860405189878237808a8101600081520390209a60405198899861014095338b52600160a01b600190031660208b015260408a01526060890152608088015260ff1660a087015260c086015260643560e086015280610100860152840190600101906111a0916133b7565b8381036101208501526001600160a01b03909116946111be9261380b565b037fabd54055439057097b39d314db5c38dc4b70c245934d3552fa3038a29be3674d91a46104005180f35b986044359850949550879461111f8a6112028885613933565b9b9099611118565b6040516370a0823160e01b81526001600160a01b038381166004830152602090829060249082908a165afa908115610d0957610400519161124d575b50946110ae565b90506020813d602011611277575b81611268602093836132e7565b81010312610cfc575189611246565b3d915061125b565b60405163ee84f40b60e01b81526001600160a01b0389166004820152602490fd5b604051631aae564160e01b8152600490fd5b346101eb5760203660031901126101eb576112cb613183565b6112d3613d97565b6001600160a01b0381169081156104a3576112f0816108986135b3565b506113675760045490600160401b821015610d4a5761131882600161133694016004556134dc565b90919060018060a01b038084549260031b9316831b921b1916179055565b7f466d40d3c5afb07f32aa87fa68d587f144c6fee2dfb883b3363937d5427e04426104005161040051a26104005180f35b60405162c40a5760e21b815260048101839052602490fd5b346101eb57610400513660031901126101eb5761139a613d97565b60405163796d957f60e01b8152600490fd5b346101eb5760203660031901126101eb576001600160a01b036113cd613183565b166104005152600360205260206040610400512054604051908152f35b346101eb57610400513660031901126101eb576002546040516001600160a01b039091168152602090f35b346101eb57610400513660031901126101eb57610224610210613472565b346101eb576101403660031901126101eb5761144d613183565b6114556131af565b61145d613199565b9060c4356001600160401b0381116101eb5761147d9036906004016131f5565b60e4356001600160401b0381116101eb5761149c9036906004016131c5565b92610104356001600160401b0381116101eb576114bd9036906004016131c5565b91909361012435151561012435036101eb576084354211610d1757611598916115876040517f947f5e3b95089db2a80a00e383947991b50e131febda16f519522edd0af91cc8602082015233604082015260018060a01b038c16606082015260018060a01b038a16608082015260443560a082015260a0815261153f81613268565b60018060a01b038c16610400515260036020526040610400512054906040519160018060a01b038d1660208401526040830152608435606083015260608252610ae682613296565b60a43590602081519101208b61408b565b6001600160a01b0387163b156101eb57604051638e0a39f560e01b81523360048201526001600160a01b03868116602483015260448035908301528716606482015260c0608482015260c48101859052936001600160fb1b0381116101eb5760051b9291839060e486013783830184810360e00160a486015260e4810182905261040051600583901b8201610104908101959201929184905b838310611942575050505050508180610400519203816104005160018060a01b0389165af18015610d0957611933575b506000926001600160a01b0382166117f457610124351561174357611688846044356137ca565b846001600160a01b0383163b1561174057604051631520322360e01b81526001600160a01b038616600482015260248101929092528180604481010381836001600160a01b0387165af180156117355761171d575b506000805160206146b08339815191529293505b604080516001600160a01b03938416815294831660208601526044359085015216918060608101610487565b6117278591613283565b61173157836116dd565b8380fd5b6040513d87823e3d90fd5b80fd5b9192611751816044356137ca565b6001600160a01b0384163b156117f057604051631520322360e01b815230600482015260248101919091528181604481836001600160a01b0389165af180156117e5579082916117d1575b5080806117ab816044356137ca565b875af16117b6613e4b565b5015610693576000805160206146b0833981519152916116f1565b6117da90613283565b61174057808561179c565b6040513d84823e3d90fd5b5080fd5b610124351561189257611809846044356137ca565b846001600160a01b0383163b1561174057604051634e14d37160e01b81526001600160a01b0380861660048301528616602482015260448101929092528180606481010381836001600160a01b0387165af180156117355761187e575b506000805160206146b08339815191529293506116f1565b6118888591613283565b6117315783611866565b6001600160a01b0381163b1561173157604051634e14d37160e01b81526001600160a01b03831660048201523060248201526044803590820152848180606481010381836001600160a01b0387165af18015611735576000805160206146b0833981519152949561191f9261190f92611924575b506044356137ca565b856001600160a01b038516614304565b6116f1565b61192d90613283565b87611906565b61193c90613283565b83611661565b9091929394956101031982890182030186528635601e19843603018112156101eb57830190602082359201916001600160401b0381116101eb5780360383136101eb57611995602092839260019561380b565b980196019493019190611631565b346101eb5760203660031901126101eb576004356001600160401b0381116101eb57366023820112156101eb576119ff60206119ec611a6b933690602481600401359101613323565b816040519382858094519384920161335a565b8101600681520301902060405190611a2282611a1b81846133b7565b03836132e7565b604051611a3681610faa81600186016133b7565b6002820154916003810154611a79600560018060a01b036004850154169301549360405197889760c0895260c089019061344d565b90878203602089015261344d565b9360408601526060850152608084015260a08301520390f35b346101eb5760403660031901126101eb576004356001600160401b0381116101eb57611ac29036906004016131f5565b90611acf3361089861353a565b5015610e535781611af792604051928337810190600682526020816024359303019020613e7b565b6104005180f35b346101eb5760a03660031901126101eb576001600160401b036004358181116101eb57611b2f9036906004016131f5565b6024358381116101eb57611b479036906004016131f5565b90611b50613199565b92611b5d336108986135b3565b5015610e535760405181868237602081838101600681520301902095600187015490611b888261337d565b15611d6557600388015460443510611d53576001600160a01b038616156104a3578411611d395783611bbc611bc59261337d565b60018901613bf1565b6104005195601f8411600114611c9e5790610c3b91611c2185807f34974c232c070c964b4e7ff79c7d9009ed9b6a4029aa166f60d2311d52fb749998999a6104005191611c93575b508160011b916000199060031b1c19161790565b60018201555b60443560028201556004810180546001600160a01b0319166001600160a01b038916179055608435600590910155604051818882378082810161040051815203902095604051858582378086810161040051815203902097604051958695608435946044359388613c47565b90508601358b611c0d565b60018101610400515260206104005120610400515b601f1986168110611d21575090610c3b92917f34974c232c070c964b4e7ff79c7d9009ed9b6a4029aa166f60d2311d52fb749996979886601f19811610611d07575b5050600185811b016001820155611c27565b850135600019600388901b60f8161c191690558880611cf5565b84890135825560209889019860019092019101611cb3565b634e487b7160e01b61040051526041600452602461040051fd5b6040516348e726ad60e01b8152600490fd5b60405163d69134e160e01b8152600490fd5b346101eb5760a03660031901126101eb576004356001600160401b0381116101eb57611da79036906004016131c5565b61018052610100526024356001600160401b0381116101eb57611dce9036906004016131c5565b60a0526080526044356001600160401b0381116101eb57611df39036906004016131c5565b6101e052610140526064356001600160401b0381116101eb57611e1a9036906004016131c5565b60c052610120526084356001600160401b0381116101eb57611e409036906004016131c5565b60e05261016052611e53336108986135b3565b5015610e53576101805160a05114801590612387575b8015612379575b801561236b575b61235957610400516101c0525b60a0516101c05110611e97576104005180f35b611ea96101c05160a0516080516136e5565b90501561234757611ec46101c0516101e05161014051613792565b3515612335576101c05160c051610120516001600160a01b0392611ef092611eeb92613792565b6137a2565b16156104a35760026020611f0e6101c05161018051610100516136e5565b919082604051938492833781016006815203019020015461232357611f3d6101c05161018051610100516136e5565b90611fb9611f536101c05160a0516080516136e5565b9190611f696101c0516101e05161014051613792565b3592611fad611f84611eeb6101c05160c05161012051613792565b95611f986101c05160e05161016051613792565b35976040516101a0526110256101a051613268565b6101a051523691613323565b60206101a051015260406101a05101526104005160606101a051015260018060a01b031660806101a051015260a06101a051015260206120036101c05161018051610100516136e5565b9190826040519384928337810160068152030190206101a051518051906001600160401b038211611d39576120428261203c855461337d565b85613bf1565b6020906001601f8411146122b657918091612076936104005192612233575b50508160011b916000199060031b1c19161790565b81555b60206101a05101518051906001600160401b038211611d39576120ac826120a3600186015461337d565b60018601613bf1565b6020906001601f84111461223e579180916120df9361040051926122335750508160011b916000199060031b1c19161790565b60018201555b60406101a0510151600282015560606101a051015160038201556004810160018060a01b0360806101a0510151166001600160601b0360a01b825416179055600560a06101a05101519101556121456101c05161018051610100516136e5565b7f81b3d17879f50f75656c1abbaa17b9e3f8476269565cf5e324f6ce031694328c61217a6101c05161018051610100516136e5565b906122206121906101c05160a0516080516136e5565b9690956121a56101c05160a0516080516136e5565b6121bc6101c09a929a516101e05161014051613792565b35916121d4611eeb6101c05160c05161012051613792565b936121e86101c05160e05161016051613792565b359581604051928392833781016104005181520390209981604051928392833781016104005181520390209960405197889788613c47565b0390a360016101c051016101c052611e84565b015190508480612061565b9190600184016104005152602061040051209061040051935b601f198416851061229b576001945083601f19811610612282575b505050811b0160018201556120e5565b015160001960f88460031b161c19169055838080612272565b81810151835560209485019460019093019290910190612257565b908361040051526020610400512091610400515b601f198516811061230b575083601f198116106122f2575b505050600190811b018155612079565b015160001960f88460031b161c191690558280806122e2565b919260206001819286850151815501940192016122ca565b604051630226ac6f60e31b8152600490fd5b604051637f7d194160e11b8152600490fd5b604051632a982bb360e01b8152600490fd5b6040516313cab10f60e11b8152600490fd5b5060e05160a0511415611e77565b5060c05160a0511415611e70565b506101e05160a0511415611e69565b346101eb5760403660031901126101eb576123bb6123b2613183565b60243590613ac7565b6040805192835260ff91909116602083015290f35b346101eb5760403660031901126101eb576123bb6123ec613183565b60243590613933565b6123fe36613222565b9092916001916002835414610760576002835561241a84613d1f565b1561269d5760018060a01b0394858516918261040051526020946007865261271061245361ffff600560406104005120015416876138dc565b0496612469866124638a826137ca565b92613933565b9190998615600014612619578134106106a5576104005160025490918291829182918691165af1612498613e4b565b5015610693576124a89034613726565b8061258b575b50610c3b9392917fd0b1fc3e37b30b21f09bb6584ae8eaf906e539f6ce3298a9bd78b87d374b27ed9760ff61257d935b8380556124fe8d604051888a823785818a81016006815203019020613e7b565b61253384604051888a823785818a810160068152030190200194604051888a823785818a81016006815203019020019461382c565b9b60405187898237808881016104005181520390209d6040519b8c9b610100968d3390528d015260408c015260608b015260808a01521660a08801528060c08801528601906133b7565b9184830360e086015261380b565b61040051909493929080808088335af16125a3613e4b565b5015610693576040805195865260ff83811660208801527fd0b1fc3e37b30b21f09bb6584ae8eaf906e539f6ce3298a9bd78b87d374b27ed99610c3b9761257d95919333917f5e8ab9aefcc768e9e44526b6fa078340387a33e72ad445b98cd87434af93a3369190a293505097509192936124ae565b6040516323b872dd60e01b818b0152336024820152306044820152606481018390527fd0b1fc3e37b30b21f09bb6584ae8eaf906e539f6ce3298a9bd78b87d374b27ed99610c3b97969561257d959094919360ff9391926126989290919061268e90612688816084810161071c565b8d614340565b600254168b614304565b6124de565b60405163ee84f40b60e01b81526001600160a01b0385166004820152602490fd5b346101eb5760203660031901126101eb576126d7613183565b6126df613d97565b60055490600182146127b8576126f78161089861353a565b9290156127975760001990808201908111610972576108d89361271c6127329261348f565b90546001600160a01b039691929091879161348f565b600554801561277d57016127486109128261348f565b600555167f4a2cf608bfb427f53279ec7f0eadf48913b9346ccefc3af138dbdec14ea0907d6104005161040051a26104005180f35b634e487b7160e01b61040051526031600452602461040051fd5b6040516302cf3fb360e51b81526001600160a01b0383166004820152602490fd5b60405163b0a503db60e01b81526001600160a01b039091166004820152602490fd5b346101eb57610400513660031901126101eb5760206040517f947f5e3b95089db2a80a00e383947991b50e131febda16f519522edd0af91cc88152f35b346101eb5760403660031901126101eb576001600160401b036004358181116101eb576128489036906004016131c5565b61022052610200526024359081116101eb576128689036906004016131c5565b610300526103e05261287c3361089861353a565b5015610e535761030051610220510361235957610400516103c0525b610220516103c051106128ac576104005180f35b6128c06103c051610300516103e0516136a1565b905015612f0c5760206128dd6103c05161022051610200516136e5565b9190826040519384928337810160068152030190206102c05260026102c05101548015611d655760056102c05101544210612efa576102c0516003015461292391613726565b6102e052610400516103405261294d6129466103c051610300516103e0516136a1565b9050613760565b610320526129686129466103c051610300516103e0516136a1565b610380526129ac6129836103c051610300516103e0516136a1565b90506129a161299182613749565b60405161042052610420516132e7565b806104205152613749565b601f19013660206104205101376129d06129466103c051610300516103e0516136a1565b61036052610400516103a0525b6129f16103c051610300516103e0516136a1565b90506103a0511015612ef5576102e051610340511015612cc957612a32610d7e611eeb612a286103c051610300516103e0516136a1565b6103a05191613792565b15612c8e576102c051600401546001600160a01b0316610280526103c051610300516103e051612a6992611eeb92612a28926136a1565b6102a052612a7d610340516102e051613726565b60018060a01b036002541690611eeb612ab9612aa36103c051610300516103e0516136a1565b6103a0516001600160a01b039492909190613792565b166104005152600760205261ffff600560406104005120015416916104005150610400515061040051506104005150612af5826102a051613ac7565b929092612710612b0586866138dc565b6102a05191900491906001600160a01b031680612c2357506102805131945b612b2e83826137ca565b861015612c10575050505081612710810204612710148215171561097257612b6961ffff612b5e612b9695613e37565b1661271084026138ef565b80938192612b85612b7d846102a051613933565b949096613726565b9384915b6102a05161028051614234565b60ff612ba86103a051610420516137b6565b91169052612bbc6103a051610360516137b6565b52612bcd6103a051610380516137b6565b52612bde6103a051610320516137b6565b52612bfc612bf26103a051610380516137b6565b51610340516137ca565b6103405260016103a051016103a0526129dd565b955091935091612b969083908690612b89565b6020602491604051928380926370a0823160e01b82526102805160048301525afa908115610d09576104005191612c5c575b5094612b24565b90506020813d602011612c86575b81612c77602093836132e7565b810103126101eb575187612c55565b3d9150612c6a565b6024612caa611eeb612a286103c051610300516103e0516136a1565b60405163ee84f40b60e01b81526001600160a01b039091166004820152fd5b612cdd6103405160036102c05101546137ca565b60036102c0510155610faa612d2560016020612d036103c05161022051610200516136e5565b91908260405193849283378101600681520301902001604051928380926133b7565b60018060a01b0360046102c05101541690612d4a6103c051610300516103e0516136a1565b612d616103c09492945161022051610200516136e5565b9093612d776103c05161022051610200516136e5565b919092604051808351612d8e81836020880161335a565b8101039020968160405192839283378101610400518152039020966040519480610100808801908852526101208601919061040051905b808210612ecb57505050612de78186612df893036020880152610320516137d7565b8581036040870152610380516137d7565b84810360608601528061026052602061042051519182815201610260526020610420510161024052610400515b818110612e9f57505083927f8ffbf5b0226477a4783d04864504c4387123a733c097c607511ad62050434a66949261257d612e8c9361034051608087015285610260510360a0870152612e7e61026051610360516137d7565b9086820360c088015261344d565b0390a460016103c051016103c052612898565b60019060ff61024051511661026051526020610260510161026052602061024051016102405201612e25565b90919283359060018060a01b0382168092036101eb57602081600193829352019401920190612dc5565b612cc9565b60405163e792cf8360e01b8152600490fd5b604051635373815f60e01b8152600490fd5b346101eb5760203660031901126101eb57612f37613183565b612f3f613d97565b6001600160a01b0381169081156104a357612f5c8161089861353a565b50612fb55760055490600160401b821015611d3957611318826001612f84940160055561348f565b7fae5b7c3b000f575c241001dc9bcb3d8778376889353b07121115574eceff78c56104005161040051a26104005180f35b604051633aec0c9d60e11b815260048101839052602490fd5b346101eb5760203660031901126101eb57612fe7613183565b612fef613d97565b612ff881613d1f565b15613126577f7dfcb1d4b5c069c3ad4d9c994ca81ed8f889cfe898eca01d3e6f50e73af6e82660018060a01b0380921691826104005152600760205260406104005120906104876040519261304c84613268565b82815416845282600182015416956020850196875260056002830154946040870195865280600385015497606081019889526004860154956080820196875261ffff9485910154169460a082019586526104005152600760205260406104005120610400518155610400516001820155610400516002820155610400516003820155610400516004820155600561040051910155511698511694519551925191511691604051958695869360809361ffff939796929760a087019860018060a01b0316875260208701526040860152606085015216910152565b60405163ee84f40b60e01b81526001600160a01b039091166004820152602490fd5b346101eb57610400513660031901126101eb57807f5d26a7f4630b011524e20c7a46022d61d8ba8ea0e861887303530b0f3362609760209252f35b600435906001600160a01b0382168203610cfc57565b606435906001600160a01b0382168203610cfc57565b602435906001600160a01b0382168203610cfc57565b9181601f84011215610cfc578235916001600160401b038311610cfc576020808501948460051b010111610cfc57565b9181601f84011215610cfc578235916001600160401b038311610cfc5760208381860195010111610cfc57565b6060600319820112610cfc576004356001600160a01b0381168103610cfc579160243591604435906001600160401b038211610cfc57613264916004016131f5565b9091565b60c081019081106001600160401b03821117610d4a57604052565b6001600160401b038111610d4a57604052565b608081019081106001600160401b03821117610d4a57604052565b60a081019081106001600160401b03821117610d4a57604052565b604081019081106001600160401b03821117610d4a57604052565b90601f801991011681019081106001600160401b03821117610d4a57604052565b6001600160401b038111610d4a57601f01601f191660200190565b92919261332f82613308565b9161333d60405193846132e7565b829481845281830111610cfc578281602093846000960137010152565b60005b83811061336d5750506000910152565b818101518382015260200161335d565b90600182811c921680156133ad575b602083101461339757565b634e487b7160e01b600052602260045260246000fd5b91607f169161338c565b8054600093926133c68261337d565b9182825260209360019160018116908160001461342e57506001146133ed575b5050505050565b90939495506000929192528360002092846000945b83861061341a575050505001019038808080806133e6565b805485870183015294019385908201613402565b60ff19168685015250505090151560051b0101915038808080806133e6565b906020916134668151809281855285808601910161335a565b601f01601f1916010190565b6040519061347f826132cc565b60018252601960f91b6020830152565b6005548110156134c65760056000527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00190600090565b634e487b7160e01b600052603260045260246000fd5b6004548110156134c65760046000527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b0190600090565b60405190613520826132cc565b600b82526a21b7b7b93234b730ba37b960a91b6020830152565b6040519060055480835282602091602082019060056000527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0936000905b8282106135905750505061358e925003836132e7565b565b85546001600160a01b031684526001958601958895509381019390910190613578565b6040519060045480835282602091602082019060046000527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b936000905b8282106136075750505061358e925003836132e7565b85546001600160a01b0316845260019586019588955093810193909101906135f1565b6040519060045480835282602091602082019060046000527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b936000905b82821061367e5750505061358e925003836132e7565b85546001600160a01b031684526001958601958895509381019390910190613668565b91908110156134c65760051b81013590601e1981360301821215610cfc5701908135916001600160401b038311610cfc576020018260051b36038113610cfc579190565b91908110156134c65760051b81013590601e1981360301821215610cfc5701908135916001600160401b038311610cfc576020018236038113610cfc579190565b9190820391821161373357565b634e487b7160e01b600052601160045260246000fd5b6001600160401b038111610d4a5760051b60200190565b9061376a82613749565b61377760405191826132e7565b8281528092613788601f1991613749565b0190602036910137565b91908110156134c65760051b0190565b356001600160a01b0381168103610cfc5790565b80518210156134c65760209160051b010190565b9190820180921161373357565b90815180825260208080930193019160005b8281106137f7575050505090565b8351855293810193928101926001016137e9565b908060209392818452848401376000828201840152601f01601f1916010190565b6040518091600090805461383f8161337d565b9160019180831690811561389c575060011461385f575b50505003902090565b909192506000526020906020600020906000915b84831061388857505050508101388080613856565b805487840152869550918301918101613873565b60ff191686525050508015150282019050388080613856565b90816020910312610cfc575160ff81168103610cfc5790565b604d811161373357600a0a90565b8181029291811591840414171561373357565b81156138f9570490565b634e487b7160e01b600052601260045260246000fd5b60ff6001199116019060ff821161373357565b60ff16604d811161373357600a0a90565b9061393d82613d1f565b15613aa6576001600160a01b03918216600081815260076020526040808220905190949261396a82613268565b83865416825261ffff600585600189015416976020850198895260028101546040860152600381015460608601526004810154608086015201541660a08301528015600014613a3f57506012925b83955116156000146139e4575050906139db6139d66139e19361390f565b613922565b906138ef565b91565b916139f060ff93613f2c565b93909316916001198301928311613a2b575091613a20613a1b613a269360ff6139e1979616906137ca565b6138ce565b926138dc565b6138ef565b634e487b7160e01b81526011600452602490fd5b60206004916040519283809263313ce56760e01b82525afa908115613a9b578391613a6c575b50926139b8565b613a8e915060203d602011613a94575b613a8681836132e7565b8101906138b5565b38613a65565b503d613a7c565b6040513d85823e3d90fd5b60405163ee84f40b60e01b81526001600160a01b0383166004820152602490fd5b613ad081613d1f565b15613126576001600160a01b039081166000818152600760205260408082209051909492613afd82613268565b84865416825261ffff600586600189015416976020850198895260028101546040860152600381015460608601526004810154608086015201541660a08301528015600014613ba557506012935b8495511615600014613b6e575050613b686139d66139e19361390f565b906138dc565b92613b7a60ff94613f2c565b94909416916001198301928311613a2b575091613b68613a1b613a269360ff6139e1979616906137ca565b60206004916040519283809263313ce56760e01b82525afa908115613a9b578391613bd2575b5093613b4b565b613beb915060203d602011613a9457613a8681836132e7565b38613bcb565b90601f8111613bff57505050565b6000916000526020600020906020601f850160051c83019410613c3d575b601f0160051c01915b828110613c3257505050565b818155600101613c26565b9092508290613c1d565b93613c656080969998979492613c739460a0885260a088019161380b565b91858303602087015261380b565b60408301969096526001600160a01b031660608201520152565b7f5d26a7f4630b011524e20c7a46022d61d8ba8ea0e861887303530b0f3362609781526001600160a01b03918216602082015291811660408301529091166060820152608081019190915260a00190565b602061358e919392936040519481613cff879351809286808701910161335a565b8201613d138251809386808501910161335a565b010380855201836132e7565b60018060a01b038091166000526007602052604060002090604051613d4381613268565b60a061ffff60058486541694858552600187015416958660208601526002810154604086015260038101546060860152600481015460808601520154169101521590811591613d90575090565b9050151590565b6000546001600160a01b03163303613dab57565b606460405162461bcd60e51b815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b919060005b8351811015613e2b576001600160a01b0380613e1083876137b6565b511690831614613e2257600101613df4565b92505060019190565b50509050600090600090565b9061ffff8092166127100191821161373357565b3d15613e76573d90613e5c82613308565b91613e6a60405193846132e7565b82523d6000602084013e565b606090565b613e88600182015461337d565b15611d655760028101546003820191613ea383548093613726565b8411613f03577f6780b8ab4bdf8711431f9c15f7ad7c23c380b76753be71377acf881c64fcba6b92613ed885613ef8946137ca565b9055613ee38161382c565b936040519283926040845260408401906133b7565b9060208301520390a2565b604051636b2f218360e01b8152600490fd5b519069ffffffffffffffffffff82168203610cfc57565b602081015160408051633fabe5a360e21b81529391600491906001600160a01b031660a0868481845afa908115614080576000968792614029575b508660008113156140135760608701518112614013576080870151811361401357506020849184519283809263313ce56760e01b82525afa90811561400857908391600091613fe9575b509501518015159182613fd6575b5050613fc9575050565b51630cd5fa0760e11b8152fd5b613fe092506137ca565b42113880613fbf565b614002915060203d602011613a9457613a8681836132e7565b38613fb1565b83513d6000823e3d90fd5b846024918551916338ee04a760e01b8352820152fd5b9690915060a0873d60a011614078575b8161404660a093836132e7565b81010312611740575061405886613f15565b50602086015161406f608060608901519801613f15565b50959038613f67565b3d9150614039565b82513d6000823e3d90fd5b9093919293614098613513565b9283516020809501206140a9613472565b85815191012096604096875198878a01937fd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac564728552898b015260608a01524660808a01523060a08a015260c089015260c0885260e08801978089106001600160401b038a1117610d4a578861415d95610122614157956141659c8c52845190209361010081019461190160f01b865261010282015201526042815261414c81613296565b519020923691613323565b90614435565b949094614469565b8251938481846005549788815201600097600589527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db090895b88828210614214575050505090826108986141c8946141be9403826132e7565b509161089861362a565b509015908161420b575b506141fa576001600160a01b0316835260039052812080546001810192908310613a2b575055565b8251638baa579f60e01b8152600490fd5b905015386141d2565b83546001600160a01b03168552879550909301926001928301920161419e565b929391926001600160a01b03918083166142b25750169261425591906137ca565b823b15610cfc57604051631520322360e01b81526001600160a01b0390921660048301526024820152906000908290818381604481015b03925af180156142a65761429d5750565b61358e90613283565b6040513d6000823e3d90fd5b93949116916142c0916137ca565b90803b15610cfc57604051634e14d37160e01b81526001600160a01b039384166004820152939092166024840152604483015260009082908183816064810161428c565b60405163a9059cbb60e01b60208201526001600160a01b0392909216602483015260448083019390935291815261358e916143406064836132e7565b60018060a01b03169061439f604051614358816132cc565b6020938482527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564858301526000808587829751910182855af1614399613e4b565b91614612565b805191821591848315614411575b5050509050156143ba5750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152fd5b9193818094500103126117f0578201519081151582036117405750803880846143ad565b90604181511460001461445f57613264916020820151906060604084015193015160001a90614583565b5050600090600290565b600581101561456d578061447a5750565b600181036144c75760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e617475726500000000000000006044820152606490fd5b600281036145145760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606490fd5b60031461451d57565b60405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608490fd5b634e487b7160e01b600052602160045260246000fd5b9291907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a083116146065791608094939160ff602094604051948552168484015260408301526060820152600093849182805260015afa156145f95781516001600160a01b038116156145f3579190565b50600190565b50604051903d90823e3d90fd5b50505050600090600390565b919290156146745750815115614626575090565b3b1561462f5790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b8251909150156146875750805190602001fd5b60405162461bcd60e51b8152602060048201529081906146ab90602483019061344d565b0390fdfe342e7ff505a8a0364cd0dc2ff195c315e43bce86b204846ecd36913e117b109ea2646970667358221220af3ed99a170e2c3351404f84427375e982c6ef8496af4adb5266df3d2a38360a64736f6c63430008180033

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

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

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.