Sepolia Testnet

Contract

0x3Fa6C4135696fBD99F7D55B552B860f5df770710

Overview

ETH Balance

0 ETH

More Info

Multichain Info

N/A
Transaction Hash
Method
Block
From
To
Add Provider Has...78751992025-03-10 18:32:36105 days ago1741631556IN
0x3Fa6C413...5df770710
0 ETH0.0435618300
Add Provider Has...78751982025-03-10 18:32:24105 days ago1741631544IN
0x3Fa6C413...5df770710
0 ETH0.0435618300
Add Provider Has...78751972025-03-10 18:32:12105 days ago1741631532IN
0x3Fa6C413...5df770710
0 ETH0.0435618300
Add Provider Has...78751962025-03-10 18:32:00105 days ago1741631520IN
0x3Fa6C413...5df770710
0 ETH0.0435618300
Add Provider Has...78751952025-03-10 18:31:48105 days ago1741631508IN
0x3Fa6C413...5df770710
0 ETH0.0435618300
Add Provider Has...78751942025-03-10 18:31:36105 days ago1741631496IN
0x3Fa6C413...5df770710
0 ETH0.0435618300
Add Provider Has...78751932025-03-10 18:31:24105 days ago1741631484IN
0x3Fa6C413...5df770710
0 ETH0.0435618300
Add Provider Has...78751922025-03-10 18:31:12105 days ago1741631472IN
0x3Fa6C413...5df770710
0 ETH0.0435618300
Add Provider Has...78751912025-03-10 18:31:00105 days ago1741631460IN
0x3Fa6C413...5df770710
0 ETH0.0435618300
Add Provider Has...78751902025-03-10 18:30:48105 days ago1741631448IN
0x3Fa6C413...5df770710
0 ETH0.0435618300
Remove Provider ...78751892025-03-10 18:30:36105 days ago1741631436IN
0x3Fa6C413...5df770710
0 ETH0.0169824300
Remove Provider ...78751882025-03-10 18:30:24105 days ago1741631424IN
0x3Fa6C413...5df770710
0 ETH0.0192705300
Remove Provider ...78751872025-03-10 18:30:12105 days ago1741631412IN
0x3Fa6C413...5df770710
0 ETH0.0215589300
Remove Provider ...78751862025-03-10 18:30:00105 days ago1741631400IN
0x3Fa6C413...5df770710
0 ETH0.0238473300
Remove Provider ...78751852025-03-10 18:29:48105 days ago1741631388IN
0x3Fa6C413...5df770710
0 ETH0.0261354300
Remove Provider ...78751842025-03-10 18:29:36105 days ago1741631376IN
0x3Fa6C413...5df770710
0 ETH0.0314298300
Remove Provider ...78751832025-03-10 18:29:24105 days ago1741631364IN
0x3Fa6C413...5df770710
0 ETH0.0341049300
Remove Provider ...78751822025-03-10 18:29:12105 days ago1741631352IN
0x3Fa6C413...5df770710
0 ETH0.0367803300
Remove Provider ...78751812025-03-10 18:29:00105 days ago1741631340IN
0x3Fa6C413...5df770710
0 ETH0.0394554300
Remove Provider ...78751802025-03-10 18:28:48105 days ago1741631328IN
0x3Fa6C413...5df770710
0 ETH0.0421305300
Add Provider Has...78379372025-03-05 13:24:00110 days ago1741181040IN
0x3Fa6C413...5df770710
0 ETH0.007260350
Add Provider Has...78379362025-03-05 13:23:48110 days ago1741181028IN
0x3Fa6C413...5df770710
0 ETH0.007260350
Add Provider Has...78379342025-03-05 13:23:24110 days ago1741181004IN
0x3Fa6C413...5df770710
0 ETH0.007260350
Add Provider Has...78379322025-03-05 13:23:00110 days ago1741180980IN
0x3Fa6C413...5df770710
0 ETH0.007260350
Add Provider Has...78379292025-03-05 13:22:24110 days ago1741180944IN
0x3Fa6C413...5df770710
0 ETH0.007260350
View all transactions

View more zero value Internal Transactions in Advanced View mode

Advanced mode:

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
VenmoReclaimVerifier

Compiler Version
v0.8.18+commit.87f61d96

Optimization Enabled:
Yes with 200 runs

Other Settings:
paris EvmVersion, MIT license

Contract Source Code (Solidity Standard Json-Input format)

// SPDX-License-Identifier: MIT

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

import { DateParsing } from "../lib/DateParsing.sol";
import { ClaimVerifier } from "../lib/ClaimVerifier.sol";
import { StringConversionUtils } from "../lib/StringConversionUtils.sol";
import { Bytes32ConversionUtils } from "../lib/Bytes32ConversionUtils.sol";

import { BaseReclaimPaymentVerifier } from "./BaseReclaimPaymentVerifier.sol";
import { INullifierRegistry } from "./nullifierRegistries/INullifierRegistry.sol";
import { IPaymentVerifier } from "./interfaces/IPaymentVerifier.sol";

pragma solidity ^0.8.18;

contract VenmoReclaimVerifier is IPaymentVerifier, BaseReclaimPaymentVerifier {

    using StringConversionUtils for string;
    using Bytes32ConversionUtils for bytes32;
    
    /* ============ Structs ============ */

    // Struct to hold the payment details extracted from the proof
    struct PaymentDetails {
        string amountString;
        string dateString;
        string paymentId;
        string recipientId;
        string intentHash;
        string providerHash;
    }

    /* ============ Constants ============ */
    
    uint8 internal constant MAX_EXTRACT_VALUES = 8; 
    uint8 internal constant MIN_WITNESS_SIGNATURE_REQUIRED = 1;

    /* ============ Constructor ============ */
    constructor(
        address _escrow,
        INullifierRegistry _nullifierRegistry,
        uint256 _timestampBuffer,
        bytes32[] memory _currencies,
        string[] memory _providerHashes
    )   
        BaseReclaimPaymentVerifier(
            _escrow, 
            _nullifierRegistry, 
            _timestampBuffer, 
            _currencies,
            _providerHashes
        )
    { }

    /* ============ External Functions ============ */

    /**
     * ONLY RAMP: Verifies a reclaim proof of an offchain Venmo payment. Ensures the right _intentAmount * _conversionRate
     * USD was paid to _payeeDetails after _intentTimestamp + timestampBuffer on Venmo.
     * Note: For Venmo fiat currency is always USD. For other verifiers which support multiple currencies,
     * _fiatCurrency needs to be checked against the fiat currency in the proof.
     *
     * @param _verifyPaymentData Payment proof and intent details required for verification
     */
    function verifyPayment(
        IPaymentVerifier.VerifyPaymentData calldata _verifyPaymentData
    )
        external 
        override
        returns (bool, bytes32)
    {
        require(msg.sender == escrow, "Only escrow can call");

        (
            PaymentDetails memory paymentDetails, 
            bool isAppclipProof
        ) = _verifyProofAndExtractValues(_verifyPaymentData.paymentProof, _verifyPaymentData.data);
                
        _verifyPaymentDetails(
            paymentDetails, 
            _verifyPaymentData,
            isAppclipProof
        );

        // Nullify the payment
        bytes32 nullifier = keccak256(abi.encodePacked(paymentDetails.paymentId));
        _validateAndAddNullifier(nullifier);

        bytes32 intentHash = bytes32(paymentDetails.intentHash.stringToUint(0));
        
        return (true, intentHash);
    }

    /* ============ Internal Functions ============ */

    /**
     * Verifies the proof and extracts the public values from the proof and _depositData.
     *
     * @param _proof The proof to verify.
     * @param _depositData The deposit data to extract the verification data from.
     */
    function _verifyProofAndExtractValues(bytes calldata _proof, bytes calldata _depositData) 
        internal
        view
        returns (PaymentDetails memory paymentDetails, bool isAppclipProof) 
    {
        // Decode proof
        ReclaimProof memory proof = abi.decode(_proof, (ReclaimProof));

        // Extract verification data
        address[] memory witnesses = _decodeDepositData(_depositData);

        verifyProofSignatures(proof, witnesses, MIN_WITNESS_SIGNATURE_REQUIRED);     // claim must have at least 1 signature from witnesses
        
        // Extract public values
        paymentDetails = _extractValues(proof);

        // Check provider hash (Required for Reclaim proofs)
        require(_validateProviderHash(paymentDetails.providerHash), "No valid providerHash");

        isAppclipProof = proof.isAppclipProof;
    }

    /**
     * Verifies the right _intentAmount * _conversionRate is paid to _payeeDetailsHash after 
     * _intentTimestamp + timestampBuffer on Venmo. Reverts if any of the conditions are not met.
     */
    function _verifyPaymentDetails(
        PaymentDetails memory paymentDetails,
        VerifyPaymentData memory _verifyPaymentData,
        bool _isAppclipProof
    ) internal view {
        uint256 expectedAmount = _verifyPaymentData.intentAmount * _verifyPaymentData.conversionRate / PRECISE_UNIT;
        uint8 decimals = IERC20Metadata(_verifyPaymentData.depositToken).decimals();

        // Validate amount
        uint256 paymentAmount = paymentDetails.amountString.stringToUint(decimals);
        require(paymentAmount >= expectedAmount, "Incorrect payment amount");
        
        // Validate recipient
        if (_isAppclipProof) {
            bytes32 hashedRecipientId = keccak256(abi.encodePacked(paymentDetails.recipientId));
            require(
                hashedRecipientId.toHexString().stringComparison(_verifyPaymentData.payeeDetails), 
                "Incorrect payment recipient"
            );
        } else {
            require(
                paymentDetails.recipientId.stringComparison(_verifyPaymentData.payeeDetails), 
                "Incorrect payment recipient"
            );
        }

        // Validate timestamp; add in buffer to build flexibility for L2 timestamps
        uint256 paymentTimestamp = DateParsing._dateStringToTimestamp(paymentDetails.dateString) + timestampBuffer;
        require(paymentTimestamp >= _verifyPaymentData.intentTimestamp, "Incorrect payment timestamp");
    }

    /**
     * Extracts the verification data from the data. In case of a Reclaim/TLSN/ZK proof, data contains the witnesses' addresses.
     * In case of a zkEmail proof, data contains the DKIM key hash. Can also contain additional data like currency code, etc.
     *
     * @param _data The data to extract the verification data from.
     */
    function _decodeDepositData(bytes calldata _data) internal pure returns (address[] memory witnesses) {
        witnesses = abi.decode(_data, (address[]));
    }

    /**
     * Extracts all values from the proof context.
     *
     * @param _proof The proof containing the context to extract values from.
     */
    function _extractValues(ReclaimProof memory _proof) internal pure returns (PaymentDetails memory paymentDetails) {
        string[] memory values = ClaimVerifier.extractAllFromContext(
            _proof.claimInfo.context, 
            MAX_EXTRACT_VALUES, 
            true
        );

        return PaymentDetails({
            // values[0] is ContextAddress
            intentHash: values[1],
            // values[2] is SENDER_ID
            amountString: values[3],
            dateString: values[4],
            paymentId: values[5],
            recipientId: values[6],
            providerHash: values[7]
        });
    }
}

// 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) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

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

pragma solidity ^0.8.0;

import "../IERC20.sol";

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

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

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (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;
    }
}

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

/*
    Copyright 2020 Set Labs Inc.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

    SPDX-License-Identifier: MIT
*/

pragma solidity ^0.8.17;

/**
 * @title AddressArrayUtils
 * @author Set Protocol
 *
 * Utility functions to handle Address Arrays
 *
 * CHANGELOG:
 * - 4/21/21: Added validatePairsWithArray methods
 */
library AddressArrayUtils {

    uint256 constant internal MAX_INT = 2**256 - 1;

    /**
     * Finds the index of the first occurrence of the given element.
     * @param A The input array to search
     * @param a The value to find
     * @return Returns (index and isIn) for the first occurrence starting from index 0
     */
    function indexOf(address[] memory A, address a) internal pure returns (uint256, bool) {
        uint256 length = A.length;
        for (uint256 i = 0; i < length; i++) {
            if (A[i] == a) {
                return (i, true);
            }
        }
        return (MAX_INT, false);
    }

    /**
    * Returns true if the value is present in the list. Uses indexOf internally.
    * @param A The input array to search
    * @param a The value to find
    * @return Returns isIn for the first occurrence starting from index 0
    */
    function contains(address[] memory A, address a) internal pure returns (bool) {
        (, bool isIn) = indexOf(A, a);
        return isIn;
    }

    /**
    * Returns true if there are 2 elements that are the same in an array
    * @param A The input array to search
    * @return Returns boolean for the first occurrence of a duplicate
    */
    function hasDuplicate(address[] memory A) internal pure returns(bool) {
        require(A.length > 0, "A is empty");

        for (uint256 i = 0; i < A.length - 1; i++) {
            address current = A[i];
            for (uint256 j = i + 1; j < A.length; j++) {
                if (current == A[j]) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @param A The input array to search
     * @param a The address to remove
     * @return Returns the array with the object removed.
     */
    function remove(address[] memory A, address a)
        internal
        pure
        returns (address[] memory)
    {
        (uint256 index, bool isIn) = indexOf(A, a);
        if (!isIn) {
            revert("Address not in array.");
        } else {
            (address[] memory _A,) = pop(A, index);
            return _A;
        }
    }

    /**
     * @param A The input array to search
     * @param a The address to remove
     */
    function removeStorage(address[] storage A, address a)
        internal
    {
        (uint256 index, bool isIn) = indexOf(A, a);
        if (!isIn) {
            revert("Address not in array.");
        } else {
            uint256 lastIndex = A.length - 1; // If the array would be empty, the previous line would throw, so no underflow here
            if (index != lastIndex) { A[index] = A[lastIndex]; }
            A.pop();
        }
    }

    /**
    * Removes specified index from array
    * @param A The input array to search
    * @param index The index to remove
    * @return Returns the new array and the removed entry
    */
    function pop(address[] memory A, uint256 index)
        internal
        pure
        returns (address[] memory, address)
    {
        uint256 length = A.length;
        require(index < A.length, "Index must be < A length");
        address[] memory newAddresses = new address[](length - 1);
        for (uint256 i = 0; i < index; i++) {
            newAddresses[i] = A[i];
        }
        for (uint256 j = index + 1; j < length; j++) {
            newAddresses[j - 1] = A[j];
        }
        return (newAddresses, A[index]);
    }
}

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

/**
 * @title Bytes32ArrayUtils
 * @author ZKP2P
 *
 * Fork of Set Protocol's AddressArrayUtils library adapted for usage with bytes32 arrays.
 */
library Bytes32ArrayUtils {

    uint256 constant internal MAX_INT = 2**256 - 1;

    /**
     * Finds the index of the first occurrence of the given element.
     * @param A The input array to search
     * @param a The value to find
     * @return Returns (index and isIn) for the first occurrence starting from index 0
     */
    function indexOf(bytes32[] memory A, bytes32 a) internal pure returns (uint256, bool) {
        uint256 length = A.length;
        for (uint256 i = 0; i < length; i++) {
            if (A[i] == a) {
                return (i, true);
            }
        }
        return (MAX_INT, false);
    }

    /**
    * Returns true if the value is present in the list. Uses indexOf internally.
    * @param A The input array to search
    * @param a The value to find
    * @return Returns isIn for the first occurrence starting from index 0
    */
    function contains(bytes32[] memory A, bytes32 a) internal pure returns (bool) {
        (, bool isIn) = indexOf(A, a);
        return isIn;
    }

    /**
    * Returns true if there are 2 elements that are the same in an array
    * @param A The input array to search
    * @return Returns boolean for the first occurrence of a duplicate
    */
    function hasDuplicate(bytes32[] memory A) internal pure returns(bool) {
        require(A.length > 0, "A is empty");

        for (uint256 i = 0; i < A.length - 1; i++) {
            bytes32 current = A[i];
            for (uint256 j = i + 1; j < A.length; j++) {
                if (current == A[j]) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @param A The input array to search
     * @param a The bytes32 to remove
     * @return Returns the array with the object removed.
     */
    function remove(bytes32[] memory A, bytes32 a)
        internal
        pure
        returns (bytes32[] memory)
    {
        (uint256 index, bool isIn) = indexOf(A, a);
        if (!isIn) {
            revert("bytes32 not in array.");
        } else {
            (bytes32[] memory _A,) = pop(A, index);
            return _A;
        }
    }

    /**
     * @param A The input array to search
     * @param a The bytes32 to remove
     */
    function removeStorage(bytes32[] storage A, bytes32 a)
        internal
    {
        (uint256 index, bool isIn) = indexOf(A, a);
        if (!isIn) {
            revert("bytes32 not in array.");
        } else {
            uint256 lastIndex = A.length - 1; // If the array would be empty, the previous line would throw, so no underflow here
            if (index != lastIndex) { A[index] = A[lastIndex]; }
            A.pop();
        }
    }

    /**
    * Removes specified index from array
    * @param A The input array to search
    * @param index The index to remove
    * @return Returns the new array and the removed entry
    */
    function pop(bytes32[] memory A, uint256 index)
        internal
        pure
        returns (bytes32[] memory, bytes32)
    {
        uint256 length = A.length;
        require(index < A.length, "Index must be < A length");
        bytes32[] memory newBytes = new bytes32[](length - 1);
        for (uint256 i = 0; i < index; i++) {
            newBytes[i] = A[i];
        }
        for (uint256 j = index + 1; j < length; j++) {
            newBytes[j - 1] = A[j];
        }
        return (newBytes, A[index]);
    }
}

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

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "./ReclaimStringUtils.sol";

// Imported from @reclaimprotocol/verifier-solidity-sdk

/**
 * Library to assist with requesting,
 * serialising & verifying credentials
 */
library Claims {
	/** Data required to describe a claim */
	struct CompleteClaimData {
		bytes32 identifier;
		address owner;
		uint32 timestampS;
		uint32 epoch;
	}

	struct ClaimInfo {
		string provider;
		string parameters;
		string context;
	}

	/** Claim with signatures & signer */
	struct SignedClaim {
		CompleteClaimData claim;
		bytes[] signatures;
	}

	/**
	 * Asserts that the claim is signed by the expected witnesses
	 */
	function assertValidSignedClaim(
		SignedClaim memory self,
		address[] memory expectedWitnessAddresses
	) internal pure {
		require(self.signatures.length > 0, "No signatures");
		address[] memory signedWitnesses = recoverSignersOfSignedClaim(self);
		for (uint256 i = 0; i < expectedWitnessAddresses.length; i++) {
			bool found = false;
			for (uint256 j = 0; j < signedWitnesses.length; j++) {
				if (signedWitnesses[j] == expectedWitnessAddresses[i]) {
					found = true;
					break;
				}
			}
			require(found, "Missing witness signature");
		}
	}

	/**
	 * @dev recovers the signer of the claim
	 */
	function recoverSignersOfSignedClaim(
		SignedClaim memory self
	) internal pure returns (address[] memory) {
		bytes memory serialised = serialise(self.claim);
		address[] memory signers = new address[](self.signatures.length);
		for (uint256 i = 0; i < self.signatures.length; i++) {
			signers[i] = verifySignature(serialised, self.signatures[i]);
		}

		return signers;
	}

	/**
	 * @dev serialises the credential into a string;
	 * the string is used to verify the signature
	 *
	 * the serialisation is the same as done by the TS library
	 */
	function serialise(
		CompleteClaimData memory self
	) internal pure returns (bytes memory) {
		return
			abi.encodePacked(
				StringUtils.bytes2str(abi.encodePacked(self.identifier)),
				"\n",
				StringUtils.address2str(self.owner),
				"\n",
				StringUtils.uint2str(self.timestampS),
				"\n",
				StringUtils.uint2str(self.epoch)
			);
	}

	/**
	 * @dev returns the address of the user that generated the signature
	 */
	function verifySignature(
		bytes memory content,
		bytes memory signature
	) internal pure returns (address signer) {
		bytes32 signedHash = keccak256(
			abi.encodePacked(
				"\x19Ethereum Signed Message:\n",
				StringUtils.uint2str(content.length),
				content
			)
		);
		return ECDSA.recover(signedHash, signature);
	}

	function hashClaimInfo(ClaimInfo memory claimInfo) internal pure returns (bytes32) {
		bytes memory serialised = abi.encodePacked(
			claimInfo.provider,
			"\n",
			claimInfo.parameters,
			"\n",
			claimInfo.context
		);
		return keccak256(serialised);
	}
}

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

// ----------------------------------------------------------------------------
// DateTime Library v2.0
//
// A gas-efficient Solidity date and time library
//
// https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary
//
// Tested date range 1970/01/01 to 2345/12/31
//
// Conventions:
// Unit      | Range         | Notes
// :-------- |:-------------:|:-----
// timestamp | >= 0          | Unix timestamp, number of seconds since 1970/01/01 00:00:00 UTC
// year      | 1970 ... 2345 |
// month     | 1 ... 12      |
// day       | 1 ... 31      |
// hour      | 0 ... 23      |
// minute    | 0 ... 59      |
// second    | 0 ... 59      |
// dayOfWeek | 1 ... 7       | 1 = Monday, ..., 7 = Sunday
//
//
// Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2018-2019. The MIT Licence.
//
// NOTE: This library has been pruned to keep only functions needed by zkp2p
// ----------------------------------------------------------------------------

library DateTime {
    uint256 constant SECONDS_PER_DAY = 24 * 60 * 60;
    uint256 constant SECONDS_PER_HOUR = 60 * 60;
    uint256 constant SECONDS_PER_MINUTE = 60;
    int256 constant OFFSET19700101 = 2440588;

    uint256 constant DOW_MON = 1;
    uint256 constant DOW_TUE = 2;
    uint256 constant DOW_WED = 3;
    uint256 constant DOW_THU = 4;
    uint256 constant DOW_FRI = 5;
    uint256 constant DOW_SAT = 6;
    uint256 constant DOW_SUN = 7;

    // ------------------------------------------------------------------------
    // Calculate the number of days from 1970/01/01 to year/month/day using
    // the date conversion algorithm from
    //   http://aa.usno.navy.mil/faq/docs/JD_Formula.php
    // and subtracting the offset 2440588 so that 1970/01/01 is day 0
    //
    // days = day
    //      - 32075
    //      + 1461 * (year + 4800 + (month - 14) / 12) / 4
    //      + 367 * (month - 2 - (month - 14) / 12 * 12) / 12
    //      - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4
    //      - offset
    // ------------------------------------------------------------------------
    function _daysFromDate(uint256 year, uint256 month, uint256 day) internal pure returns (uint256 _days) {
        require(year >= 1970);
        int256 _year = int256(year);
        int256 _month = int256(month);
        int256 _day = int256(day);

        int256 __days = _day - 32075 + (1461 * (_year + 4800 + (_month - 14) / 12)) / 4
            + (367 * (_month - 2 - ((_month - 14) / 12) * 12)) / 12
            - (3 * ((_year + 4900 + (_month - 14) / 12) / 100)) / 4 - OFFSET19700101;

        _days = uint256(__days);
    }

    function timestampFromDateTime(
        uint256 year,
        uint256 month,
        uint256 day,
        uint256 hour,
        uint256 minute,
        uint256 second
    )
        internal
        pure
        returns (uint256 timestamp)
    {
        timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + hour * SECONDS_PER_HOUR
            + minute * SECONDS_PER_MINUTE + second;
    }
}

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

// Imported from @reclaimprotocol/verifier-solidity-sdk

/**
 * Utilities for string manipulation & conversion
 */
library StringUtils {
	function address2str(address x) internal pure returns (string memory) {
		bytes memory s = new bytes(40);
		for (uint i = 0; i < 20; i++) {
			bytes1 b = bytes1(uint8(uint(uint160(x)) / (2 ** (8 * (19 - i)))));
			bytes1 hi = bytes1(uint8(b) / 16);
			bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi));
			s[2 * i] = getChar(hi);
			s[2 * i + 1] = getChar(lo);
		}
		return string(abi.encodePacked("0x", s));
	}

	function bytes2str(bytes memory buffer) internal pure returns (string memory) {
		// Fixed buffer size for hexadecimal convertion
		bytes memory converted = new bytes(buffer.length * 2);
		bytes memory _base = "0123456789abcdef";

		for (uint256 i = 0; i < buffer.length; i++) {
			converted[i * 2] = _base[uint8(buffer[i]) / _base.length];
			converted[i * 2 + 1] = _base[uint8(buffer[i]) % _base.length];
		}

		return string(abi.encodePacked("0x", converted));
	}

	function getChar(bytes1 b) internal pure returns (bytes1 c) {
		if (uint8(b) < 10) return bytes1(uint8(b) + 0x30);
		else return bytes1(uint8(b) + 0x57);
	}

	function bool2str(bool _b) internal pure returns (string memory _uintAsString) {
		if (_b) {
			return "true";
		} else {
			return "false";
		}
	}

	function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
		if (_i == 0) {
			return "0";
		}
		uint j = _i;
		uint len;
		while (j != 0) {
			len++;
			j /= 10;
		}
		bytes memory bstr = new bytes(len);
		uint k = len;
		while (_i != 0) {
			k = k - 1;
			uint8 temp = (48 + uint8(_i - (_i / 10) * 10));
			bytes1 b1 = bytes1(temp);
			bstr[k] = b1;
			_i /= 10;
		}
		return string(bstr);
	}

	function areEqual(
		string calldata _a,
		string storage _b
	) internal pure returns (bool) {
		return keccak256(abi.encodePacked((_a))) == keccak256(abi.encodePacked((_b)));
	}

	function areEqual(string memory _a, string memory _b) internal pure returns (bool) {
		return keccak256(abi.encodePacked((_a))) == keccak256(abi.encodePacked((_b)));
	}

	function toLower(string memory str) internal pure returns (string memory) {
		bytes memory bStr = bytes(str);
		bytes memory bLower = new bytes(bStr.length);
		for (uint i = 0; i < bStr.length; i++) {
			// Uppercase character...
			if ((uint8(bStr[i]) >= 65) && (uint8(bStr[i]) <= 90)) {
				// So we add 32 to make it lowercase
				bLower[i] = bytes1(uint8(bStr[i]) + 32);
			} else {
				bLower[i] = bStr[i];
			}
		}
		return string(bLower);
	}

	function substring(
		string memory str,
		uint startIndex,
		uint endIndex
	) internal pure returns (string memory) {
		bytes memory strBytes = bytes(str);
		bytes memory result = new bytes(endIndex - startIndex);
		for (uint i = startIndex; i < endIndex; i++) {
			result[i - startIndex] = strBytes[i];
		}
		return string(result);
	}
}

/*
    Copyright 2020 Set Labs Inc.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

    SPDX-License-Identifier: Apache-2.0
*/

pragma solidity ^0.8.18;

/**
 * @title StringArrayUtils
 * @author Set Protocol
 *
 * Utility functions to handle String Arrays
 */
library StringArrayUtils {

    /**
     * Finds the index of the first occurrence of the given element.
     * @param A The input string to search
     * @param a The value to find
     * @return Returns (index and isIn) for the first occurrence starting from index 0
     */
    function indexOf(string[] memory A, string memory a) internal pure returns (uint256, bool) {
        uint256 length = A.length;
        for (uint256 i = 0; i < length; i++) {
            if (keccak256(bytes(A[i])) == keccak256(bytes(a))) {
                return (i, true);
            }
        }
        return (type(uint256).max, false);
    }

    /**
     * @param A The input array to search
     * @param a The string to remove
     */
    function removeStorage(string[] storage A, string memory a)
        internal
    {
        (uint256 index, bool isIn) = indexOf(A, a);
        if (!isIn) {
            revert("String not in array.");
        } else {
            uint256 lastIndex = A.length - 1; // If the array would be empty, the previous line would throw, so no underflow here
            if (index != lastIndex) { A[index] = A[lastIndex]; }
            A.pop();
        }
    }
}

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

library Bytes32ConversionUtils {

    /// @notice Convert a bytes32 value into its hex string representation WITH '0x' prefix.
    /// @dev Resulting string is 66 characters long: 
    ///      - 2 chars for "0x" 
    ///      - 64 chars for the hex digits.
    function toHexString(bytes32 data) internal pure returns (string memory) {
        bytes memory alphabet = "0123456789abcdef";
        // 66 = 2 (for "0x") + 64 (for 32 bytes * 2 hex chars each)
        bytes memory str = new bytes(66);

        // Add '0x' prefix
        str[0] = '0';
        str[1] = 'x';

        for (uint i = 0; i < 32; i++) {
            // Each byte splits into two hex characters.
            // High nibble (4 bits)
            str[2 + 2*i]   = alphabet[uint(uint8(data[i] >> 4))];
            // Low nibble (4 bits)
            str[3 + 2*i] = alphabet[uint(uint8(data[i] & 0x0f))];
        }
        return string(str);
    }
}

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

library ClaimVerifier {
    
    /* ============ Constants ============ */

    bytes constant CONTEXT_ADDRESS_BYTES      = bytes("{\"contextAddress\":\"");
    bytes constant CONTEXT_MESSAGE_BYTES      = bytes("\"contextMessage\":\"");
    bytes constant EXTRACTED_PARAMETERS_BYTES = bytes("\"extractedParameters\":{\"");
    bytes constant PROVIDER_HASH_PARAM_BYTES  = bytes("\"providerHash\":\"");

    /* ============ Internal Functions ============ */

    /**
     * Find the end index of target string in the data string. Returns the end index + 1 if
     * the target string in the data string if found. Returns type(uint256).max if:
     * - Target is longer than data
     * - Target is not found
     * Parts of the code are adapted from: https://basescan.org/address/0x7281630e4346dd4c0b7ae3b4689c1d0102741410#code
     */
    function findSubstringEndIndex(
        string memory data,
        string memory target
    ) internal pure returns (uint256) {
        bytes memory dataBytes = bytes(data);
        bytes memory targetBytes = bytes(target);

        if (dataBytes.length < targetBytes.length) {
            return type(uint256).max;
        }

        // Find start of target
        for (uint i = 0; i <= dataBytes.length - targetBytes.length; i++) {
            bool isMatch = true;
            for (uint j = 0; j < targetBytes.length && isMatch; j++) {
                if (dataBytes[i + j] != targetBytes[j]) {
                    isMatch = false;
                    break;
                }
            }
            if (isMatch) {
                return i + targetBytes.length; // Return end index + 1
            }
        }
        return type(uint256).max;
    }

    /**
     * Extracts given target field value from context in claims. Extracts only ONE value.
     * Pass prefix formatted with quotes, for example '"providerHash\":\"'
     * Parts of the code are adapted from: https://basescan.org/address/0x7281630e4346dd4c0b7ae3b4689c1d0102741410#code
     *
     * @param data      Context string from which target value needs to be extracted
     * @param prefix    Prefix of the target value that needs to be extracted            
     */
    function extractFieldFromContext(
        string memory data,
        string memory prefix
    ) internal pure returns (string memory) {
        // Find end index of prefix; which is the start index of the value
        uint256 start = findSubstringEndIndex(data, prefix);
        bytes memory dataBytes = bytes(data);
        if (start == dataBytes.length) {
            return ""; // Prefix not found. Malformed or missing message
        }

        // Find the end of the VALUE, assuming it ends with a quote not preceded by a backslash
        uint256 end = start;
        while (
            end < dataBytes.length &&
            !(dataBytes[end] == '"' && dataBytes[end - 1] != "\\")
        ) {
            end++;
        }
        if (end <= start) {
            return ""; // Malformed or missing message
        }
        bytes memory contextMessage = new bytes(end - start);
        for (uint i = start; i < end; i++) {
            contextMessage[i - start] = dataBytes[i];
        }
        return string(contextMessage);
    }

    /**
     * Extracts ALL values from context in a single pass. Context is stored as serialized JSON string with 
     * two keys: extractedParameters and providerHash. ExtractedParameters itself is a JSON string with 
     * key-value pairs. This function returns extracted individual values from extractedParameters along 
     * with providerHash (if extractProviderHash is true). Use maxValues to limit the number of expected values
     * to be extracted from extractedParameters. In most cases, one would need to extract all values from
     * extractedParameters and providerHash, hence use this function over calling extractFieldFromContext 
     * multiple times.
     * 
     * @param data                  Context string from which target value needs to be extracted
     * @param maxValues             Maximum number of values to be extracted from extractedParameters including intentHash and providerHash
     * @param extractIntentAndProviderHash Extracts and returns intentHash and providerHash if true
     */
    function extractAllFromContext(
        string memory data,
        uint8 maxValues,
        bool extractIntentAndProviderHash
    ) internal pure returns (string[] memory) {
        require(maxValues > 0, "Max values must be greater than 0");

        bytes memory dataBytes = bytes(data);
        
        // Reuse variables to avoid "stack too deep"
        uint index = 0;
        uint valuesFound = 0;
        uint startIndex;
        uint endIndex;
        bool isValue;

        uint[] memory valueIndices = new uint[](2 * maxValues);

        // Extract context address
        for (uint i = 0; i < CONTEXT_ADDRESS_BYTES.length; i++) {
            require(
                dataBytes[index + i] == CONTEXT_ADDRESS_BYTES[i],
                "Extraction failed. Malformed contextAddress"
            );
        }
        index += CONTEXT_ADDRESS_BYTES.length;

        // Extract context address value if it exists
        startIndex = index;
        while (
            index < dataBytes.length &&
            !(dataBytes[index] == '"' && dataBytes[index - 1] != "\\")
        ) {
            index++;
        }
        require(index < dataBytes.length, "Extraction failed. Malformed contextAddress");
        endIndex = index;
        if (endIndex == startIndex) {
            revert("Extraction failed. Empty contextAddress value");
        }
        valueIndices[2 * valuesFound] = startIndex;
        valueIndices[2 * valuesFound + 1] = endIndex;
        valuesFound++;
        index += 2; // move past the closing quote and comma

        // Extract context message
        for (uint i = 0; i < CONTEXT_MESSAGE_BYTES.length; i++) {
            require(
                dataBytes[index + i] == CONTEXT_MESSAGE_BYTES[i],
                "Extraction failed. Malformed contextMessage"
            );
        }
        index += CONTEXT_MESSAGE_BYTES.length;

        // Extract context message value if it exists
        startIndex = index;
        while (
            index < dataBytes.length &&
            !(dataBytes[index] == '"' && dataBytes[index - 1] != "\\")
        ) {
            index++;
        }
        require(index < dataBytes.length, "Extraction failed. Malformed contextMessage");
        endIndex = index;
        if (endIndex == startIndex) {
            revert("Extraction failed. Empty contextMessage value");
        }
        valueIndices[2 * valuesFound] = startIndex;
        valueIndices[2 * valuesFound + 1] = endIndex;
        valuesFound++;
        index += 2; // move past the closing quote and comma

        for (uint i = 0; i < EXTRACTED_PARAMETERS_BYTES.length; i++) {
            require(
                dataBytes[index + i] == EXTRACTED_PARAMETERS_BYTES[i],
                "Extraction failed. Malformed extractedParameters"
            );
        }
        index += EXTRACTED_PARAMETERS_BYTES.length;
        isValue = false; // starts with a key right after '{\"extractedParameters\":{\"'

        while (index < dataBytes.length) {
            // Keep incrementing until '"', escaped quotes are not considered
            if (!(dataBytes[index] == '"' && dataBytes[index - 1] != "\\")) {
                index++;
                continue;
            }
            if (!isValue) {
                // \":\" (3 chars)
                require(
                    dataBytes[index + 1] == ":" && dataBytes[index + 2] == '"',
                    "Extraction failed. Malformed data 1"
                );
                index += 3; // move it after \"
                isValue = true;
                // Mark start
                valueIndices[2 * valuesFound] = index; // start index
            } else {
                // \",\" (3 chars) or \"}, (3 chars)
                // \"}} is not supported, there should always be a providerHash
                bool commaThenQuote = (dataBytes[index + 1] == "," && dataBytes[index + 2] == '"');
                bool braceThenComma = (dataBytes[index + 1] == '}' && dataBytes[index + 2] == ",");
                require(
                    commaThenQuote || braceThenComma,
                    "Extraction failed. Malformed data 2"
                );
                valueIndices[2 * valuesFound + 1] = index; // end index
                valuesFound++;

                // Revert if valuesFound == maxValues and next char is a comma as there will be more values
                if (commaThenQuote) {
                    // Revert if valuesFound == maxValues and next char is a comma as there will be more values
                    require(valuesFound != maxValues, "Extraction failed. Exceeded max values");
                    index += 3;
                    isValue = false;
                } else { // index + 1 = "}"
                    index += 3;
                    break; // end of extractedParameters
                }
            }
        }

        if (extractIntentAndProviderHash) {
            for (uint i = 0; i < PROVIDER_HASH_PARAM_BYTES.length; i++) {
                require(
                    dataBytes[index + i] == PROVIDER_HASH_PARAM_BYTES[i],
                    "Extraction failed. Malformed providerHash"
                );
            }
            index += PROVIDER_HASH_PARAM_BYTES.length;

            // final indices tuple in valueIndices will be for star and end indices of provider hash
            valueIndices[2 * valuesFound] = index;
            // Keep incrementing until '"'
            while (index < dataBytes.length && dataBytes[index] != '"') {
                index++;
            }
            valueIndices[2 * valuesFound + 1] = index;
            valuesFound++;
        }

        string[] memory values = new string[](valuesFound);
        for (uint i = 0; i < valuesFound; i++) {
            startIndex = valueIndices[2 * i];
            endIndex = valueIndices[2 * i + 1];
            bytes memory contextValue = new bytes(endIndex - startIndex);
            for (uint j = startIndex; j < endIndex; j++) {
                contextValue[j - startIndex] = dataBytes[j];
            }
            values[i] = string(contextValue);
        }
        return values;
    }
}

//SPDX-License-Identifier: MIT

import { DateTime } from "../external/DateTime.sol";

import { StringConversionUtils } from "./StringConversionUtils.sol";

pragma solidity ^0.8.18;

library DateParsing {
    
    using StringConversionUtils for string;

    /**
     * @notice Iterates through every character in the date string and splits the string at each dash, "T", or colon. Function will revert
     * if there are not 6 substrings formed from the split. The substrings are then converted to uints and passed to the DateTime lib
     * to get the unix timestamp. This function is SPECIFIC TO THE DATE FORMAT YYYY-MM-DDTHH:MM:SS, not suitable for use with other date
     * formats. It returns UTC timestamps.
     *
     * @param _dateString       Date string to be converted to a UTC timestamp
     */
    function _dateStringToTimestamp(string memory _dateString) internal pure returns (uint256 utcTimestamp) {
        string[6] memory extractedStrings;
        uint256 breakCounter;
        uint256 lastBreak;
        for (uint256 i = 0; i < bytes(_dateString).length; i++) {
            if (bytes(_dateString)[i] == 0x2d || bytes(_dateString)[i] == 0x3a || bytes(_dateString)[i] == 0x54) {
                extractedStrings[breakCounter] = _dateString.substring(lastBreak, i);
                lastBreak = i + 1;
                breakCounter++;
            }
        }
        // Add last substring to array
        extractedStrings[breakCounter] = _dateString.substring(lastBreak, bytes(_dateString).length);

        // Check that exactly 6 substrings were found (string is split at 5 different places)
        require(breakCounter == 5, "Invalid date string");

        utcTimestamp = DateTime.timestampFromDateTime(
            extractedStrings[0].stringToUint(0),    // year
            extractedStrings[1].stringToUint(0),    // month
            extractedStrings[2].stringToUint(0),    // day
            extractedStrings[3].stringToUint(0),    // hour
            extractedStrings[4].stringToUint(0),    // minute
            extractedStrings[5].stringToUint(0)     // second
        );
    }
}

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

// Building on zk-email's StringUtils library we add the ability to handle decimals when
// converting from string to Uint
library StringConversionUtils {
    
    /**
     * @notice Function that parses numbers returned as strings including floating point numbers. Returned floating point
     * numbers are to have the desired amount of decimal specified. If the stringified version of the floating point
     * number has more decimal places than desired then the function will revert in order to be maximally safe. If
     * the returned number has multiple floating points then the function will revert.
     *
     * Examples: _s = "12.34", _expectedDecimals = 6 => 12340000
     *           _s = "12.34", _expectedDecimals = 2 => 1234
     *           _s = "12.34", _expectedDecimals = 1 => REVERT (we never want loss of precision only addition)
     *           _s = "12.34.56", _expectedDecimals = 6 => REVERT (Invalid number)
     *
     * @param _s                    String being processed
     * @param _desiredDecimals      Desired amount of decimal places
     */
    function stringToUint(string memory _s, uint256 _desiredDecimals) internal pure returns (uint256) {
        return stringToUint(_s, 0x2E, _desiredDecimals);
    }

    function stringToUint(
        string memory _s,
        bytes1 _decimalCharacter,
        uint256 _desiredDecimals
    )
        internal
        pure
        returns (uint256)
    {
        bytes memory b = bytes(_s);

        uint256 result = 0;
        uint256 decimalPlaces = 0;

        bool decimals = false;
        for (uint256 i = 0; i < b.length; i++) {
            if (b[i] >= 0x30 && b[i] <= 0x39) {
                result = result * 10 + (uint256(uint8(b[i])) - 48);
            }

            if (decimals) {
                decimalPlaces++;
            }

            if (b[i] == _decimalCharacter) {
                require(decimals == false, "String has multiple decimals");
                decimals = true;
            }
        }

        require(decimalPlaces <= _desiredDecimals, "String has too many decimal places");
        return result * (10 ** (_desiredDecimals - decimalPlaces));
    }

    /**
     * @notice Function that returns a substring from _startIndex to _endIndex (non-inclusive).
     *
     * @param _str           String being processed
     * @param _startIndex    Index to start parsing from
     * @param _endIndex      Index to stop parsing at (index not included in result)
     */
    function substring(string memory _str, uint _startIndex, uint _endIndex) internal pure returns (string memory ) {
        bytes memory strBytes = bytes(_str);
        bytes memory result = new bytes(_endIndex-_startIndex);
        for(uint i = _startIndex; i < _endIndex; i++) {
            result[i-_startIndex] = strBytes[i];
        }
        return string(result);
    }

    function stringComparison(string memory _a, string memory _b) internal pure returns (bool) {
        return (keccak256(abi.encodePacked(_a)) == keccak256(abi.encodePacked(_b)));
    }
}

// SPDX-License-Identifier: MIT

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

import { Bytes32ArrayUtils } from "../external/Bytes32ArrayUtils.sol";
import { IBasePaymentVerifier } from "./interfaces/IBasePaymentVerifier.sol";
import { INullifierRegistry } from "./nullifierRegistries/INullifierRegistry.sol";

pragma solidity ^0.8.18;

contract BasePaymentVerifier is Ownable, IBasePaymentVerifier {

    using Bytes32ArrayUtils for bytes32[];

    /* ============ Events ============ */
    event TimestampBufferSet(uint256 timestampBuffer);
    event CurrencyAdded(bytes32 currencyCode);
    event CurrencyRemoved(bytes32 currencyCode);
    
    /* ============ State Variables ============ */
    address public immutable escrow;
    INullifierRegistry public nullifierRegistry;
    
    uint256 public timestampBuffer;

    bytes32[] internal currencies;
    mapping(bytes32 => bool) public isCurrency;
    
    /* ============ Constructor ============ */
    constructor(
        address _escrow,
        INullifierRegistry _nullifierRegistry,
        uint256 _timestampBuffer,
        bytes32[] memory _currencies
    )
        Ownable()
    {
        escrow = _escrow;
        nullifierRegistry = _nullifierRegistry;
        timestampBuffer = _timestampBuffer;

        for (uint256 i = 0; i < _currencies.length; i++) {
            addCurrency(_currencies[i]);
        }
    }

    /* ============ External Functions ============ */

    /**
     * @notice OWNER ONLY: Adds a currency code to supported currencies
     * @param _currencyCode Currency code to add
     */
    function addCurrency(bytes32 _currencyCode) public onlyOwner {
        require(!isCurrency[_currencyCode], "Currency already added");
        
        currencies.push(_currencyCode);
        isCurrency[_currencyCode] = true;
        
        emit CurrencyAdded(_currencyCode);
    }

    /**
     * @notice OWNER ONLY: Removes a currency code from supported currencies
     * @param _currencyCode Currency code to remove
     */
    function removeCurrency(bytes32 _currencyCode) external onlyOwner {
        require(isCurrency[_currencyCode], "Currency not added");
        
        currencies.removeStorage(_currencyCode);
        isCurrency[_currencyCode] = false;
        
        emit CurrencyRemoved(_currencyCode);
    }

    /**
     * @notice OWNER ONLY: Sets the timestamp buffer for payments. This is the amount of time in seconds
     * that the timestamp can be off by and still be considered valid. Necessary to build in flexibility 
     * with L2 timestamps.
     *
     * @param _timestampBuffer    The timestamp buffer for payments
     */
    function setTimestampBuffer(uint256 _timestampBuffer) external onlyOwner {
        timestampBuffer = _timestampBuffer;
        emit TimestampBufferSet(_timestampBuffer);
    }

    /* ============ External View Functions ============ */

    function getCurrencies() external view returns (bytes32[] memory) {
        return currencies;
    }

    /* ============ Internal Functions ============ */

    function _validateAndAddNullifier(bytes32 _nullifier) internal {
        require(!nullifierRegistry.isNullified(_nullifier), "Nullifier has already been used");
        nullifierRegistry.addNullifier(_nullifier);
    }
}

//SPDX-License-Identifier: MIT
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

import { AddressArrayUtils } from "../external/AddressArrayUtils.sol";
import { Claims } from "../external/Claims.sol";
import { StringArrayUtils } from "../external/StringArrayUtils.sol";

import { BasePaymentVerifier } from "./BasePaymentVerifier.sol";
import { ClaimVerifier } from "../lib/ClaimVerifier.sol";
import { INullifierRegistry } from "./nullifierRegistries/INullifierRegistry.sol";
import { IReclaimVerifier } from "./interfaces/IReclaimVerifier.sol";

pragma solidity ^0.8.18;

contract BaseReclaimPaymentVerifier is IReclaimVerifier, BasePaymentVerifier {

    using AddressArrayUtils for address[];
    using StringArrayUtils for string[];

    /* ============ Constants ============ */

    uint256 internal constant PRECISE_UNIT = 1e18;

    /* ============ State Variables ============ */
    mapping(string => bool) public isProviderHash;
    string[] public providerHashes;                         // Set of provider hashes that these proofs should be for

    /* ============ Events ============ */
    event ProviderHashAdded(string providerHash);
    event ProviderHashRemoved(string providerHash);

    /* ============ Constructor ============ */
    constructor(
        address _ramp,
        INullifierRegistry _nulliferRegistry,
        uint256 _timestampBuffer,
        bytes32[] memory _currencies,
        string[] memory _providerHashes
    )
        BasePaymentVerifier(
            _ramp,
            _nulliferRegistry,
            _timestampBuffer,
            _currencies
        )
    {
        for (uint256 i = 0; i < _providerHashes.length; i++) {
            require(!isProviderHash[_providerHashes[i]], "Provider hash already added");
            isProviderHash[_providerHashes[i]] = true;
            providerHashes.push(_providerHashes[i]);

            emit ProviderHashAdded(_providerHashes[i]);
        }
    }

    /* ============ Admin Functions ============ */

    /**
     * ONLY OWNER: Add provider hash string. Provider hash must not have been previously added.
     *
     * @param _newProviderHash    New provider hash to be added
     */
    function addProviderHash(string memory _newProviderHash) external onlyOwner {
        require(!isProviderHash[_newProviderHash], "Provider hash already added");

        isProviderHash[_newProviderHash] = true;
        providerHashes.push(_newProviderHash);

        emit ProviderHashAdded(_newProviderHash);
    }

    /**
     * ONLY OWNER: Remove provider hash string. Provider hash must have been previously added.
     *
     * @param _removeProviderHash    Provider hash to be removed
     */
    function removeProviderHash(string memory _removeProviderHash) external onlyOwner {
        require(isProviderHash[_removeProviderHash], "Provider hash not found");

        delete isProviderHash[_removeProviderHash];
        providerHashes.removeStorage(_removeProviderHash);

        emit ProviderHashRemoved(_removeProviderHash);
    }

    /* ============ Public Functions ============ */
    
    /**
     * Verify proof generated by witnesses. Claim is constructed by hashing claimInfo (provider, context, parameters)
     * to get the identifier. And then signing on (identifier, owner, timestamp, epoch) to get claim signature. 
     * This function verifies a claim by performing the following checks on the claim
     * - Calculates the hash of the claimInfo and checks if it matches the identifier in the claim
     * - Checks if the signatures are valid and from the witnesses
     * This function reverts if
     * - No signatures are found on the proof
     * - ClaimInfo hash does not match the identifier in the claim
     * - Signatures are invalid (not from the witnesses)
     * 
     * DEV NOTE: This function does NOT validate that the claim provider hash is valid. That is the 
     * responsibility of the caller. Ensure witnesses are unique otherwise the threshold can be met 
     * with duplicate witnesses.
     * 
     * Parts of the code are adapted from: https://basescan.org/address/0x7281630e4346dd4c0b7ae3b4689c1d0102741410#code
     *    
     * @param proof                 Proof to be verified
     * @param _witnesses            List of accepted witnesses
     * @param _requiredThreshold    Minimum number of signatures required from accepted witnesses
     */
    function verifyProofSignatures(
        ReclaimProof memory proof, 
        address[] memory _witnesses,
        uint256 _requiredThreshold
    ) public pure returns (bool) {

        require(_requiredThreshold > 0, "Required threshold must be greater than 0");
        require(_requiredThreshold <= _witnesses.length, "Required threshold must be less than or equal to number of witnesses");
        require(proof.signedClaim.signatures.length > 0, "No signatures");

        Claims.SignedClaim memory signed = Claims.SignedClaim(
            proof.signedClaim.claim,
            proof.signedClaim.signatures
        );

        // check if the hash from the claimInfo is equal to the infoHash in the claimData
        bytes32 hashed = Claims.hashClaimInfo(proof.claimInfo);
        require(proof.signedClaim.claim.identifier == hashed, "ClaimInfo hash doesn't match");
        require(hashed != bytes32(0), "ClaimInfo hash is zero");

        // Recover signers of the signed claim
        address[] memory claimSigners = Claims.recoverSignersOfSignedClaim(signed);
        require(claimSigners.length >= _requiredThreshold, "Fewer signatures than required threshold");

        // Track unique signers using an array
        address[] memory seenSigners = new address[](claimSigners.length);
        uint256 validWitnessSignatures;

        // Count how many signers are accepted witnesses, skipping duplicates
        for (uint256 i = 0; i < claimSigners.length; i++) {
            address currSigner = claimSigners[i];
            if (seenSigners.contains(currSigner)) {
                continue;
            }

            if (_witnesses.contains(currSigner)) {
                seenSigners[validWitnessSignatures] = currSigner;
                validWitnessSignatures++;
            }
        }

        // Check threshold
        require(
            validWitnessSignatures >= _requiredThreshold,
            "Not enough valid witness signatures"
        );

        return true;
    }


    /* ============ View Functions ============ */

    function getProviderHashes() external view returns (string[] memory) {
        return providerHashes;
    }

    /* ============ Internal Functions ============ */

    function _validateProviderHash(string memory _providerHash) internal view returns (bool) {
        return isProviderHash[_providerHash];
    }

    function _validateAndAddSigNullifier(bytes[] memory _sigArray) internal {
        bytes32 nullifier = keccak256(abi.encode(_sigArray));
        require(!nullifierRegistry.isNullified(nullifier), "Nullifier has already been used");
        nullifierRegistry.addNullifier(nullifier);
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

interface IBasePaymentVerifier {
    function getCurrencies() external view returns (bytes32[] memory currencyCodes);
    function isCurrency(bytes32 _currencyCode) external view returns (bool);
}

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

import { IBasePaymentVerifier } from "./IBasePaymentVerifier.sol";

interface IPaymentVerifier is IBasePaymentVerifier {

    /* ============ Structs ============ */

    struct VerifyPaymentData {
        bytes paymentProof;                     // Payment proof
        address depositToken;                   // Address of deposit token locked in escrow
        uint256 intentAmount;                   // Amount of deposit token that offchain payer wants to take
        uint256 intentTimestamp;                // Timestamp at which intent was created. Offchain payment must be made after this timestamp.
        string payeeDetails;                    // Payee details (hash of payee's payment platform ID OR just raw ID)
        bytes32 fiatCurrency;                   // Fiat currency the offchain payer paid in
        uint256 conversionRate;                 // Conversion rate of deposit token to fiat currency
        bytes data;                             // Additional data required for verification (e.g. attester address)
    }

    /* ============ External Functions ============ */

    function verifyPayment(
        VerifyPaymentData calldata _verifyPaymentData
    )   
        external
        returns(bool success, bytes32 intentHash);

}

File 24 of 25 : IReclaimVerifier.sol
//SPDX-License-Identifier: MIT

import { Claims } from "../../external/Claims.sol";

pragma solidity ^0.8.18;

interface IReclaimVerifier {
    
    struct ReclaimProof {
        Claims.ClaimInfo claimInfo;
        Claims.SignedClaim signedClaim;
        bool isAppclipProof;
    }
}

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

interface INullifierRegistry {
    function addNullifier(bytes32 _nullifier) external;
    function isNullified(bytes32 _nullifier) external view returns(bool);
}

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

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_escrow","type":"address"},{"internalType":"contract INullifierRegistry","name":"_nullifierRegistry","type":"address"},{"internalType":"uint256","name":"_timestampBuffer","type":"uint256"},{"internalType":"bytes32[]","name":"_currencies","type":"bytes32[]"},{"internalType":"string[]","name":"_providerHashes","type":"string[]"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"currencyCode","type":"bytes32"}],"name":"CurrencyAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"currencyCode","type":"bytes32"}],"name":"CurrencyRemoved","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":"string","name":"providerHash","type":"string"}],"name":"ProviderHashAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"providerHash","type":"string"}],"name":"ProviderHashRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"timestampBuffer","type":"uint256"}],"name":"TimestampBufferSet","type":"event"},{"inputs":[{"internalType":"bytes32","name":"_currencyCode","type":"bytes32"}],"name":"addCurrency","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_newProviderHash","type":"string"}],"name":"addProviderHash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"escrow","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrencies","outputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getProviderHashes","outputs":[{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"isCurrency","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"isProviderHash","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nullifierRegistry","outputs":[{"internalType":"contract INullifierRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"providerHashes","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_currencyCode","type":"bytes32"}],"name":"removeCurrency","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_removeProviderHash","type":"string"}],"name":"removeProviderHash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_timestampBuffer","type":"uint256"}],"name":"setTimestampBuffer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"timestampBuffer","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"paymentProof","type":"bytes"},{"internalType":"address","name":"depositToken","type":"address"},{"internalType":"uint256","name":"intentAmount","type":"uint256"},{"internalType":"uint256","name":"intentTimestamp","type":"uint256"},{"internalType":"string","name":"payeeDetails","type":"string"},{"internalType":"bytes32","name":"fiatCurrency","type":"bytes32"},{"internalType":"uint256","name":"conversionRate","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct IPaymentVerifier.VerifyPaymentData","name":"_verifyPaymentData","type":"tuple"}],"name":"verifyPayment","outputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"string","name":"provider","type":"string"},{"internalType":"string","name":"parameters","type":"string"},{"internalType":"string","name":"context","type":"string"}],"internalType":"struct Claims.ClaimInfo","name":"claimInfo","type":"tuple"},{"components":[{"components":[{"internalType":"bytes32","name":"identifier","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint32","name":"timestampS","type":"uint32"},{"internalType":"uint32","name":"epoch","type":"uint32"}],"internalType":"struct Claims.CompleteClaimData","name":"claim","type":"tuple"},{"internalType":"bytes[]","name":"signatures","type":"bytes[]"}],"internalType":"struct Claims.SignedClaim","name":"signedClaim","type":"tuple"},{"internalType":"bool","name":"isAppclipProof","type":"bool"}],"internalType":"struct IReclaimVerifier.ReclaimProof","name":"proof","type":"tuple"},{"internalType":"address[]","name":"_witnesses","type":"address[]"},{"internalType":"uint256","name":"_requiredThreshold","type":"uint256"}],"name":"verifyProofSignatures","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"}]

60a06040523480156200001157600080fd5b50604051620050f2380380620050f28339810160408190526200003491620005bc565b84848484848484848462000048336200027a565b6001600160a01b03848116608052600180546001600160a01b031916918516919091179055600282905560005b8151811015620000c157620000ac828281518110620000985762000098620006b9565b6020026020010151620002ca60201b60201c565b80620000b881620006cf565b91505062000075565b505050505060005b815181101562000269576005828281518110620000ea57620000ea620006b9565b6020026020010151604051620001019190620006f7565b9081526040519081900360200190205460ff1615620001675760405162461bcd60e51b815260206004820152601b60248201527f50726f7669646572206861736820616c7265616479206164646564000000000060448201526064015b60405180910390fd5b60016005838381518110620001805762000180620006b9565b6020026020010151604051620001979190620006f7565b908152602001604051809103902060006101000a81548160ff0219169083151502179055506006828281518110620001d357620001d3620006b9565b60209081029190910181015182546001810184556000938452919092200190620001fe9082620007a4565b507fc9e1c78701d1853eeb1eb35ad3449e4b336281f672d5837243d21239dc6f4096828281518110620002355762000235620006b9565b60200260200101516040516200024c919062000870565b60405180910390a1806200026081620006cf565b915050620000c9565b5050505050505050505050620008a5565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b620002d4620003b6565b60008181526004602052604090205460ff1615620003355760405162461bcd60e51b815260206004820152601660248201527f43757272656e637920616c72656164792061646465640000000000000000000060448201526064016200015e565b6003805460018082019092557fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b01829055600082815260046020908152604091829020805460ff1916909317909255518281527f15e2bb2d9733845f72b6cc683c4d7bafd2d79cfd5c160cb1be756b6faa97dbdc910160405180910390a150565b6000546001600160a01b03163314620004125760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016200015e565b565b6001600160a01b03811681146200042a57600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b03811182821017156200046e576200046e6200042d565b604052919050565b60006001600160401b038211156200049257620004926200042d565b5060051b60200190565b60005b83811015620004b95781810151838201526020016200049f565b50506000910152565b6000601f8381840112620004d557600080fd5b82516020620004ee620004e88362000476565b62000443565b82815260059290921b850181019181810190878411156200050e57600080fd5b8287015b84811015620005b05780516001600160401b0380821115620005345760008081fd5b818a0191508a603f8301126200054a5760008081fd5b858201516040828211156200056357620005636200042d565b62000576828b01601f1916890162000443565b92508183528c818386010111156200058e5760008081fd5b6200059f828985018387016200049c565b505084525091830191830162000512565b50979650505050505050565b600080600080600060a08688031215620005d557600080fd5b8551620005e28162000414565b80955050602080870151620005f78162000414565b6040880151606089015191965094506001600160401b03808211156200061c57600080fd5b818901915089601f8301126200063157600080fd5b815162000642620004e88262000476565b81815260059190911b8301840190848101908c8311156200066257600080fd5b938501935b82851015620006825784518252938501939085019062000667565b60808c015190975094505050808311156200069c57600080fd5b5050620006ac88828901620004c2565b9150509295509295909350565b634e487b7160e01b600052603260045260246000fd5b600060018201620006f057634e487b7160e01b600052601160045260246000fd5b5060010190565b600082516200070b8184602087016200049c565b9190910192915050565b600181811c908216806200072a57607f821691505b6020821081036200074b57634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200079f57600081815260208120601f850160051c810160208610156200077a5750805b601f850160051c820191505b818110156200079b5782815560010162000786565b5050505b505050565b81516001600160401b03811115620007c057620007c06200042d565b620007d881620007d1845462000715565b8462000751565b602080601f831160018114620008105760008415620007f75750858301515b600019600386901b1c1916600185901b1785556200079b565b600085815260208120601f198616915b82811015620008415788860151825594840194600190910190840162000820565b5085821015620008605787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6020815260008251806020840152620008918160408501602087016200049c565b601f01601f19169190910160400192915050565b60805161482a620008c86000396000818161029f01526105e3015261482a6000f3fe608060405234801561001057600080fd5b50600436106101165760003560e01c8063b2a3fda4116100a2578063dbac582111610071578063dbac582114610283578063e2fdcc171461029a578063efd4ad5a146102c1578063f2fde38b146102d4578063fa93cd38146102e757600080fd5b8063b2a3fda414610235578063b870676c14610248578063bd6e77411461025b578063c2107e731461026e57600080fd5b806361c661de116100e957806361c661de146101b657806369037180146101cb578063715018a6146101de57806384c7b439146101e65780638da5cb5b1461021057600080fd5b80631cba9bed1461011b5780632ca6927d146101305780633e1d0edc1461017357806357c6e12a14610196575b600080fd5b61012e6101293660046139f4565b6102fa565b005b61015e61013e3660046139f4565b805160208183018101805160058252928201919093012091525460ff1681565b60405190151581526020015b60405180910390f35b61015e610181366004613a28565b60046020526000908152604090205460ff1681565b6101a96101a4366004613a28565b6103e2565b60405161016a9190613a91565b6101be61048e565b60405161016a9190613aa4565b61012e6101d9366004613a28565b6104e6565b61012e6105c1565b6101f96101f4366004613ae8565b6105d5565b60408051921515835260208301919091520161016a565b6000546001600160a01b03165b6040516001600160a01b03909116815260200161016a565b61012e610243366004613a28565b6106da565b60015461021d906001600160a01b031681565b61012e6102693660046139f4565b610717565b61027661082d565b60405161016a9190613b2a565b61028c60025481565b60405190815260200161016a565b61021d7f000000000000000000000000000000000000000000000000000000000000000081565b61012e6102cf366004613a28565b610906565b61012e6102e2366004613ba3565b6109b4565b61015e6102f5366004613eb1565b610a2d565b610302610ded565b6005816040516103129190613f1d565b9081526040519081900360200190205460ff166103765760405162461bcd60e51b815260206004820152601760248201527f50726f76696465722068617368206e6f7420666f756e6400000000000000000060448201526064015b60405180910390fd5b6005816040516103869190613f1d565b908152604051908190036020019020805460ff191690556103a8600682610e47565b7f6f594b4b4f0580c66aabba75218163a99fa1fe330a171cc9e2bfd8e167db22f8816040516103d79190613a91565b60405180910390a150565b600681815481106103f257600080fd5b90600052602060002001600091509050805461040d90613f39565b80601f016020809104026020016040519081016040528092919081815260200182805461043990613f39565b80156104865780601f1061045b57610100808354040283529160200191610486565b820191906000526020600020905b81548152906001019060200180831161046957829003601f168201915b505050505081565b606060038054806020026020016040519081016040528092919081815260200182805480156104dc57602002820191906000526020600020905b8154815260200190600101908083116104c8575b5050505050905090565b6104ee610ded565b60008181526004602052604090205460ff16156105465760405162461bcd60e51b815260206004820152601660248201527510dd5c9c995b98de48185b1c9958591e48185919195960521b604482015260640161036d565b6003805460018082019092557fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b01829055600082815260046020908152604091829020805460ff1916909317909255518281527f15e2bb2d9733845f72b6cc683c4d7bafd2d79cfd5c160cb1be756b6faa97dbdc91016103d7565b6105c9610ded565b6105d36000611001565b565b600080336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146106475760405162461bcd60e51b815260206004820152601460248201527313db9b1e48195cd8dc9bddc818d85b8818d85b1b60621b604482015260640161036d565b6000806106696106578680613f73565b61066460e0890189613f73565b611051565b91509150610681828661067b90613fb9565b83611124565b600082604001516040516020016106989190613f1d565b6040516020818303038152906040528051906020012090506106b981611396565b60808301516000906106cb90826114b1565b60019890975095505050505050565b6106e2610ded565b60028190556040518181527f11e8dda5412ecab251b7783921be2eb37fdf13b389fc7f671f2cee11a1dfefea906020016103d7565b61071f610ded565b60058160405161072f9190613f1d565b9081526040519081900360200190205460ff161561078f5760405162461bcd60e51b815260206004820152601b60248201527f50726f7669646572206861736820616c72656164792061646465640000000000604482015260640161036d565b60016005826040516107a19190613f1d565b908152604051908190036020019020805491151560ff19909216919091179055600680546001810182556000919091527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f016107fd82826140d3565b507fc9e1c78701d1853eeb1eb35ad3449e4b336281f672d5837243d21239dc6f4096816040516103d79190613a91565b60606006805480602002602001604051908101604052809291908181526020016000905b828210156108fd57838290600052602060002001805461087090613f39565b80601f016020809104026020016040519081016040528092919081815260200182805461089c90613f39565b80156108e95780601f106108be576101008083540402835291602001916108e9565b820191906000526020600020905b8154815290600101906020018083116108cc57829003601f168201915b505050505081526020019060010190610851565b50505050905090565b61090e610ded565b60008181526004602052604090205460ff166109615760405162461bcd60e51b815260206004820152601260248201527110dd5c9c995b98de481b9bdd08185919195960721b604482015260640161036d565b61096c6003826114cb565b60008181526004602052604090819020805460ff19169055517fa3c6a408fb053e3ef5e9bcd22e3574324a5ae99fad263cc4080da481842d3d4c906103d79083815260200190565b6109bc610ded565b6001600160a01b038116610a215760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840161036d565b610a2a81611001565b50565b6000808211610a905760405162461bcd60e51b815260206004820152602960248201527f5265717569726564207468726573686f6c64206d75737420626520677265617460448201526806572207468616e20360bc1b606482015260840161036d565b8251821115610b155760405162461bcd60e51b8152602060048201526044602482018190527f5265717569726564207468726573686f6c64206d757374206265206c65737320908201527f7468616e206f7220657175616c20746f206e756d626572206f66207769746e656064820152637373657360e01b608482015260a40161036d565b60008460200151602001515111610b5e5760405162461bcd60e51b815260206004820152600d60248201526c4e6f207369676e61747572657360981b604482015260640161036d565b604080518082019091526020808601805151835251810151908201528451600090610b88906115f3565b602087015151519091508114610be05760405162461bcd60e51b815260206004820152601c60248201527f436c61696d496e666f206861736820646f65736e2774206d6174636800000000604482015260640161036d565b80610c265760405162461bcd60e51b8152602060048201526016602482015275436c61696d496e666f2068617368206973207a65726f60501b604482015260640161036d565b6000610c3183611636565b90508481511015610c955760405162461bcd60e51b815260206004820152602860248201527f4665776572207369676e617475726573207468616e20726571756972656420746044820152671a1c995cda1bdb1960c21b606482015260840161036d565b600081516001600160401b03811115610cb057610cb06138d2565b604051908082528060200260200182016040528015610cd9578160200160208202803683370190505b5090506000805b8351811015610d81576000848281518110610cfd57610cfd614192565b60200260200101519050610d1a818561170f90919063ffffffff16565b15610d255750610d6f565b610d2f8a8261170f565b15610d6d5780848481518110610d4757610d47614192565b6001600160a01b039092166020928302919091019091015282610d69816141be565b9350505b505b80610d79816141be565b915050610ce0565b5086811015610dde5760405162461bcd60e51b815260206004820152602360248201527f4e6f7420656e6f7567682076616c6964207769746e657373207369676e61747560448201526272657360e81b606482015260840161036d565b50600198975050505050505050565b6000546001600160a01b031633146105d35760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161036d565b600080610f2484805480602002602001604051908101604052809291908181526020016000905b82821015610f1a578382906000526020600020018054610e8d90613f39565b80601f0160208091040260200160405190810160405280929190818152602001828054610eb990613f39565b8015610f065780601f10610edb57610100808354040283529160200191610f06565b820191906000526020600020905b815481529060010190602001808311610ee957829003601f168201915b505050505081526020019060010190610e6e565b5050505084611725565b9150915080610f6c5760405162461bcd60e51b815260206004820152601460248201527329ba3934b733903737ba1034b71030b93930bc9760611b604482015260640161036d565b8354600090610f7d906001906141d7565b9050808314610fcc57848181548110610f9857610f98614192565b90600052602060002001858481548110610fb457610fb4614192565b906000526020600020019081610fca91906141ea565b505b84805480610fdc57610fdc6142bc565b600190038181906000526020600020016000610ff89190613858565b90555050505050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b61108a6040518060c001604052806060815260200160608152602001606081526020016060815260200160608152602001606081525090565b600080611099868801886142d2565b905060006110a78686611796565b90506110b582826001610a2d565b506110bf826117a4565b93506110ce8460a001516118c7565b6111125760405162461bcd60e51b815260206004820152601560248201527409cde40ecc2d8d2c840e0e4deecd2c8cae490c2e6d605b1b604482015260640161036d565b81604001519250505094509492505050565b6000670de0b6b3a76400008360c0015184604001516111439190614306565b61114d9190614333565b9050600083602001516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611193573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111b79190614347565b85519091506000906111cc9060ff84166114b1565b90508281101561121e5760405162461bcd60e51b815260206004820152601860248201527f496e636f7272656374207061796d656e7420616d6f756e740000000000000000604482015260640161036d565b83156112bc576000866060015160405160200161123b9190613f1d565b60405160208183030381529060405280519060200120905061126a8660800151611264836118f2565b90611ab7565b6112b65760405162461bcd60e51b815260206004820152601b60248201527f496e636f7272656374207061796d656e7420726563697069656e740000000000604482015260640161036d565b5061131b565b608085015160608701516112cf91611ab7565b61131b5760405162461bcd60e51b815260206004820152601b60248201527f496e636f7272656374207061796d656e7420726563697069656e740000000000604482015260640161036d565b600060025461132d8860200151611b10565b611337919061436a565b9050856060015181101561138d5760405162461bcd60e51b815260206004820152601b60248201527f496e636f7272656374207061796d656e742074696d657374616d700000000000604482015260640161036d565b50505050505050565b60015460405163169394bb60e01b8152600481018390526001600160a01b039091169063169394bb90602401602060405180830381865afa1580156113df573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611403919061437d565b156114505760405162461bcd60e51b815260206004820152601f60248201527f4e756c6c69666965722068617320616c7265616479206265656e207573656400604482015260640161036d565b600154604051632dea6f9960e11b8152600481018390526001600160a01b0390911690635bd4df3290602401600060405180830381600087803b15801561149657600080fd5b505af11580156114aa573d6000803e3d6000fd5b5050505050565b60006114c283601760f91b84611ccc565b90505b92915050565b6000806115278480548060200260200160405190810160405280929190818152602001828054801561151c57602002820191906000526020600020905b815481526020019060010190808311611508575b505050505084611ea9565b91509150806115705760405162461bcd60e51b8152602060048201526015602482015274313cba32b99999103737ba1034b71030b93930bc9760591b604482015260640161036d565b8354600090611581906001906141d7565b90508083146115c65784818154811061159c5761159c614192565b90600052602060002001548584815481106115b9576115b9614192565b6000918252602090912001555b848054806115d6576115d66142bc565b600190038181906000526020600020016000905590555050505050565b6000808260000151836020015184604001516040516020016116179392919061439a565b60408051601f1981840301815291905280516020909101209392505050565b606060006116478360000151611ef9565b905060008360200151516001600160401b03811115611668576116686138d2565b604051908082528060200260200182016040528015611691578160200160208202803683370190505b50905060005b846020015151811015611707576116cb83866020015183815181106116be576116be614192565b6020026020010151611f85565b8282815181106116dd576116dd614192565b6001600160a01b0390921660209283029190910190910152806116ff816141be565b915050611697565b509392505050565b60008061171c8484611fce565b95945050505050565b81516000908190815b8181101561178357848051906020012086828151811061175057611750614192565b602002602001015180519060200120036117715792506001915061178f9050565b8061177b816141be565b91505061172e565b50600019600092509250505b9250929050565b60606114c2828401846143f5565b6117dd6040518060c001604052806060815260200160608152602001606081526020016060815260200160608152602001606081525090565b60006117f483600001516040015160086001612030565b90506040518060c001604052808260038151811061181457611814614192565b602002602001015181526020018260048151811061183457611834614192565b602002602001015181526020018260058151811061185457611854614192565b602002602001015181526020018260068151811061187457611874614192565b602002602001015181526020018260018151811061189457611894614192565b60200260200101518152602001826007815181106118b4576118b4614192565b6020026020010151815250915050919050565b60006005826040516118d99190613f1d565b9081526040519081900360200190205460ff1692915050565b604080518082018252601081526f181899199a1a9b1b9c1cb0b131b232b360811b6020820152815160428082526080820190935260609260009190602082018180368337019050509050600360fc1b8160008151811061195457611954614192565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061198357611983614192565b60200101906001600160f81b031916908160001a90535060005b6020811015611707578260048683602081106119bb576119bb614192565b1a60f81b6001600160f81b031916901c60f81c60ff16815181106119e1576119e1614192565b01602001516001600160f81b031916826119fc836002614306565b611a0790600261436a565b81518110611a1757611a17614192565b60200101906001600160f81b031916908160001a90535082858260208110611a4157611a41614192565b825191901a600f16908110611a5857611a58614192565b01602001516001600160f81b03191682611a73836002614306565b611a7e90600361436a565b81518110611a8e57611a8e614192565b60200101906001600160f81b031916908160001a90535080611aaf816141be565b91505061199d565b600081604051602001611aca9190613f1d565b6040516020818303038152906040528051906020012083604051602001611af19190613f1d565b6040516020818303038152906040528051906020012014905092915050565b6000611b1a613892565b60008060005b8551811015611c0357858181518110611b3b57611b3b614192565b6020910101516001600160f81b031916602d60f81b1480611b805750858181518110611b6957611b69614192565b6020910101516001600160f81b031916601d60f91b145b80611baf5750858181518110611b9857611b98614192565b6020910101516001600160f81b031916601560fa1b145b15611bf157611bbf868383612f01565b848460068110611bd157611bd1614192565b6020020152611be181600161436a565b915082611bed816141be565b9350505b80611bfb816141be565b915050611b20565b508451611c139086908390612f01565b838360068110611c2557611c25614192565b602002015260058214611c705760405162461bcd60e51b8152602060048201526013602482015272496e76616c6964206461746520737472696e6760681b604482015260640161036d565b61171c611c86600085815b6020020151906114b1565b611c936000866001611c7b565b611ca06000876002611c7b565b611cad6000886003611c7b565b611cba6000896004611c7b565b611cc760008a6005611c7b565b612fcd565b600083818080805b8451811015611e2257603060f81b858281518110611cf457611cf4614192565b01602001516001600160f81b03191610801590611d355750603960f81b858281518110611d2357611d23614192565b01602001516001600160f81b03191611155b15611d78576030858281518110611d4e57611d4e614192565b0160200151611d60919060f81c6141d7565b611d6b85600a614306565b611d75919061436a565b93505b8115611d8c5782611d88816141be565b9350505b876001600160f81b031916858281518110611da957611da9614192565b01602001516001600160f81b03191603611e10578115611e0b5760405162461bcd60e51b815260206004820152601c60248201527f537472696e6720686173206d756c7469706c6520646563696d616c7300000000604482015260640161036d565b600191505b80611e1a816141be565b915050611cd4565b5085821115611e7e5760405162461bcd60e51b815260206004820152602260248201527f537472696e672068617320746f6f206d616e7920646563696d616c20706c6163604482015261657360f01b606482015260840161036d565b611e8882876141d7565b611e9390600a61450d565b611e9d9084614306565b98975050505050505050565b81516000908190815b818110156117835784868281518110611ecd57611ecd614192565b602002602001015103611ee75792506001915061178f9050565b80611ef1816141be565b915050611eb2565b6060611f298260000151604051602001611f1591815260200190565b604051602081830303815290604052613029565b611f3683602001516131ec565b611f49846040015163ffffffff16613355565b611f5c856060015163ffffffff16613355565b604051602001611f6f9493929190614519565b6040516020818303038152906040529050919050565b600080611f928451613355565b84604051602001611fa4929190614592565b604051602081830303815290604052805190602001209050611fc68184613480565b949350505050565b81516000908190815b8181101561178357846001600160a01b0316868281518110611ffb57611ffb614192565b60200260200101516001600160a01b03160361201e5792506001915061178f9050565b80612028816141be565b915050611fd7565b606060008360ff161161208f5760405162461bcd60e51b815260206004820152602160248201527f4d61782076616c756573206d7573742062652067726561746572207468616e206044820152600360fc1b606482015260840161036d565b83600080808080806120a28a60026145ed565b60ff166001600160401b038111156120bc576120bc6138d2565b6040519080825280602002602001820160405280156120e5578160200160208202803683370190505b50905060005b604051806040016040528060138152602001723d9131b7b73a32bc3a20b2323932b9b9911d1160691b815250518110156121c457604051806040016040528060138152602001723d9131b7b73a32bc3a20b2323932b9b9911d1160691b815250818151811061215c5761215c614192565b01602001516001600160f81b03191688612176838a61436a565b8151811061218657612186614192565b01602001516001600160f81b031916146121b25760405162461bcd60e51b815260040161036d90614610565b806121bc816141be565b9150506120eb565b50604051806040016040528060138152602001723d9131b7b73a32bc3a20b2323932b9b9911d1160691b81525051866121fd919061436a565b95508593505b865186108015612273575086868151811061222057612220614192565b6020910101516001600160f81b031916601160f91b1480156122715750866122496001886141d7565b8151811061225957612259614192565b6020910101516001600160f81b031916601760fa1b14155b155b1561228a5785612282816141be565b965050612203565b865186106122aa5760405162461bcd60e51b815260040161036d90614610565b8592508383036123125760405162461bcd60e51b815260206004820152602d60248201527f45787472616374696f6e206661696c65642e20456d70747920636f6e7465787460448201526c416464726573732076616c756560981b606482015260840161036d565b838161231f876002614306565b8151811061232f5761232f614192565b60209081029190910101528281612347876002614306565b61235290600161436a565b8151811061236257612362614192565b602090810291909101015284612377816141be565b9550612386905060028761436a565b955060005b604051806040016040528060128152602001711131b7b73a32bc3a26b2b9b9b0b3b2911d1160711b8152505181101561246257604051806040016040528060128152602001711131b7b73a32bc3a26b2b9b9b0b3b2911d1160711b81525081815181106123fa576123fa614192565b01602001516001600160f81b03191688612414838a61436a565b8151811061242457612424614192565b01602001516001600160f81b031916146124505760405162461bcd60e51b815260040161036d9061465b565b8061245a816141be565b91505061238b565b50604051806040016040528060128152602001711131b7b73a32bc3a26b2b9b9b0b3b2911d1160711b815250518661249a919061436a565b95508593505b86518610801561251057508686815181106124bd576124bd614192565b6020910101516001600160f81b031916601160f91b14801561250e5750866124e66001886141d7565b815181106124f6576124f6614192565b6020910101516001600160f81b031916601760fa1b14155b155b15612527578561251f816141be565b9650506124a0565b865186106125475760405162461bcd60e51b815260040161036d9061465b565b8592508383036125af5760405162461bcd60e51b815260206004820152602d60248201527f45787472616374696f6e206661696c65642e20456d70747920636f6e7465787460448201526c4d6573736167652076616c756560981b606482015260840161036d565b83816125bc876002614306565b815181106125cc576125cc614192565b602090810291909101015282816125e4876002614306565b6125ef90600161436a565b815181106125ff576125ff614192565b602090810291909101015284612614816141be565b9550612623905060028761436a565b955060005b604051806040016040528060188152602001771132bc3a3930b1ba32b22830b930b6b2ba32b939911d3d9160411b8152505181101561275457604051806040016040528060188152602001771132bc3a3930b1ba32b22830b930b6b2ba32b939911d3d9160411b81525081815181106126a3576126a3614192565b01602001516001600160f81b031916886126bd838a61436a565b815181106126cd576126cd614192565b01602001516001600160f81b031916146127425760405162461bcd60e51b815260206004820152603060248201527f45787472616374696f6e206661696c65642e204d616c666f726d65642065787460448201526f726163746564506172616d657465727360801b606482015260840161036d565b8061274c816141be565b915050612628565b50604051806040016040528060188152602001771132bc3a3930b1ba32b22830b930b6b2ba32b939911d3d9160411b8152505186612792919061436a565b9550600091505b8651861015612b3c578686815181106127b4576127b4614192565b6020910101516001600160f81b031916601160f91b1480156128055750866127dd6001886141d7565b815181106127ed576127ed614192565b6020910101516001600160f81b031916601760fa1b14155b61281b5785612813816141be565b965050612799565b81612923578661282c87600161436a565b8151811061283c5761283c614192565b6020910101516001600160f81b031916601d60f91b14801561288c57508661286587600261436a565b8151811061287557612875614192565b6020910101516001600160f81b031916601160f91b145b6128e45760405162461bcd60e51b815260206004820152602360248201527f45787472616374696f6e206661696c65642e204d616c666f726d65642064617460448201526261203160e81b606482015260840161036d565b6128ef60038761436a565b9550600191508581612902876002614306565b8151811061291257612912614192565b602002602001018181525050612799565b60008761293188600161436a565b8151811061294157612941614192565b6020910101516001600160f81b031916600b60fa1b14801561299157508761296a88600261436a565b8151811061297a5761297a614192565b6020910101516001600160f81b031916601160f91b145b90506000886129a189600161436a565b815181106129b1576129b1614192565b6020910101516001600160f81b031916607d60f81b148015612a015750886129da89600261436a565b815181106129ea576129ea614192565b6020910101516001600160f81b031916600b60fa1b145b90508180612a0c5750805b612a645760405162461bcd60e51b815260206004820152602360248201527f45787472616374696f6e206661696c65642e204d616c666f726d65642064617460448201526230901960e91b606482015260840161036d565b8783612a71896002614306565b612a7c90600161436a565b81518110612a8c57612a8c614192565b602090810291909101015286612aa1816141be565b9750508115612b21578b60ff168703612b0b5760405162461bcd60e51b815260206004820152602660248201527f45787472616374696f6e206661696c65642e204578636565646564206d61782060448201526576616c75657360d01b606482015260840161036d565b612b1660038961436a565b975060009350612b35565b612b2c60038961436a565b97505050612b3c565b5050612799565b8815612d495760005b6040518060400160405280601081526020016f11383937bb34b232b92430b9b4111d1160811b81525051811015612c5a576040518060400160405280601081526020016f11383937bb34b232b92430b9b4111d1160811b8152508181518110612bb057612bb0614192565b01602001516001600160f81b03191688612bca838a61436a565b81518110612bda57612bda614192565b01602001516001600160f81b03191614612c485760405162461bcd60e51b815260206004820152602960248201527f45787472616374696f6e206661696c65642e204d616c666f726d65642070726f6044820152680ecd2c8cae490c2e6d60bb1b606482015260840161036d565b80612c52816141be565b915050612b45565b506040518060400160405280601081526020016f11383937bb34b232b92430b9b4111d1160811b8152505186612c90919061436a565b95508581612c9f876002614306565b81518110612caf57612caf614192565b6020026020010181815250505b865186108015612cf15750868681518110612cd957612cd9614192565b6020910101516001600160f81b031916601160f91b14155b15612d085785612d00816141be565b965050612cbc565b8581612d15876002614306565b612d2090600161436a565b81518110612d3057612d30614192565b602090810291909101015284612d45816141be565b9550505b6000856001600160401b03811115612d6357612d636138d2565b604051908082528060200260200182016040528015612d9657816020015b6060815260200190600190039081612d815790505b50905060005b86811015612ef15782612db0826002614306565b81518110612dc057612dc0614192565b6020026020010151955082816002612dd89190614306565b612de390600161436a565b81518110612df357612df3614192565b6020026020010151945060008686612e0b91906141d7565b6001600160401b03811115612e2257612e226138d2565b6040519080825280601f01601f191660200182016040528015612e4c576020820181803683370190505b509050865b86811015612ebe578a8181518110612e6b57612e6b614192565b01602001516001600160f81b03191682612e858a846141d7565b81518110612e9557612e95614192565b60200101906001600160f81b031916908160001a90535080612eb6816141be565b915050612e51565b5080838381518110612ed257612ed2614192565b6020026020010181905250508080612ee9906141be565b915050612d9c565b509b9a5050505050505050505050565b6060836000612f1085856141d7565b6001600160401b03811115612f2757612f276138d2565b6040519080825280601f01601f191660200182016040528015612f51576020820181803683370190505b509050845b84811015612fc357828181518110612f7057612f70614192565b01602001516001600160f81b03191682612f8a88846141d7565b81518110612f9a57612f9a614192565b60200101906001600160f81b031916908160001a90535080612fbb816141be565b915050612f56565b5095945050505050565b600081612fdb603c85614306565b612fe7610e1087614306565b62015180612ff68b8b8b61349c565b6130009190614306565b61300a919061436a565b613014919061436a565b61301e919061436a565b979650505050505050565b606060008251600261303b9190614306565b6001600160401b03811115613052576130526138d2565b6040519080825280601f01601f19166020018201604052801561307c576020820181803683370190505b5060408051808201909152601081526f181899199a1a9b1b9c1cb0b131b232b360811b602082015290915060005b84518110156131c2578182518683815181106130c8576130c8614192565b01602001516130da919060f81c614333565b815181106130ea576130ea614192565b01602001516001600160f81b03191683613105836002614306565b8151811061311557613115614192565b60200101906001600160f81b031916908160001a90535081825186838151811061314157613141614192565b0160200151613153919060f81c6146a6565b8151811061316357613163614192565b01602001516001600160f81b0319168361317e836002614306565b61318990600161436a565b8151811061319957613199614192565b60200101906001600160f81b031916908160001a905350806131ba816141be565b9150506130aa565b50816040516020016131d491906146ba565b60405160208183030381529060405292505050919050565b60408051602880825260608281019093526000919060208201818036833701905050905060005b601481101561332c5760006132298260136141d7565b613234906008614306565b61323f90600261450d565b613252906001600160a01b038716614333565b60f81b9050600060108260f81c61326991906146e4565b60f81b905060008160f81c601061328091906145ed565b8360f81c61328e9190614706565b60f81b905061329c826135cd565b856132a8866002614306565b815181106132b8576132b8614192565b60200101906001600160f81b031916908160001a9053506132d8816135cd565b856132e4866002614306565b6132ef90600161436a565b815181106132ff576132ff614192565b60200101906001600160f81b031916908160001a9053505050508080613324906141be565b915050613213565b508060405160200161333e91906146ba565b604051602081830303815290604052915050919050565b60608160000361337c5750506040805180820190915260018152600360fc1b602082015290565b8160005b81156133a65780613390816141be565b915061339f9050600a83614333565b9150613380565b6000816001600160401b038111156133c0576133c06138d2565b6040519080825280601f01601f1916602001820160405280156133ea576020820181803683370190505b509050815b8515613477576134006001826141d7565b9050600061340f600a88614333565b61341a90600a614306565b61342490886141d7565b61342f90603061471f565b905060008160f81b90508084848151811061344c5761344c614192565b60200101906001600160f81b031916908160001a90535061346e600a89614333565b975050506133ef565b50949350505050565b600080600061348f8585613608565b915091506117078161364a565b60006107b28410156134ad57600080fd5b838383600062253d8c60046064600c6134c7600e88614738565b6134d19190614758565b6134dd88611324614786565b6134e79190614786565b6134f19190614758565b6134fc9060036147ae565b6135069190614758565b600c80613514600e88614738565b61351e9190614758565b61352990600c6147ae565b613534600288614738565b61353e9190614738565b61354a9061016f6147ae565b6135549190614758565b6004600c613563600e89614738565b61356d9190614758565b613579896112c0614786565b6135839190614786565b61358f906105b56147ae565b6135999190614758565b6135a5617d4b87614738565b6135af9190614786565b6135b99190614786565b6135c39190614738565b611e9d9190614738565b6000600a60f883901c10156135f4576135eb60f883901c603061471f565b60f81b92915050565b6135eb60f883901c605761471f565b919050565b600080825160410361363e5760208301516040840151606085015160001a61363287828585613794565b9450945050505061178f565b5060009050600261178f565b600081600481111561365e5761365e6147de565b036136665750565b600181600481111561367a5761367a6147de565b036136c75760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e61747572650000000000000000604482015260640161036d565b60028160048111156136db576136db6147de565b036137285760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e67746800604482015260640161036d565b600381600481111561373c5761373c6147de565b03610a2a5760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b606482015260840161036d565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08311156137cb575060009050600361384f565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa15801561381f573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166138485760006001925092505061384f565b9150600090505b94509492505050565b50805461386490613f39565b6000825580601f10613874575050565b601f016020900490600052602060002090810190610a2a91906138b9565b6040518060c001604052806006905b60608152602001906001900390816138a15790505090565b5b808211156138ce57600081556001016138ba565b5090565b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b038111828210171561390a5761390a6138d2565b60405290565b604051606081016001600160401b038111828210171561390a5761390a6138d2565b60405161010081016001600160401b038111828210171561390a5761390a6138d2565b604051601f8201601f191681016001600160401b038111828210171561397d5761397d6138d2565b604052919050565b600082601f83011261399657600080fd5b81356001600160401b038111156139af576139af6138d2565b6139c2601f8201601f1916602001613955565b8181528460208386010111156139d757600080fd5b816020850160208301376000918101602001919091529392505050565b600060208284031215613a0657600080fd5b81356001600160401b03811115613a1c57600080fd5b611fc684828501613985565b600060208284031215613a3a57600080fd5b5035919050565b60005b83811015613a5c578181015183820152602001613a44565b50506000910152565b60008151808452613a7d816020860160208601613a41565b601f01601f19169290920160200192915050565b6020815260006114c26020830184613a65565b6020808252825182820181905260009190848201906040850190845b81811015613adc57835183529284019291840191600101613ac0565b50909695505050505050565b600060208284031215613afa57600080fd5b81356001600160401b03811115613b1057600080fd5b82016101008185031215613b2357600080fd5b9392505050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015613b7f57603f19888603018452613b6d858351613a65565b94509285019290850190600101613b51565b5092979650505050505050565b80356001600160a01b038116811461360357600080fd5b600060208284031215613bb557600080fd5b6114c282613b8c565b803563ffffffff8116811461360357600080fd5b60006001600160401b03821115613beb57613beb6138d2565b5060051b60200190565b600082601f830112613c0657600080fd5b81356020613c1b613c1683613bd2565b613955565b82815260059290921b84018101918181019086841115613c3a57600080fd5b8286015b84811015613c795780356001600160401b03811115613c5d5760008081fd5b613c6b8986838b0101613985565b845250918301918301613c3e565b509695505050505050565b600081830360a0811215613c9757600080fd5b604051604081016001600160401b038282108183111715613cba57613cba6138d2565b816040528294506080841215613ccf57600080fd5b613cd76138e8565b935085358452613ce960208701613b8c565b6020850152613cfa60408701613bbe565b6040850152613d0b60608701613bbe565b606085015292825260808501359280841115613d2657600080fd5b5050613d3485838601613bf5565b6020820152505092915050565b8015158114610a2a57600080fd5b803561360381613d41565b600060608284031215613d6c57600080fd5b613d74613910565b905081356001600160401b0380821115613d8d57600080fd5b9083019060608286031215613da157600080fd5b613da9613910565b823582811115613db857600080fd5b613dc487828601613985565b825250602083013582811115613dd957600080fd5b613de587828601613985565b602083015250604083013582811115613dfd57600080fd5b613e0987828601613985565b60408301525083526020840135915080821115613e2557600080fd5b50613e3284828501613c84565b602083015250613e4460408301613d4f565b604082015292915050565b600082601f830112613e6057600080fd5b81356020613e70613c1683613bd2565b82815260059290921b84018101918181019086841115613e8f57600080fd5b8286015b84811015613c7957613ea481613b8c565b8352918301918301613e93565b600080600060608486031215613ec657600080fd5b83356001600160401b0380821115613edd57600080fd5b613ee987838801613d5a565b94506020860135915080821115613eff57600080fd5b50613f0c86828701613e4f565b925050604084013590509250925092565b60008251613f2f818460208701613a41565b9190910192915050565b600181811c90821680613f4d57607f821691505b602082108103613f6d57634e487b7160e01b600052602260045260246000fd5b50919050565b6000808335601e19843603018112613f8a57600080fd5b8301803591506001600160401b03821115613fa457600080fd5b60200191503681900382131561178f57600080fd5b60006101008236031215613fcc57600080fd5b613fd4613932565b82356001600160401b0380821115613feb57600080fd5b613ff736838701613985565b835261400560208601613b8c565b60208401526040850135604084015260608501356060840152608085013591508082111561403257600080fd5b61403e36838701613985565b608084015260a085013560a084015260c085013560c084015260e085013591508082111561406b57600080fd5b5061407836828601613985565b60e08301525092915050565b601f8211156140ce57600081815260208120601f850160051c810160208610156140ab5750805b601f850160051c820191505b818110156140ca578281556001016140b7565b5050505b505050565b81516001600160401b038111156140ec576140ec6138d2565b614100816140fa8454613f39565b84614084565b602080601f831160018114614135576000841561411d5750858301515b600019600386901b1c1916600185901b1785556140ca565b600085815260208120601f198616915b8281101561416457888601518255948401946001909101908401614145565b50858210156141825787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000600182016141d0576141d06141a8565b5060010190565b818103818111156114c5576114c56141a8565b8181036141f5575050565b6141ff8254613f39565b6001600160401b03811115614216576142166138d2565b614224816140fa8454613f39565b6000601f82116001811461425857600083156142405750848201545b600019600385901b1c1916600184901b1784556114aa565b600085815260209020601f19841690600086815260209020845b838110156142925782860154825560019586019590910190602001614272565b50858310156141825793015460001960f8600387901b161c19169092555050600190811b01905550565b634e487b7160e01b600052603160045260246000fd5b6000602082840312156142e457600080fd5b81356001600160401b038111156142fa57600080fd5b611fc684828501613d5a565b80820281158282048414176114c5576114c56141a8565b634e487b7160e01b600052601260045260246000fd5b6000826143425761434261431d565b500490565b60006020828403121561435957600080fd5b815160ff81168114613b2357600080fd5b808201808211156114c5576114c56141a8565b60006020828403121561438f57600080fd5b8151613b2381613d41565b600084516143ac818460208901613a41565b600560f91b90830181815285519091906143cd816001850160208a01613a41565b600192019182015283516143e8816002840160208801613a41565b0160020195945050505050565b60006020828403121561440757600080fd5b81356001600160401b0381111561441d57600080fd5b611fc684828501613e4f565b600181815b8085111561446457816000190482111561444a5761444a6141a8565b8085161561445757918102915b93841c939080029061442e565b509250929050565b60008261447b575060016114c5565b81614488575060006114c5565b816001811461449e57600281146144a8576144c4565b60019150506114c5565b60ff8411156144b9576144b96141a8565b50506001821b6114c5565b5060208310610133831016604e8410600b84101617156144e7575081810a6114c5565b6144f18383614429565b8060001904821115614505576145056141a8565b029392505050565b60006114c2838361446c565b6000855161452b818460208a01613a41565b600560f91b908301818152865190919061454c816001850160208b01613a41565b600192019182018190528551614569816002850160208a01613a41565b60029201918201528351614584816003840160208801613a41565b016003019695505050505050565b7f19457468657265756d205369676e6564204d6573736167653a0a0000000000008152600083516145ca81601a850160208801613a41565b8351908301906145e181601a840160208801613a41565b01601a01949350505050565b60ff8181168382160290811690818114614609576146096141a8565b5092915050565b6020808252602b908201527f45787472616374696f6e206661696c65642e204d616c666f726d656420636f6e60408201526a746578744164647265737360a81b606082015260800190565b6020808252602b908201527f45787472616374696f6e206661696c65642e204d616c666f726d656420636f6e60408201526a746578744d65737361676560a81b606082015260800190565b6000826146b5576146b561431d565b500690565b61060f60f31b8152600082516146d7816002850160208701613a41565b9190910160020192915050565b600060ff8316806146f7576146f761431d565b8060ff84160491505092915050565b60ff82811682821603908111156114c5576114c56141a8565b60ff81811683821601908111156114c5576114c56141a8565b8181036000831280158383131683831282161715614609576146096141a8565b6000826147675761476761431d565b600160ff1b821460001984141615614781576147816141a8565b500590565b80820182811260008312801582168215821617156147a6576147a66141a8565b505092915050565b80820260008212600160ff1b841416156147ca576147ca6141a8565b81810583148215176114c5576114c56141a8565b634e487b7160e01b600052602160045260246000fdfea2646970667358221220925e45366b7c24daac4a0b502f9080dc8dc50cb5b346c53338f71e1c99ed467764736f6c63430008120033000000000000000000000000ff0149799631d7a5bde2e7ea9b306c42b3d9a9ca0000000000000000000000009cf61278c2cc9c41578fe3a0ed375e9870d514f1000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000001c4ae21aac0c6549d71dd96035b7e0bdb6c79ebdba8891b666115bc976d16a29e000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000000000000000000000000000000000000000056000000000000000000000000000000000000000000000000000000000000005e0000000000000000000000000000000000000000000000000000000000000066000000000000000000000000000000000000000000000000000000000000000423078626262346436383133633163636163373235333637333039346365346331653132326665333538363832333932383531636661333332666538333539623866640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042307865633961386333666335323166656639613830356665393762616262353035383536353639613133376338386464346563336133633239333038326134646264000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004230786134313862643739633762376434393336306166353665366165393661653038373734323437346461303366656130653939623931396530393361386165626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000423078353264313833356534376337636463616462356631356538316262316338373064333034613564303531316634643561653830333763333163323463663064330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042307861613938323563353863626230613662343266323061376439323662623365666636303638323433666564386461376638366261663034363766376264343562000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004230783639343230363237306432326634376664316630616364313232343237303061636536376264366636323930323661386163343331353763356430623962353300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000423078326430653962366134633130666434633564616130643162386537623537613830306465376266643230393638666561333636643365663266633638616533360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042307866383537616137363134303232326234336364363039366632646263653366393532626535393862306237343966316432353064386462643135343064613933000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004230783335356534333561363139353137396461306238386238393462616262383036633561386364383630313064353136323736626437383130653031336630306100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000423078326232666238386235303366313836326436633764353037343635333535656234396164353465336136376262316332623032313661663365643366343264370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042307831346465386235353033613461363937336262616139616133303165633738343365396263616133616630356536363130623534633666636335366161343235000000000000000000000000000000000000000000000000000000000000

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106101165760003560e01c8063b2a3fda4116100a2578063dbac582111610071578063dbac582114610283578063e2fdcc171461029a578063efd4ad5a146102c1578063f2fde38b146102d4578063fa93cd38146102e757600080fd5b8063b2a3fda414610235578063b870676c14610248578063bd6e77411461025b578063c2107e731461026e57600080fd5b806361c661de116100e957806361c661de146101b657806369037180146101cb578063715018a6146101de57806384c7b439146101e65780638da5cb5b1461021057600080fd5b80631cba9bed1461011b5780632ca6927d146101305780633e1d0edc1461017357806357c6e12a14610196575b600080fd5b61012e6101293660046139f4565b6102fa565b005b61015e61013e3660046139f4565b805160208183018101805160058252928201919093012091525460ff1681565b60405190151581526020015b60405180910390f35b61015e610181366004613a28565b60046020526000908152604090205460ff1681565b6101a96101a4366004613a28565b6103e2565b60405161016a9190613a91565b6101be61048e565b60405161016a9190613aa4565b61012e6101d9366004613a28565b6104e6565b61012e6105c1565b6101f96101f4366004613ae8565b6105d5565b60408051921515835260208301919091520161016a565b6000546001600160a01b03165b6040516001600160a01b03909116815260200161016a565b61012e610243366004613a28565b6106da565b60015461021d906001600160a01b031681565b61012e6102693660046139f4565b610717565b61027661082d565b60405161016a9190613b2a565b61028c60025481565b60405190815260200161016a565b61021d7f000000000000000000000000ff0149799631d7a5bde2e7ea9b306c42b3d9a9ca81565b61012e6102cf366004613a28565b610906565b61012e6102e2366004613ba3565b6109b4565b61015e6102f5366004613eb1565b610a2d565b610302610ded565b6005816040516103129190613f1d565b9081526040519081900360200190205460ff166103765760405162461bcd60e51b815260206004820152601760248201527f50726f76696465722068617368206e6f7420666f756e6400000000000000000060448201526064015b60405180910390fd5b6005816040516103869190613f1d565b908152604051908190036020019020805460ff191690556103a8600682610e47565b7f6f594b4b4f0580c66aabba75218163a99fa1fe330a171cc9e2bfd8e167db22f8816040516103d79190613a91565b60405180910390a150565b600681815481106103f257600080fd5b90600052602060002001600091509050805461040d90613f39565b80601f016020809104026020016040519081016040528092919081815260200182805461043990613f39565b80156104865780601f1061045b57610100808354040283529160200191610486565b820191906000526020600020905b81548152906001019060200180831161046957829003601f168201915b505050505081565b606060038054806020026020016040519081016040528092919081815260200182805480156104dc57602002820191906000526020600020905b8154815260200190600101908083116104c8575b5050505050905090565b6104ee610ded565b60008181526004602052604090205460ff16156105465760405162461bcd60e51b815260206004820152601660248201527510dd5c9c995b98de48185b1c9958591e48185919195960521b604482015260640161036d565b6003805460018082019092557fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b01829055600082815260046020908152604091829020805460ff1916909317909255518281527f15e2bb2d9733845f72b6cc683c4d7bafd2d79cfd5c160cb1be756b6faa97dbdc91016103d7565b6105c9610ded565b6105d36000611001565b565b600080336001600160a01b037f000000000000000000000000ff0149799631d7a5bde2e7ea9b306c42b3d9a9ca16146106475760405162461bcd60e51b815260206004820152601460248201527313db9b1e48195cd8dc9bddc818d85b8818d85b1b60621b604482015260640161036d565b6000806106696106578680613f73565b61066460e0890189613f73565b611051565b91509150610681828661067b90613fb9565b83611124565b600082604001516040516020016106989190613f1d565b6040516020818303038152906040528051906020012090506106b981611396565b60808301516000906106cb90826114b1565b60019890975095505050505050565b6106e2610ded565b60028190556040518181527f11e8dda5412ecab251b7783921be2eb37fdf13b389fc7f671f2cee11a1dfefea906020016103d7565b61071f610ded565b60058160405161072f9190613f1d565b9081526040519081900360200190205460ff161561078f5760405162461bcd60e51b815260206004820152601b60248201527f50726f7669646572206861736820616c72656164792061646465640000000000604482015260640161036d565b60016005826040516107a19190613f1d565b908152604051908190036020019020805491151560ff19909216919091179055600680546001810182556000919091527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f016107fd82826140d3565b507fc9e1c78701d1853eeb1eb35ad3449e4b336281f672d5837243d21239dc6f4096816040516103d79190613a91565b60606006805480602002602001604051908101604052809291908181526020016000905b828210156108fd57838290600052602060002001805461087090613f39565b80601f016020809104026020016040519081016040528092919081815260200182805461089c90613f39565b80156108e95780601f106108be576101008083540402835291602001916108e9565b820191906000526020600020905b8154815290600101906020018083116108cc57829003601f168201915b505050505081526020019060010190610851565b50505050905090565b61090e610ded565b60008181526004602052604090205460ff166109615760405162461bcd60e51b815260206004820152601260248201527110dd5c9c995b98de481b9bdd08185919195960721b604482015260640161036d565b61096c6003826114cb565b60008181526004602052604090819020805460ff19169055517fa3c6a408fb053e3ef5e9bcd22e3574324a5ae99fad263cc4080da481842d3d4c906103d79083815260200190565b6109bc610ded565b6001600160a01b038116610a215760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840161036d565b610a2a81611001565b50565b6000808211610a905760405162461bcd60e51b815260206004820152602960248201527f5265717569726564207468726573686f6c64206d75737420626520677265617460448201526806572207468616e20360bc1b606482015260840161036d565b8251821115610b155760405162461bcd60e51b8152602060048201526044602482018190527f5265717569726564207468726573686f6c64206d757374206265206c65737320908201527f7468616e206f7220657175616c20746f206e756d626572206f66207769746e656064820152637373657360e01b608482015260a40161036d565b60008460200151602001515111610b5e5760405162461bcd60e51b815260206004820152600d60248201526c4e6f207369676e61747572657360981b604482015260640161036d565b604080518082019091526020808601805151835251810151908201528451600090610b88906115f3565b602087015151519091508114610be05760405162461bcd60e51b815260206004820152601c60248201527f436c61696d496e666f206861736820646f65736e2774206d6174636800000000604482015260640161036d565b80610c265760405162461bcd60e51b8152602060048201526016602482015275436c61696d496e666f2068617368206973207a65726f60501b604482015260640161036d565b6000610c3183611636565b90508481511015610c955760405162461bcd60e51b815260206004820152602860248201527f4665776572207369676e617475726573207468616e20726571756972656420746044820152671a1c995cda1bdb1960c21b606482015260840161036d565b600081516001600160401b03811115610cb057610cb06138d2565b604051908082528060200260200182016040528015610cd9578160200160208202803683370190505b5090506000805b8351811015610d81576000848281518110610cfd57610cfd614192565b60200260200101519050610d1a818561170f90919063ffffffff16565b15610d255750610d6f565b610d2f8a8261170f565b15610d6d5780848481518110610d4757610d47614192565b6001600160a01b039092166020928302919091019091015282610d69816141be565b9350505b505b80610d79816141be565b915050610ce0565b5086811015610dde5760405162461bcd60e51b815260206004820152602360248201527f4e6f7420656e6f7567682076616c6964207769746e657373207369676e61747560448201526272657360e81b606482015260840161036d565b50600198975050505050505050565b6000546001600160a01b031633146105d35760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161036d565b600080610f2484805480602002602001604051908101604052809291908181526020016000905b82821015610f1a578382906000526020600020018054610e8d90613f39565b80601f0160208091040260200160405190810160405280929190818152602001828054610eb990613f39565b8015610f065780601f10610edb57610100808354040283529160200191610f06565b820191906000526020600020905b815481529060010190602001808311610ee957829003601f168201915b505050505081526020019060010190610e6e565b5050505084611725565b9150915080610f6c5760405162461bcd60e51b815260206004820152601460248201527329ba3934b733903737ba1034b71030b93930bc9760611b604482015260640161036d565b8354600090610f7d906001906141d7565b9050808314610fcc57848181548110610f9857610f98614192565b90600052602060002001858481548110610fb457610fb4614192565b906000526020600020019081610fca91906141ea565b505b84805480610fdc57610fdc6142bc565b600190038181906000526020600020016000610ff89190613858565b90555050505050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b61108a6040518060c001604052806060815260200160608152602001606081526020016060815260200160608152602001606081525090565b600080611099868801886142d2565b905060006110a78686611796565b90506110b582826001610a2d565b506110bf826117a4565b93506110ce8460a001516118c7565b6111125760405162461bcd60e51b815260206004820152601560248201527409cde40ecc2d8d2c840e0e4deecd2c8cae490c2e6d605b1b604482015260640161036d565b81604001519250505094509492505050565b6000670de0b6b3a76400008360c0015184604001516111439190614306565b61114d9190614333565b9050600083602001516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611193573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111b79190614347565b85519091506000906111cc9060ff84166114b1565b90508281101561121e5760405162461bcd60e51b815260206004820152601860248201527f496e636f7272656374207061796d656e7420616d6f756e740000000000000000604482015260640161036d565b83156112bc576000866060015160405160200161123b9190613f1d565b60405160208183030381529060405280519060200120905061126a8660800151611264836118f2565b90611ab7565b6112b65760405162461bcd60e51b815260206004820152601b60248201527f496e636f7272656374207061796d656e7420726563697069656e740000000000604482015260640161036d565b5061131b565b608085015160608701516112cf91611ab7565b61131b5760405162461bcd60e51b815260206004820152601b60248201527f496e636f7272656374207061796d656e7420726563697069656e740000000000604482015260640161036d565b600060025461132d8860200151611b10565b611337919061436a565b9050856060015181101561138d5760405162461bcd60e51b815260206004820152601b60248201527f496e636f7272656374207061796d656e742074696d657374616d700000000000604482015260640161036d565b50505050505050565b60015460405163169394bb60e01b8152600481018390526001600160a01b039091169063169394bb90602401602060405180830381865afa1580156113df573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611403919061437d565b156114505760405162461bcd60e51b815260206004820152601f60248201527f4e756c6c69666965722068617320616c7265616479206265656e207573656400604482015260640161036d565b600154604051632dea6f9960e11b8152600481018390526001600160a01b0390911690635bd4df3290602401600060405180830381600087803b15801561149657600080fd5b505af11580156114aa573d6000803e3d6000fd5b5050505050565b60006114c283601760f91b84611ccc565b90505b92915050565b6000806115278480548060200260200160405190810160405280929190818152602001828054801561151c57602002820191906000526020600020905b815481526020019060010190808311611508575b505050505084611ea9565b91509150806115705760405162461bcd60e51b8152602060048201526015602482015274313cba32b99999103737ba1034b71030b93930bc9760591b604482015260640161036d565b8354600090611581906001906141d7565b90508083146115c65784818154811061159c5761159c614192565b90600052602060002001548584815481106115b9576115b9614192565b6000918252602090912001555b848054806115d6576115d66142bc565b600190038181906000526020600020016000905590555050505050565b6000808260000151836020015184604001516040516020016116179392919061439a565b60408051601f1981840301815291905280516020909101209392505050565b606060006116478360000151611ef9565b905060008360200151516001600160401b03811115611668576116686138d2565b604051908082528060200260200182016040528015611691578160200160208202803683370190505b50905060005b846020015151811015611707576116cb83866020015183815181106116be576116be614192565b6020026020010151611f85565b8282815181106116dd576116dd614192565b6001600160a01b0390921660209283029190910190910152806116ff816141be565b915050611697565b509392505050565b60008061171c8484611fce565b95945050505050565b81516000908190815b8181101561178357848051906020012086828151811061175057611750614192565b602002602001015180519060200120036117715792506001915061178f9050565b8061177b816141be565b91505061172e565b50600019600092509250505b9250929050565b60606114c2828401846143f5565b6117dd6040518060c001604052806060815260200160608152602001606081526020016060815260200160608152602001606081525090565b60006117f483600001516040015160086001612030565b90506040518060c001604052808260038151811061181457611814614192565b602002602001015181526020018260048151811061183457611834614192565b602002602001015181526020018260058151811061185457611854614192565b602002602001015181526020018260068151811061187457611874614192565b602002602001015181526020018260018151811061189457611894614192565b60200260200101518152602001826007815181106118b4576118b4614192565b6020026020010151815250915050919050565b60006005826040516118d99190613f1d565b9081526040519081900360200190205460ff1692915050565b604080518082018252601081526f181899199a1a9b1b9c1cb0b131b232b360811b6020820152815160428082526080820190935260609260009190602082018180368337019050509050600360fc1b8160008151811061195457611954614192565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061198357611983614192565b60200101906001600160f81b031916908160001a90535060005b6020811015611707578260048683602081106119bb576119bb614192565b1a60f81b6001600160f81b031916901c60f81c60ff16815181106119e1576119e1614192565b01602001516001600160f81b031916826119fc836002614306565b611a0790600261436a565b81518110611a1757611a17614192565b60200101906001600160f81b031916908160001a90535082858260208110611a4157611a41614192565b825191901a600f16908110611a5857611a58614192565b01602001516001600160f81b03191682611a73836002614306565b611a7e90600361436a565b81518110611a8e57611a8e614192565b60200101906001600160f81b031916908160001a90535080611aaf816141be565b91505061199d565b600081604051602001611aca9190613f1d565b6040516020818303038152906040528051906020012083604051602001611af19190613f1d565b6040516020818303038152906040528051906020012014905092915050565b6000611b1a613892565b60008060005b8551811015611c0357858181518110611b3b57611b3b614192565b6020910101516001600160f81b031916602d60f81b1480611b805750858181518110611b6957611b69614192565b6020910101516001600160f81b031916601d60f91b145b80611baf5750858181518110611b9857611b98614192565b6020910101516001600160f81b031916601560fa1b145b15611bf157611bbf868383612f01565b848460068110611bd157611bd1614192565b6020020152611be181600161436a565b915082611bed816141be565b9350505b80611bfb816141be565b915050611b20565b508451611c139086908390612f01565b838360068110611c2557611c25614192565b602002015260058214611c705760405162461bcd60e51b8152602060048201526013602482015272496e76616c6964206461746520737472696e6760681b604482015260640161036d565b61171c611c86600085815b6020020151906114b1565b611c936000866001611c7b565b611ca06000876002611c7b565b611cad6000886003611c7b565b611cba6000896004611c7b565b611cc760008a6005611c7b565b612fcd565b600083818080805b8451811015611e2257603060f81b858281518110611cf457611cf4614192565b01602001516001600160f81b03191610801590611d355750603960f81b858281518110611d2357611d23614192565b01602001516001600160f81b03191611155b15611d78576030858281518110611d4e57611d4e614192565b0160200151611d60919060f81c6141d7565b611d6b85600a614306565b611d75919061436a565b93505b8115611d8c5782611d88816141be565b9350505b876001600160f81b031916858281518110611da957611da9614192565b01602001516001600160f81b03191603611e10578115611e0b5760405162461bcd60e51b815260206004820152601c60248201527f537472696e6720686173206d756c7469706c6520646563696d616c7300000000604482015260640161036d565b600191505b80611e1a816141be565b915050611cd4565b5085821115611e7e5760405162461bcd60e51b815260206004820152602260248201527f537472696e672068617320746f6f206d616e7920646563696d616c20706c6163604482015261657360f01b606482015260840161036d565b611e8882876141d7565b611e9390600a61450d565b611e9d9084614306565b98975050505050505050565b81516000908190815b818110156117835784868281518110611ecd57611ecd614192565b602002602001015103611ee75792506001915061178f9050565b80611ef1816141be565b915050611eb2565b6060611f298260000151604051602001611f1591815260200190565b604051602081830303815290604052613029565b611f3683602001516131ec565b611f49846040015163ffffffff16613355565b611f5c856060015163ffffffff16613355565b604051602001611f6f9493929190614519565b6040516020818303038152906040529050919050565b600080611f928451613355565b84604051602001611fa4929190614592565b604051602081830303815290604052805190602001209050611fc68184613480565b949350505050565b81516000908190815b8181101561178357846001600160a01b0316868281518110611ffb57611ffb614192565b60200260200101516001600160a01b03160361201e5792506001915061178f9050565b80612028816141be565b915050611fd7565b606060008360ff161161208f5760405162461bcd60e51b815260206004820152602160248201527f4d61782076616c756573206d7573742062652067726561746572207468616e206044820152600360fc1b606482015260840161036d565b83600080808080806120a28a60026145ed565b60ff166001600160401b038111156120bc576120bc6138d2565b6040519080825280602002602001820160405280156120e5578160200160208202803683370190505b50905060005b604051806040016040528060138152602001723d9131b7b73a32bc3a20b2323932b9b9911d1160691b815250518110156121c457604051806040016040528060138152602001723d9131b7b73a32bc3a20b2323932b9b9911d1160691b815250818151811061215c5761215c614192565b01602001516001600160f81b03191688612176838a61436a565b8151811061218657612186614192565b01602001516001600160f81b031916146121b25760405162461bcd60e51b815260040161036d90614610565b806121bc816141be565b9150506120eb565b50604051806040016040528060138152602001723d9131b7b73a32bc3a20b2323932b9b9911d1160691b81525051866121fd919061436a565b95508593505b865186108015612273575086868151811061222057612220614192565b6020910101516001600160f81b031916601160f91b1480156122715750866122496001886141d7565b8151811061225957612259614192565b6020910101516001600160f81b031916601760fa1b14155b155b1561228a5785612282816141be565b965050612203565b865186106122aa5760405162461bcd60e51b815260040161036d90614610565b8592508383036123125760405162461bcd60e51b815260206004820152602d60248201527f45787472616374696f6e206661696c65642e20456d70747920636f6e7465787460448201526c416464726573732076616c756560981b606482015260840161036d565b838161231f876002614306565b8151811061232f5761232f614192565b60209081029190910101528281612347876002614306565b61235290600161436a565b8151811061236257612362614192565b602090810291909101015284612377816141be565b9550612386905060028761436a565b955060005b604051806040016040528060128152602001711131b7b73a32bc3a26b2b9b9b0b3b2911d1160711b8152505181101561246257604051806040016040528060128152602001711131b7b73a32bc3a26b2b9b9b0b3b2911d1160711b81525081815181106123fa576123fa614192565b01602001516001600160f81b03191688612414838a61436a565b8151811061242457612424614192565b01602001516001600160f81b031916146124505760405162461bcd60e51b815260040161036d9061465b565b8061245a816141be565b91505061238b565b50604051806040016040528060128152602001711131b7b73a32bc3a26b2b9b9b0b3b2911d1160711b815250518661249a919061436a565b95508593505b86518610801561251057508686815181106124bd576124bd614192565b6020910101516001600160f81b031916601160f91b14801561250e5750866124e66001886141d7565b815181106124f6576124f6614192565b6020910101516001600160f81b031916601760fa1b14155b155b15612527578561251f816141be565b9650506124a0565b865186106125475760405162461bcd60e51b815260040161036d9061465b565b8592508383036125af5760405162461bcd60e51b815260206004820152602d60248201527f45787472616374696f6e206661696c65642e20456d70747920636f6e7465787460448201526c4d6573736167652076616c756560981b606482015260840161036d565b83816125bc876002614306565b815181106125cc576125cc614192565b602090810291909101015282816125e4876002614306565b6125ef90600161436a565b815181106125ff576125ff614192565b602090810291909101015284612614816141be565b9550612623905060028761436a565b955060005b604051806040016040528060188152602001771132bc3a3930b1ba32b22830b930b6b2ba32b939911d3d9160411b8152505181101561275457604051806040016040528060188152602001771132bc3a3930b1ba32b22830b930b6b2ba32b939911d3d9160411b81525081815181106126a3576126a3614192565b01602001516001600160f81b031916886126bd838a61436a565b815181106126cd576126cd614192565b01602001516001600160f81b031916146127425760405162461bcd60e51b815260206004820152603060248201527f45787472616374696f6e206661696c65642e204d616c666f726d65642065787460448201526f726163746564506172616d657465727360801b606482015260840161036d565b8061274c816141be565b915050612628565b50604051806040016040528060188152602001771132bc3a3930b1ba32b22830b930b6b2ba32b939911d3d9160411b8152505186612792919061436a565b9550600091505b8651861015612b3c578686815181106127b4576127b4614192565b6020910101516001600160f81b031916601160f91b1480156128055750866127dd6001886141d7565b815181106127ed576127ed614192565b6020910101516001600160f81b031916601760fa1b14155b61281b5785612813816141be565b965050612799565b81612923578661282c87600161436a565b8151811061283c5761283c614192565b6020910101516001600160f81b031916601d60f91b14801561288c57508661286587600261436a565b8151811061287557612875614192565b6020910101516001600160f81b031916601160f91b145b6128e45760405162461bcd60e51b815260206004820152602360248201527f45787472616374696f6e206661696c65642e204d616c666f726d65642064617460448201526261203160e81b606482015260840161036d565b6128ef60038761436a565b9550600191508581612902876002614306565b8151811061291257612912614192565b602002602001018181525050612799565b60008761293188600161436a565b8151811061294157612941614192565b6020910101516001600160f81b031916600b60fa1b14801561299157508761296a88600261436a565b8151811061297a5761297a614192565b6020910101516001600160f81b031916601160f91b145b90506000886129a189600161436a565b815181106129b1576129b1614192565b6020910101516001600160f81b031916607d60f81b148015612a015750886129da89600261436a565b815181106129ea576129ea614192565b6020910101516001600160f81b031916600b60fa1b145b90508180612a0c5750805b612a645760405162461bcd60e51b815260206004820152602360248201527f45787472616374696f6e206661696c65642e204d616c666f726d65642064617460448201526230901960e91b606482015260840161036d565b8783612a71896002614306565b612a7c90600161436a565b81518110612a8c57612a8c614192565b602090810291909101015286612aa1816141be565b9750508115612b21578b60ff168703612b0b5760405162461bcd60e51b815260206004820152602660248201527f45787472616374696f6e206661696c65642e204578636565646564206d61782060448201526576616c75657360d01b606482015260840161036d565b612b1660038961436a565b975060009350612b35565b612b2c60038961436a565b97505050612b3c565b5050612799565b8815612d495760005b6040518060400160405280601081526020016f11383937bb34b232b92430b9b4111d1160811b81525051811015612c5a576040518060400160405280601081526020016f11383937bb34b232b92430b9b4111d1160811b8152508181518110612bb057612bb0614192565b01602001516001600160f81b03191688612bca838a61436a565b81518110612bda57612bda614192565b01602001516001600160f81b03191614612c485760405162461bcd60e51b815260206004820152602960248201527f45787472616374696f6e206661696c65642e204d616c666f726d65642070726f6044820152680ecd2c8cae490c2e6d60bb1b606482015260840161036d565b80612c52816141be565b915050612b45565b506040518060400160405280601081526020016f11383937bb34b232b92430b9b4111d1160811b8152505186612c90919061436a565b95508581612c9f876002614306565b81518110612caf57612caf614192565b6020026020010181815250505b865186108015612cf15750868681518110612cd957612cd9614192565b6020910101516001600160f81b031916601160f91b14155b15612d085785612d00816141be565b965050612cbc565b8581612d15876002614306565b612d2090600161436a565b81518110612d3057612d30614192565b602090810291909101015284612d45816141be565b9550505b6000856001600160401b03811115612d6357612d636138d2565b604051908082528060200260200182016040528015612d9657816020015b6060815260200190600190039081612d815790505b50905060005b86811015612ef15782612db0826002614306565b81518110612dc057612dc0614192565b6020026020010151955082816002612dd89190614306565b612de390600161436a565b81518110612df357612df3614192565b6020026020010151945060008686612e0b91906141d7565b6001600160401b03811115612e2257612e226138d2565b6040519080825280601f01601f191660200182016040528015612e4c576020820181803683370190505b509050865b86811015612ebe578a8181518110612e6b57612e6b614192565b01602001516001600160f81b03191682612e858a846141d7565b81518110612e9557612e95614192565b60200101906001600160f81b031916908160001a90535080612eb6816141be565b915050612e51565b5080838381518110612ed257612ed2614192565b6020026020010181905250508080612ee9906141be565b915050612d9c565b509b9a5050505050505050505050565b6060836000612f1085856141d7565b6001600160401b03811115612f2757612f276138d2565b6040519080825280601f01601f191660200182016040528015612f51576020820181803683370190505b509050845b84811015612fc357828181518110612f7057612f70614192565b01602001516001600160f81b03191682612f8a88846141d7565b81518110612f9a57612f9a614192565b60200101906001600160f81b031916908160001a90535080612fbb816141be565b915050612f56565b5095945050505050565b600081612fdb603c85614306565b612fe7610e1087614306565b62015180612ff68b8b8b61349c565b6130009190614306565b61300a919061436a565b613014919061436a565b61301e919061436a565b979650505050505050565b606060008251600261303b9190614306565b6001600160401b03811115613052576130526138d2565b6040519080825280601f01601f19166020018201604052801561307c576020820181803683370190505b5060408051808201909152601081526f181899199a1a9b1b9c1cb0b131b232b360811b602082015290915060005b84518110156131c2578182518683815181106130c8576130c8614192565b01602001516130da919060f81c614333565b815181106130ea576130ea614192565b01602001516001600160f81b03191683613105836002614306565b8151811061311557613115614192565b60200101906001600160f81b031916908160001a90535081825186838151811061314157613141614192565b0160200151613153919060f81c6146a6565b8151811061316357613163614192565b01602001516001600160f81b0319168361317e836002614306565b61318990600161436a565b8151811061319957613199614192565b60200101906001600160f81b031916908160001a905350806131ba816141be565b9150506130aa565b50816040516020016131d491906146ba565b60405160208183030381529060405292505050919050565b60408051602880825260608281019093526000919060208201818036833701905050905060005b601481101561332c5760006132298260136141d7565b613234906008614306565b61323f90600261450d565b613252906001600160a01b038716614333565b60f81b9050600060108260f81c61326991906146e4565b60f81b905060008160f81c601061328091906145ed565b8360f81c61328e9190614706565b60f81b905061329c826135cd565b856132a8866002614306565b815181106132b8576132b8614192565b60200101906001600160f81b031916908160001a9053506132d8816135cd565b856132e4866002614306565b6132ef90600161436a565b815181106132ff576132ff614192565b60200101906001600160f81b031916908160001a9053505050508080613324906141be565b915050613213565b508060405160200161333e91906146ba565b604051602081830303815290604052915050919050565b60608160000361337c5750506040805180820190915260018152600360fc1b602082015290565b8160005b81156133a65780613390816141be565b915061339f9050600a83614333565b9150613380565b6000816001600160401b038111156133c0576133c06138d2565b6040519080825280601f01601f1916602001820160405280156133ea576020820181803683370190505b509050815b8515613477576134006001826141d7565b9050600061340f600a88614333565b61341a90600a614306565b61342490886141d7565b61342f90603061471f565b905060008160f81b90508084848151811061344c5761344c614192565b60200101906001600160f81b031916908160001a90535061346e600a89614333565b975050506133ef565b50949350505050565b600080600061348f8585613608565b915091506117078161364a565b60006107b28410156134ad57600080fd5b838383600062253d8c60046064600c6134c7600e88614738565b6134d19190614758565b6134dd88611324614786565b6134e79190614786565b6134f19190614758565b6134fc9060036147ae565b6135069190614758565b600c80613514600e88614738565b61351e9190614758565b61352990600c6147ae565b613534600288614738565b61353e9190614738565b61354a9061016f6147ae565b6135549190614758565b6004600c613563600e89614738565b61356d9190614758565b613579896112c0614786565b6135839190614786565b61358f906105b56147ae565b6135999190614758565b6135a5617d4b87614738565b6135af9190614786565b6135b99190614786565b6135c39190614738565b611e9d9190614738565b6000600a60f883901c10156135f4576135eb60f883901c603061471f565b60f81b92915050565b6135eb60f883901c605761471f565b919050565b600080825160410361363e5760208301516040840151606085015160001a61363287828585613794565b9450945050505061178f565b5060009050600261178f565b600081600481111561365e5761365e6147de565b036136665750565b600181600481111561367a5761367a6147de565b036136c75760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e61747572650000000000000000604482015260640161036d565b60028160048111156136db576136db6147de565b036137285760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e67746800604482015260640161036d565b600381600481111561373c5761373c6147de565b03610a2a5760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b606482015260840161036d565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08311156137cb575060009050600361384f565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa15801561381f573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166138485760006001925092505061384f565b9150600090505b94509492505050565b50805461386490613f39565b6000825580601f10613874575050565b601f016020900490600052602060002090810190610a2a91906138b9565b6040518060c001604052806006905b60608152602001906001900390816138a15790505090565b5b808211156138ce57600081556001016138ba565b5090565b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b038111828210171561390a5761390a6138d2565b60405290565b604051606081016001600160401b038111828210171561390a5761390a6138d2565b60405161010081016001600160401b038111828210171561390a5761390a6138d2565b604051601f8201601f191681016001600160401b038111828210171561397d5761397d6138d2565b604052919050565b600082601f83011261399657600080fd5b81356001600160401b038111156139af576139af6138d2565b6139c2601f8201601f1916602001613955565b8181528460208386010111156139d757600080fd5b816020850160208301376000918101602001919091529392505050565b600060208284031215613a0657600080fd5b81356001600160401b03811115613a1c57600080fd5b611fc684828501613985565b600060208284031215613a3a57600080fd5b5035919050565b60005b83811015613a5c578181015183820152602001613a44565b50506000910152565b60008151808452613a7d816020860160208601613a41565b601f01601f19169290920160200192915050565b6020815260006114c26020830184613a65565b6020808252825182820181905260009190848201906040850190845b81811015613adc57835183529284019291840191600101613ac0565b50909695505050505050565b600060208284031215613afa57600080fd5b81356001600160401b03811115613b1057600080fd5b82016101008185031215613b2357600080fd5b9392505050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015613b7f57603f19888603018452613b6d858351613a65565b94509285019290850190600101613b51565b5092979650505050505050565b80356001600160a01b038116811461360357600080fd5b600060208284031215613bb557600080fd5b6114c282613b8c565b803563ffffffff8116811461360357600080fd5b60006001600160401b03821115613beb57613beb6138d2565b5060051b60200190565b600082601f830112613c0657600080fd5b81356020613c1b613c1683613bd2565b613955565b82815260059290921b84018101918181019086841115613c3a57600080fd5b8286015b84811015613c795780356001600160401b03811115613c5d5760008081fd5b613c6b8986838b0101613985565b845250918301918301613c3e565b509695505050505050565b600081830360a0811215613c9757600080fd5b604051604081016001600160401b038282108183111715613cba57613cba6138d2565b816040528294506080841215613ccf57600080fd5b613cd76138e8565b935085358452613ce960208701613b8c565b6020850152613cfa60408701613bbe565b6040850152613d0b60608701613bbe565b606085015292825260808501359280841115613d2657600080fd5b5050613d3485838601613bf5565b6020820152505092915050565b8015158114610a2a57600080fd5b803561360381613d41565b600060608284031215613d6c57600080fd5b613d74613910565b905081356001600160401b0380821115613d8d57600080fd5b9083019060608286031215613da157600080fd5b613da9613910565b823582811115613db857600080fd5b613dc487828601613985565b825250602083013582811115613dd957600080fd5b613de587828601613985565b602083015250604083013582811115613dfd57600080fd5b613e0987828601613985565b60408301525083526020840135915080821115613e2557600080fd5b50613e3284828501613c84565b602083015250613e4460408301613d4f565b604082015292915050565b600082601f830112613e6057600080fd5b81356020613e70613c1683613bd2565b82815260059290921b84018101918181019086841115613e8f57600080fd5b8286015b84811015613c7957613ea481613b8c565b8352918301918301613e93565b600080600060608486031215613ec657600080fd5b83356001600160401b0380821115613edd57600080fd5b613ee987838801613d5a565b94506020860135915080821115613eff57600080fd5b50613f0c86828701613e4f565b925050604084013590509250925092565b60008251613f2f818460208701613a41565b9190910192915050565b600181811c90821680613f4d57607f821691505b602082108103613f6d57634e487b7160e01b600052602260045260246000fd5b50919050565b6000808335601e19843603018112613f8a57600080fd5b8301803591506001600160401b03821115613fa457600080fd5b60200191503681900382131561178f57600080fd5b60006101008236031215613fcc57600080fd5b613fd4613932565b82356001600160401b0380821115613feb57600080fd5b613ff736838701613985565b835261400560208601613b8c565b60208401526040850135604084015260608501356060840152608085013591508082111561403257600080fd5b61403e36838701613985565b608084015260a085013560a084015260c085013560c084015260e085013591508082111561406b57600080fd5b5061407836828601613985565b60e08301525092915050565b601f8211156140ce57600081815260208120601f850160051c810160208610156140ab5750805b601f850160051c820191505b818110156140ca578281556001016140b7565b5050505b505050565b81516001600160401b038111156140ec576140ec6138d2565b614100816140fa8454613f39565b84614084565b602080601f831160018114614135576000841561411d5750858301515b600019600386901b1c1916600185901b1785556140ca565b600085815260208120601f198616915b8281101561416457888601518255948401946001909101908401614145565b50858210156141825787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000600182016141d0576141d06141a8565b5060010190565b818103818111156114c5576114c56141a8565b8181036141f5575050565b6141ff8254613f39565b6001600160401b03811115614216576142166138d2565b614224816140fa8454613f39565b6000601f82116001811461425857600083156142405750848201545b600019600385901b1c1916600184901b1784556114aa565b600085815260209020601f19841690600086815260209020845b838110156142925782860154825560019586019590910190602001614272565b50858310156141825793015460001960f8600387901b161c19169092555050600190811b01905550565b634e487b7160e01b600052603160045260246000fd5b6000602082840312156142e457600080fd5b81356001600160401b038111156142fa57600080fd5b611fc684828501613d5a565b80820281158282048414176114c5576114c56141a8565b634e487b7160e01b600052601260045260246000fd5b6000826143425761434261431d565b500490565b60006020828403121561435957600080fd5b815160ff81168114613b2357600080fd5b808201808211156114c5576114c56141a8565b60006020828403121561438f57600080fd5b8151613b2381613d41565b600084516143ac818460208901613a41565b600560f91b90830181815285519091906143cd816001850160208a01613a41565b600192019182015283516143e8816002840160208801613a41565b0160020195945050505050565b60006020828403121561440757600080fd5b81356001600160401b0381111561441d57600080fd5b611fc684828501613e4f565b600181815b8085111561446457816000190482111561444a5761444a6141a8565b8085161561445757918102915b93841c939080029061442e565b509250929050565b60008261447b575060016114c5565b81614488575060006114c5565b816001811461449e57600281146144a8576144c4565b60019150506114c5565b60ff8411156144b9576144b96141a8565b50506001821b6114c5565b5060208310610133831016604e8410600b84101617156144e7575081810a6114c5565b6144f18383614429565b8060001904821115614505576145056141a8565b029392505050565b60006114c2838361446c565b6000855161452b818460208a01613a41565b600560f91b908301818152865190919061454c816001850160208b01613a41565b600192019182018190528551614569816002850160208a01613a41565b60029201918201528351614584816003840160208801613a41565b016003019695505050505050565b7f19457468657265756d205369676e6564204d6573736167653a0a0000000000008152600083516145ca81601a850160208801613a41565b8351908301906145e181601a840160208801613a41565b01601a01949350505050565b60ff8181168382160290811690818114614609576146096141a8565b5092915050565b6020808252602b908201527f45787472616374696f6e206661696c65642e204d616c666f726d656420636f6e60408201526a746578744164647265737360a81b606082015260800190565b6020808252602b908201527f45787472616374696f6e206661696c65642e204d616c666f726d656420636f6e60408201526a746578744d65737361676560a81b606082015260800190565b6000826146b5576146b561431d565b500690565b61060f60f31b8152600082516146d7816002850160208701613a41565b9190910160020192915050565b600060ff8316806146f7576146f761431d565b8060ff84160491505092915050565b60ff82811682821603908111156114c5576114c56141a8565b60ff81811683821601908111156114c5576114c56141a8565b8181036000831280158383131683831282161715614609576146096141a8565b6000826147675761476761431d565b600160ff1b821460001984141615614781576147816141a8565b500590565b80820182811260008312801582168215821617156147a6576147a66141a8565b505092915050565b80820260008212600160ff1b841416156147ca576147ca6141a8565b81810583148215176114c5576114c56141a8565b634e487b7160e01b600052602160045260246000fdfea2646970667358221220925e45366b7c24daac4a0b502f9080dc8dc50cb5b346c53338f71e1c99ed467764736f6c63430008120033

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

000000000000000000000000ff0149799631d7a5bde2e7ea9b306c42b3d9a9ca0000000000000000000000009cf61278c2cc9c41578fe3a0ed375e9870d514f1000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000001c4ae21aac0c6549d71dd96035b7e0bdb6c79ebdba8891b666115bc976d16a29e000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000000000000000000000000000000000000000056000000000000000000000000000000000000000000000000000000000000005e0000000000000000000000000000000000000000000000000000000000000066000000000000000000000000000000000000000000000000000000000000000423078626262346436383133633163636163373235333637333039346365346331653132326665333538363832333932383531636661333332666538333539623866640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042307865633961386333666335323166656639613830356665393762616262353035383536353639613133376338386464346563336133633239333038326134646264000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004230786134313862643739633762376434393336306166353665366165393661653038373734323437346461303366656130653939623931396530393361386165626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000423078353264313833356534376337636463616462356631356538316262316338373064333034613564303531316634643561653830333763333163323463663064330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042307861613938323563353863626230613662343266323061376439323662623365666636303638323433666564386461376638366261663034363766376264343562000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004230783639343230363237306432326634376664316630616364313232343237303061636536376264366636323930323661386163343331353763356430623962353300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000423078326430653962366134633130666434633564616130643162386537623537613830306465376266643230393638666561333636643365663266633638616533360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042307866383537616137363134303232326234336364363039366632646263653366393532626535393862306237343966316432353064386462643135343064613933000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004230783335356534333561363139353137396461306238386238393462616262383036633561386364383630313064353136323736626437383130653031336630306100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000423078326232666238386235303366313836326436633764353037343635333535656234396164353465336136376262316332623032313661663365643366343264370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042307831346465386235353033613461363937336262616139616133303165633738343365396263616133616630356536363130623534633666636335366161343235000000000000000000000000000000000000000000000000000000000000

-----Decoded View---------------
Arg [0] : _escrow (address): 0xFF0149799631D7A5bdE2e7eA9b306c42b3d9a9ca
Arg [1] : _nullifierRegistry (address): 0x9cF61278C2Cc9C41578fE3a0ED375E9870D514F1
Arg [2] : _timestampBuffer (uint256): 30
Arg [3] : _currencies (bytes32[]): System.Byte[]
Arg [4] : _providerHashes (string[]): 0xbbb4d6813c1ccac7253673094ce4c1e122fe358682392851cfa332fe8359b8fd,0xec9a8c3fc521fef9a805fe97babb505856569a137c88dd4ec3a3c293082a4dbd,0xa418bd79c7b7d49360af56e6ae96ae087742474da03fea0e99b919e093a8aebc,0x52d1835e47c7cdcadb5f15e81bb1c870d304a5d0511f4d5ae8037c31c24cf0d3,0xaa9825c58cbb0a6b42f20a7d926bb3eff6068243fed8da7f86baf0467f7bd45b,0x694206270d22f47fd1f0acd12242700ace67bd6f629026a8ac43157c5d0b9b53,0x2d0e9b6a4c10fd4c5daa0d1b8e7b57a800de7bfd20968fea366d3ef2fc68ae36,0xf857aa76140222b43cd6096f2dbce3f952be598b0b749f1d250d8dbd1540da93,0x355e435a6195179da0b88b894babb806c5a8cd86010d516276bd7810e013f00a,0x2b2fb88b503f1862d6c7d507465355eb49ad54e3a67bb1c2b0216af3ed3f42d7,0x14de8b5503a4a6973bbaa9aa301ec7843e9bcaa3af05e6610b54c6fcc56aa425

-----Encoded View---------------
63 Constructor Arguments found :
Arg [0] : 000000000000000000000000ff0149799631d7a5bde2e7ea9b306c42b3d9a9ca
Arg [1] : 0000000000000000000000009cf61278c2cc9c41578fe3a0ed375e9870d514f1
Arg [2] : 000000000000000000000000000000000000000000000000000000000000001e
Arg [3] : 00000000000000000000000000000000000000000000000000000000000000a0
Arg [4] : 00000000000000000000000000000000000000000000000000000000000000e0
Arg [5] : 0000000000000000000000000000000000000000000000000000000000000001
Arg [6] : c4ae21aac0c6549d71dd96035b7e0bdb6c79ebdba8891b666115bc976d16a29e
Arg [7] : 000000000000000000000000000000000000000000000000000000000000000b
Arg [8] : 0000000000000000000000000000000000000000000000000000000000000160
Arg [9] : 00000000000000000000000000000000000000000000000000000000000001e0
Arg [10] : 0000000000000000000000000000000000000000000000000000000000000260
Arg [11] : 00000000000000000000000000000000000000000000000000000000000002e0
Arg [12] : 0000000000000000000000000000000000000000000000000000000000000360
Arg [13] : 00000000000000000000000000000000000000000000000000000000000003e0
Arg [14] : 0000000000000000000000000000000000000000000000000000000000000460
Arg [15] : 00000000000000000000000000000000000000000000000000000000000004e0
Arg [16] : 0000000000000000000000000000000000000000000000000000000000000560
Arg [17] : 00000000000000000000000000000000000000000000000000000000000005e0
Arg [18] : 0000000000000000000000000000000000000000000000000000000000000660
Arg [19] : 0000000000000000000000000000000000000000000000000000000000000042
Arg [20] : 3078626262346436383133633163636163373235333637333039346365346331
Arg [21] : 6531323266653335383638323339323835316366613333326665383335396238
Arg [22] : 6664000000000000000000000000000000000000000000000000000000000000
Arg [23] : 0000000000000000000000000000000000000000000000000000000000000042
Arg [24] : 3078656339613863336663353231666566396138303566653937626162623530
Arg [25] : 3538353635363961313337633838646434656333613363323933303832613464
Arg [26] : 6264000000000000000000000000000000000000000000000000000000000000
Arg [27] : 0000000000000000000000000000000000000000000000000000000000000042
Arg [28] : 3078613431386264373963376237643439333630616635366536616539366165
Arg [29] : 3038373734323437346461303366656130653939623931396530393361386165
Arg [30] : 6263000000000000000000000000000000000000000000000000000000000000
Arg [31] : 0000000000000000000000000000000000000000000000000000000000000042
Arg [32] : 3078353264313833356534376337636463616462356631356538316262316338
Arg [33] : 3730643330346135643035313166346435616538303337633331633234636630
Arg [34] : 6433000000000000000000000000000000000000000000000000000000000000
Arg [35] : 0000000000000000000000000000000000000000000000000000000000000042
Arg [36] : 3078616139383235633538636262306136623432663230613764393236626233
Arg [37] : 6566663630363832343366656438646137663836626166303436376637626434
Arg [38] : 3562000000000000000000000000000000000000000000000000000000000000
Arg [39] : 0000000000000000000000000000000000000000000000000000000000000042
Arg [40] : 3078363934323036323730643232663437666431663061636431323234323730
Arg [41] : 3061636536376264366636323930323661386163343331353763356430623962
Arg [42] : 3533000000000000000000000000000000000000000000000000000000000000
Arg [43] : 0000000000000000000000000000000000000000000000000000000000000042
Arg [44] : 3078326430653962366134633130666434633564616130643162386537623537
Arg [45] : 6138303064653762666432303936386665613336366433656632666336386165
Arg [46] : 3336000000000000000000000000000000000000000000000000000000000000
Arg [47] : 0000000000000000000000000000000000000000000000000000000000000042
Arg [48] : 3078663835376161373631343032323262343363643630393666326462636533
Arg [49] : 6639353262653539386230623734396631643235306438646264313534306461
Arg [50] : 3933000000000000000000000000000000000000000000000000000000000000
Arg [51] : 0000000000000000000000000000000000000000000000000000000000000042
Arg [52] : 3078333535653433356136313935313739646130623838623839346261626238
Arg [53] : 3036633561386364383630313064353136323736626437383130653031336630
Arg [54] : 3061000000000000000000000000000000000000000000000000000000000000
Arg [55] : 0000000000000000000000000000000000000000000000000000000000000042
Arg [56] : 3078326232666238386235303366313836326436633764353037343635333535
Arg [57] : 6562343961643534653361363762623163326230323136616633656433663432
Arg [58] : 6437000000000000000000000000000000000000000000000000000000000000
Arg [59] : 0000000000000000000000000000000000000000000000000000000000000042
Arg [60] : 3078313464653862353530336134613639373362626161396161333031656337
Arg [61] : 3834336539626361613361663035653636313062353463366663633536616134
Arg [62] : 3235000000000000000000000000000000000000000000000000000000000000


Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]

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