ETH Price: $2,093.08 (+1.03%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

1 Internal Transaction and 2 Token Transfers found.

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Method Block
From
To
0x60793d81190359042024-01-18 19:42:47803 days ago1705606967  Contract Creation0 ETH
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions

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

Minimal Proxy Contract for 0x00000000000060d035a8002956b5fb02e3968eec

Contract Name:
Stash

Compiler Version
v0.8.23+commit.f704f362

Optimization Enabled:
Yes with 200 runs

Other Settings:
paris EvmVersion, MIT license

Contract Source Code (Solidity Standard Json-Input format)

// SPDX-License-Identifier: MIT

pragma solidity 0.8.23;

import {IAuction} from "./interfaces/IAuction.sol";
import {IWETH} from "./interfaces/IWETH.sol";
import {OrderType} from "./helpers/Enum.sol";
import {Order, PunkBid} from "./helpers/Struct.sol";
import {MerkleProofLib} from "lib/solady/src/utils/MerkleProofLib.sol";
import {SignatureCheckerLib} from "lib/solady/src/utils/SignatureCheckerLib.sol";
import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol";
import {IERC721} from "forge-std/interfaces/IERC721.sol";
import {IERC1155} from "forge-std/interfaces/IERC1155.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {ILegacyWrappedPunks} from "./interfaces/ILegacyWrappedPunks.sol";
import {ICryptoPunks721} from "./interfaces/ICryptoPunks721.sol";
import {ICryptoPunks} from "./interfaces/ICryptoPunks.sol";
import {IStashFactory} from "./interfaces/IStashFactory.sol";
import {IPunkTransferHelper} from "./interfaces/IPunkTransferHelper.sol";

/**
 * @title Stash
 * @author Yuga Labs
 * @custom:security-contact security@yugalabs.io
 * @notice A multipurpose user deployed contract.
 */
contract Stash {
    // --------------------- STASH EVENTS ---------------------

    /// @dev Emitted when an order is placed.
    event OrderPlaced(Order order);

    /// @dev Emitted when an order is updated.
    event OrderUpdated(Order originalOrder, Order updatedOrder);

    /// @dev Emitted when an order is removed, either because it was filled or the auction was finalized.
    event OrderRemoved(Order order);

    /// @dev Emitted when a punk bid is canceled.
    event PunkBidCanceled(uint256 indexed bidNonce);

    /// @dev Emitted when the Stash's global nonce is incremented, cancelling all Punk bids.
    event AllPunkBidsCanceled();

    /// @dev Emitted when a punk bid is accepted.
    event PunkBidAccepted(uint256 indexed price, uint256 indexed punkIndex);

    // --------------------- CUSTOM ERRORS ---------------------

    /// @dev The punk bid has expired.
    error BidExpired();

    /// @dev The bid either has zero units or does not include the CryptoPunks address.
    error InvalidBid();

    /// @dev The bid has been used or canceled.
    error BidCanceled();

    /// @dev The caller is not authorized to perform this action.
    error Unauthorized();

    /// @dev The order does not exist. It may have been filled, canceled, or never existed.
    error OrderNotFound();

    /// @dev The merkle proof provided does not match the CryptoPunk Bid.
    error InvalidProof();

    /// @dev The vault already has 10 orders for the given payment token.
    error TooManyOrders();

    /// @dev The order type is not supported by the Stash.
    error UnknownOrderType();

    /// @dev The Punk is either not listed for sale, or there was an error paying the caller.
    error FailedToBuyPunk();

    /// @dev This Stash does not have an active bid on the auction that is attempting to process the order.
    error NoBidForAuction();

    /// @dev The provided signature does not match the provided CryptoPunk bid.
    error InvalidSignature();

    /// @dev The caller is not a valid auction contract, as determined by the StashFactory.
    error CallerNotAuction();

    /// @dev Payment to the Stash owner failed.
    error FailedToWithdraw();

    /// @dev The Stash has already been initialized.
    error AlreadyInitialized();

    /// @dev The order is being altered in a way that is not allowed by the order type.
    error InvalidOrderAlteration();

    /// @dev An order or withdrawal is being requested for an amount that exceeds the available balance.
    error RequestExceedsAvailableBalance();

    /// @dev An auction is attempting to pull more funds than the Stash owner has approved from the Stash.
    error CannotTransferMoreThanBidAmount();

    // --------------------- MODIFIERS ---------------------

    modifier onlyOwner() {
        if (msg.sender != owner) revert CallerNotAuction();
        _;
    }

    // ----------------- CONSTANTS & IMMUTABLES -----------------

    uint256 private constant _VERSION = 1;
    bytes32 private constant _COLLECTION_BID_ROOT = bytes32(0);
    bytes32 private constant _ORDER_TYPEHASH =
        keccak256("Order(uint16 numberOfUnits,uint80 pricePerUnit,address auction)");
    bytes32 private constant _PUNK_BID_TYPEHASH = keccak256(
        "PunkBid(Order order,uint256 accountNonce,uint256 bidNonce,uint256 expiration,bytes32 root)Order(uint16 numberOfUnits,uint80 pricePerUnit,address auction)"
    );

    ICryptoPunks private immutable _CRYPTOPUNKS;
    ILegacyWrappedPunks private immutable _LEGACY_WRAPPED_PUNKS;
    ICryptoPunks721 private immutable _CRYPTOPUNKS_721;
    IWETH private immutable _WETH;
    IStashFactory private immutable _STASH_FACTORY;
    IPunkTransferHelper private immutable _PUNK_TRANSFER_HELPER;

    // -------------------- CONSTRUCTOR --------------------

    constructor(
        address stashFactory,
        address weth,
        address punks,
        address legacyWrappedPunks,
        address cryptoPunksWrapped,
        address punkTransferHelper
    ) {
        _STASH_FACTORY = IStashFactory(stashFactory);
        _WETH = IWETH(weth);
        _CRYPTOPUNKS = ICryptoPunks(punks);
        _LEGACY_WRAPPED_PUNKS = ILegacyWrappedPunks(legacyWrappedPunks);
        _CRYPTOPUNKS_721 = ICryptoPunks721(cryptoPunksWrapped);
        _PUNK_TRANSFER_HELPER = IPunkTransferHelper(punkTransferHelper);
        _initialized = true;
    }

    // --------------------- STORAGE ---------------------

    /// @dev Whether or not the contract has been initialized.
    bool private _initialized;

    /// @notice The permanent and immutable owner of the stash. Set once at initialization.
    address public owner;

    /// @notice The current nonce of the stash owner's account. Used for punk bidding, can be incremented to cancel all open bids.
    uint56 public punkAccountNonce;

    /// @notice A mapping of punk bid nonces to the number of times they can be used.
    mapping(uint256 punkBidNonce => uint256 usesRemaining) public punkBidNonceUsesRemaining;

    /// @notice A mapping of punk bid nonces to whether or not they have been used.
    mapping(uint256 punkBidNonce => bool isUsed) public usedPunkBidNonces;

    /// @notice Returns an array of all current orders for a given payment token.
    mapping(address paymentToken => Order[] orders) public paymentTokenToOrders;

    // --------------------- EXTERNAL ---------------------

    // allow receiving ETH.
    receive() external payable {}

    /**
     * @notice Initializes the contract. This is called only once upon deployment by the StashFactory.
     * @param _owner The permanent and immutable owner of the stash.
     */
    function initialize(address _owner) external {
        if (_initialized) revert AlreadyInitialized();

        owner = _owner;
        _initialized = true;
    }

    /**
     * @notice Places an order for an auction. If one exists, it will be replaced or incremented depending on the order type.
     * @param pricePerUnit The price per unit of the order.
     * @param numberOfUnits The number of units included in the order.
     * @dev The stash owner must initiate this transaction by calling the corresponding bid function on a valid auction contract.
     */
    function placeOrder(uint80 pricePerUnit, uint16 numberOfUnits) external payable {
        // Prevent unwanted bids by enforcing that the user initiated the transaction and that the caller is a registered auction.
        if (tx.origin != owner) revert Unauthorized();
        if (!_STASH_FACTORY.isAuction(msg.sender)) revert CallerNotAuction();

        (address paymentToken, OrderType orderType) = IAuction(msg.sender).bidConfig();

        uint256 paymentTokenBalance = _balanceOfToken(paymentToken);
        (uint256 lockedAmount, uint256 finalizedIndexes) = _totalLockedAndStaleBids(paymentToken);

        uint256 _availableLiquidity;
        unchecked {
            // Locked amount cannot exceed paymentTokenBalance.
            _availableLiquidity = paymentTokenBalance - lockedAmount;
        }

        _cleanStaleBids(paymentToken, finalizedIndexes);

        Order memory newOrder = Order(numberOfUnits, pricePerUnit, msg.sender);

        Order[] storage _orders = paymentTokenToOrders[paymentToken];

        for (uint256 i = 0; i < _orders.length; ++i) {
            Order storage _order = _orders[i];
            if (_order.auction == msg.sender) {
                // cache the existing order to emit an event later.
                Order memory existingOrder = _order;

                // This will check that the stash has funds to cover the order, and modify the existing order in place.
                _replaceOrIncrementExistingOrders(_order, numberOfUnits, pricePerUnit, _availableLiquidity, orderType);

                emit OrderUpdated(existingOrder, _order);

                return;
            }
        }

        if (_bidDeltaExceedsLiquidity(0, uint256(numberOfUnits) * pricePerUnit, _availableLiquidity)) {
            revert RequestExceedsAvailableBalance();
        }

        _orders.push(newOrder);

        /**
         * Limit the number of orders to 10 to prevent gas issues. Realistically, there will only ever be one order
         * per payment token at a time. This is just a safety measure.
         */
        if (_orders.length > 10) revert TooManyOrders();

        emit OrderPlaced(newOrder);
    }

    /**
     * @notice Processes an order for a given auction, transferring payment to the auction contract.
     * @param costPerUnit The cost per unit of the order.
     * @param numberOfUnits The number of units to process.
     * @dev This function is called by the auction contract, which should handle minting corresponding units
     * as part of the transaction. The order's numberOfUnits will be lowered by numberOfUnits, and the order will be
     * removed if numberOfUnits is equal to the order's numberOfUnits.
     */
    function processOrder(uint80 costPerUnit, uint16 numberOfUnits) external {
        if (!_STASH_FACTORY.isAuction(msg.sender)) revert CallerNotAuction();
        (address paymentToken,) = IAuction(msg.sender).bidConfig();

        Order[] storage _orders = paymentTokenToOrders[paymentToken];
        for (uint256 i = 0; i < _orders.length;) {
            Order storage _order = _orders[i];
            if (_order.auction == msg.sender) {
                if (costPerUnit > _order.pricePerUnit || numberOfUnits > _order.numberOfUnits) {
                    revert CannotTransferMoreThanBidAmount();
                }

                if (numberOfUnits == _order.numberOfUnits) {
                    _removeBid(paymentToken, i);
                } else {
                    // cache the existing order to emit an event later.
                    Order memory _originalOrder = _order;

                    unchecked {
                        _order.numberOfUnits -= numberOfUnits;
                    }

                    emit OrderUpdated(_originalOrder, _order);
                }

                _transferTokens(paymentToken, uint256(numberOfUnits) * costPerUnit);
                return;
            } else {
                unchecked {
                    ++i;
                }
            }
        }

        revert NoBidForAuction();
    }

    /**
     * @notice Allows selling a punk to the stash. A valid signature from the stash owner is required for successful execution.
     * @param bid The bid that was signed off-chain.
     * @param punkIndex The id of the punk to sell. Must be included in the bid's merkle tree.
     * @param signature The signed bid.
     * @param proof The merkle proof for the punkIndex.
     * @dev This function will revert if the bid is invalid, expired, or canceled. It will also revert if the bid.
     * does not contain the punkIndex in its merkle tree. If the bid is valid, the punk will be transferred to the stash.
     * owner and the bid's numberOfUnits will be decremented. If numberOfUnits is 1, the bid will be marked as used.
     */
    function processPunkBid(PunkBid calldata bid, uint256 punkIndex, bytes memory signature, bytes32[] calldata proof)
        external
    {
        uint256 availableETH = availableLiquidity(address(0));

        Order calldata order = bid.order;
        uint256 bidPrice = order.pricePerUnit;

        if (order.numberOfUnits == 0) revert InvalidBid();
        if (order.auction != address(_CRYPTOPUNKS)) revert InvalidBid();
        if (punkAccountNonce != bid.accountNonce) revert BidCanceled();
        if (usedPunkBidNonces[bid.bidNonce]) revert BidCanceled();
        if (bid.expiration > 0 && block.timestamp > bid.expiration) revert BidExpired();
        if (!_isValidSignature(bid, signature)) revert InvalidSignature();

        if (bid.root != _COLLECTION_BID_ROOT) {
            if (!MerkleProofLib.verifyCalldata(proof, bid.root, keccak256(abi.encode(punkIndex)))) {
                revert InvalidProof();
            }
        }

        // if balance is too low, we try to use owner's approved weth to supplement.
        if (bidPrice > availableETH) {
            _swapWETH(bidPrice - availableETH);
        }

        uint256 remainingUnits = punkBidNonceUsesRemaining[bid.bidNonce];

        // we have already checked if the nonce is marked as used, so if remainingUnits is 0, this is the first use.
        if (remainingUnits == 0) {
            if (order.numberOfUnits == 1) {
                usedPunkBidNonces[bid.bidNonce] = true;
            } else {
                unchecked {
                    punkBidNonceUsesRemaining[bid.bidNonce] = order.numberOfUnits - 1;
                }
            }
            // If remainingUnits is greater than 1, decrement it.
        } else if (remainingUnits > 1) {
            unchecked {
                --punkBidNonceUsesRemaining[bid.bidNonce];
            }
            // remainingUnits is 1 - this is the last use, so mark the nonce as used.
        } else {
            delete punkBidNonceUsesRemaining[bid.bidNonce];
            usedPunkBidNonces[bid.bidNonce] = true;
        }

        (bool isPunkForSaleInLegacyMarketplace,,, uint256 minValue,) = _CRYPTOPUNKS.punksOfferedForSale(punkIndex);
        uint256 amountDueToCaller;

        // if the punk is listed for sale, we can infer that it is unwrapped.
        if (isPunkForSaleInLegacyMarketplace) {
            if (minValue > bidPrice) {
                revert CannotTransferMoreThanBidAmount();
            } else if (minValue < bidPrice) {
                /**
                 * If the punk is listed under the bid, incentivize MEV fulfillment by paying the difference to the caller.
                 * We cache the value to be used after finalization to avoid reentrancy issues.
                 */
                unchecked {
                    amountDueToCaller = bidPrice - minValue;
                }
            }

            _CRYPTOPUNKS.buyPunk{value: minValue}(punkIndex);
            // If it is not listed, we can still fulfill the bid if the punk is wrapped in the legacy or 721 wrapper.
        } else {
            /**
             * If the punk is wrapped in the legacy wrapper or CryptoPunks721, we need to unwrap it. Owners of Legacy Wrapped
             * Punks or CryptoPunks721 have to approve their punks to the PunkTransferHelper. To prevent the Stash owner from
             * maliciously replacing the bid with a lower price after the wrapped punk has already been approved, while unwrapped
             * punk bids can be fulfilled by anybody, processing bids using punks that are wrapped will verify that msg.sender is
             * the owner of the punk.
             */

            address _unwrappedCryptoPunkOwner = _CRYPTOPUNKS.punkIndexToAddress(punkIndex);

            bytes32 _punkIndexAndOwnerAddressPacked;

            assembly {
                // Shift _punkIndex left by 160 bits to make room for the _assetContract
                let shiftedPunkIndex := shl(160, punkIndex)
                // Combine shifted _punkIndex with _assetContract
                _punkIndexAndOwnerAddressPacked := or(shiftedPunkIndex, caller())
            }

            if (_unwrappedCryptoPunkOwner == address(_LEGACY_WRAPPED_PUNKS)) {
                // Transfer punk to stash for unwrapping. This implicitly checks that caller is owner of wrapped punk to prevent malicious bid replacement:
                _PUNK_TRANSFER_HELPER.transferLegacyWrappedPunkToStash(_punkIndexAndOwnerAddressPacked);
                // Unwrap punk:
                _LEGACY_WRAPPED_PUNKS.burn(punkIndex);
            } else if (_unwrappedCryptoPunkOwner == address(_CRYPTOPUNKS_721)) {
                // Transfer punk to stash for unwrapping. This implicitly checks that caller is owner of wrapped punk to prevent malicious bid replacement:
                _PUNK_TRANSFER_HELPER.transfer721PunkToStash(_punkIndexAndOwnerAddressPacked);
                // Unwrap punk:
                _CRYPTOPUNKS_721.unwrapPunk(punkIndex);
            } else {
                revert FailedToBuyPunk();
            }

            // set bidPrice as amount to pay caller. Caller will always be the stash owner in this case:
            amountDueToCaller = bidPrice;
        }

        _CRYPTOPUNKS.transferPunk(owner, punkIndex);
        if (amountDueToCaller > 0) {
            (bool callerPaid,) = payable(msg.sender).call{value: amountDueToCaller}("");
            if (!callerPaid) revert FailedToBuyPunk();
        }

        emit PunkBidAccepted(bidPrice, punkIndex);
    }

    /**
     * @notice Cancels a bid.
     * @param bidNonce The nonce of the bid to cancel.
     */
    function cancelPunkBid(uint256 bidNonce) external onlyOwner {
        usedPunkBidNonces[bidNonce] = true;

        emit PunkBidCanceled(bidNonce);
    }

    /**
     * @notice increments the global account nonce, canceling all existing offchain bids.
     * @dev a very motivated stash owner could overflow their nonce, but there would be no benefit to doing so.
     */
    function cancelAllPunkBids() external onlyOwner {
        unchecked {
            ++punkAccountNonce;
        }

        emit AllPunkBidsCanceled();
    }

    // --------------------- WITHDRAWALS ---------------------

    /**
     * @notice Used by the CryptoPunks721 contract to wrap punks. Punks must be deposited to the Stash for wrapping.
     * @param punkIndex The index of the punk to wrap.
     */
    function wrapPunk(uint256 punkIndex) external {
        if (msg.sender != address(_CRYPTOPUNKS_721)) revert Unauthorized();

        _CRYPTOPUNKS.transferPunk(address(_CRYPTOPUNKS_721), punkIndex);
    }

    /**
     * @notice withdraws funds from the stash.
     * @param tokenAddress The address of the token to withdraw. Zero address for ETH.
     * @param amount The amount to withdraw in wei.
     * @dev This function allows withdrawal of funds that are not committed to an active bid. It will also
     * clean up any stale bids that have been finalized or expired.
     */
    function withdraw(address tokenAddress, uint256 amount) external onlyOwner {
        (uint256 _lockedAmount, uint256 finalizedIndexes) = _totalLockedAndStaleBids(tokenAddress);

        uint256 tokenBalance = _balanceOfToken(tokenAddress);

        uint256 availableToWithdraw = tokenBalance - _lockedAmount;
        if (amount > availableToWithdraw) revert RequestExceedsAvailableBalance();

        _cleanStaleBids(tokenAddress, finalizedIndexes);

        _transferTokens(tokenAddress, amount);
    }

    /**
     * @notice Convenience function to withdraw ERC721 tokens from the stash.
     * @param tokenAddress The address of the token to withdraw.
     * @param tokenIds An array of token IDs to withdraw.
     */
    function withdrawERC721(address tokenAddress, uint256[] calldata tokenIds) external onlyOwner {
        IERC721 tokenContract = IERC721(tokenAddress);

        for (uint256 i = 0; i < tokenIds.length; ++i) {
            tokenContract.transferFrom(address(this), owner, tokenIds[i]);
        }
    }

    /**
     * @notice Convenience function to withdraw ERC1155 tokens from the stash.
     * @param tokenAddress The address of the token to withdraw.
     * @param tokenIds An array of token IDs to withdraw.
     * @param amounts An array of amounts to withdraw.
     */
    function withdrawERC1155(address tokenAddress, uint256[] calldata tokenIds, uint256[] calldata amounts)
        external
        onlyOwner
    {
        IERC1155 tokenContract = IERC1155(tokenAddress);

        for (uint256 i = 0; i < tokenIds.length; ++i) {
            tokenContract.safeTransferFrom(address(this), owner, tokenIds[i], amounts[i], "");
        }
    }

    /**
     * @notice Convenience function to withdraw CryptoPunks from the stash.
     * @param tokenIds An array of punk IDs to withdraw.
     */
    function withdrawPunks(uint256[] calldata tokenIds) external onlyOwner {
        for (uint256 i = 0; i < tokenIds.length; ++i) {
            _CRYPTOPUNKS.transferPunk(owner, tokenIds[i]);
        }
    }

    // --------------------- VIEW ---------------------

    /**
     * @notice Fetches a bid corresponding to an auction.
     * @param auction The address of the auction to fetch a bid for.
     * @return bid The bid corresponding to the auction.
     */
    function getOrder(address auction) external view returns (Order memory) {
        (address paymentToken,) = IAuction(auction).bidConfig();
        Order[] storage _orders = paymentTokenToOrders[paymentToken];

        for (uint256 i = 0; i < _orders.length; ++i) {
            Order storage bid = _orders[i];
            if (bid.auction == auction) {
                if (!IAuction(bid.auction).finalized()) {
                    return bid;
                } else {
                    // If the auction is finalized, the bid is invalid.
                    revert OrderNotFound();
                }
            }
        }

        revert OrderNotFound();
    }

    /**
     * @notice Fetches the total amount of locked funds for a given token.
     * @param tokenAddress The address of the token to fetch the locked amount for. Zero address for ETH.
     */
    function totalLocked(address tokenAddress) external view returns (uint256 lockedAmount) {
        (lockedAmount,) = _totalLockedAndStaleBids(tokenAddress);
    }

    /**
     * @notice Fetches the total amount of available funds for a given token.
     * @param tokenAddress The address of the token to fetch the available amount for. Zero address for ETH.
     */
    function availableLiquidity(address tokenAddress) public view returns (uint256 availableAmount) {
        uint256 tokenBalance = _balanceOfToken(tokenAddress);
        (uint256 lockedAmount,) = _totalLockedAndStaleBids(tokenAddress);
        availableAmount = tokenBalance - lockedAmount;
    }

    /**
     * @notice convenience function that returns the total amount of useable ETH and WETH.
     * @return availableAmount The total amount of useable ETH and WETH.
     */
    function availableLiquidityWETHAndETH() public view returns (uint256 availableAmount) {
        uint256 wethHeldByOwner = _WETH.balanceOf(owner);
        uint256 wethApprovedByOwner = _WETH.allowance(owner, address(this));
        uint256 availableWETH = wethHeldByOwner > wethApprovedByOwner ? wethApprovedByOwner : wethHeldByOwner;

        availableAmount = availableWETH + availableLiquidity(address(_WETH)) + availableLiquidity(address(0));
    }

    /**
     * @notice Returns the current version of this particular Stash.
     * @return _VERSION The current version of this Stash.
     */
    function version() external pure returns (uint256) {
        return _VERSION;
    }

    // --------------------- ERC165 ---------------------

    function onERC1155Received(address, address, uint256, uint256, bytes memory) public virtual returns (bytes4) {
        return this.onERC1155Received.selector;
    }

    function onERC1155BatchReceived(address, address, uint256[] memory, uint256[] memory, bytes memory)
        public
        virtual
        returns (bytes4)
    {
        return this.onERC1155BatchReceived.selector;
    }

    function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) {
        return this.onERC721Received.selector;
    }

    // --------------------- INTERNAL ---------------------

    function _transferTokens(address tokenAddress, uint256 amount) internal {
        if (tokenAddress == address(0)) {
            (bool success,) = payable(msg.sender).call{value: amount}("");
            if (!success) revert FailedToWithdraw();
        } else {
            SafeTransferLib.safeTransfer(tokenAddress, msg.sender, amount);
        }
    }

    function _replaceOrIncrementExistingOrders(
        Order storage existingOrder,
        uint16 updatedNumberOfUnits,
        uint80 updatedPricePerUnit,
        uint256 _availableLiquidity,
        OrderType _type
    ) internal {
        if (_type == OrderType.UNREPLACEABLE) revert InvalidOrderAlteration();

        uint16 newTotalNumberOfUnits;

        if (_type == OrderType.SUBSEQUENT_BIDS_REPLACE_EXISTING_PRICE_INCREASE_REQUIRED) {
            if (existingOrder.pricePerUnit >= updatedPricePerUnit) {
                revert InvalidOrderAlteration();
            } else {
                // `numberOfUnits` is allowed to decrease as long as `pricePerUnit` increases.
                newTotalNumberOfUnits = updatedNumberOfUnits;
            }
        } else if (_type == OrderType.SUBSEQUENT_BIDS_OVERWRITE_PRICE_AND_ADD_UNITS) {
            newTotalNumberOfUnits = existingOrder.numberOfUnits + updatedNumberOfUnits;
        } else {
            revert UnknownOrderType();
        }

        if (
            _bidDeltaExceedsLiquidity(
                uint256(existingOrder.numberOfUnits) * existingOrder.pricePerUnit,
                uint256(newTotalNumberOfUnits) * updatedPricePerUnit,
                _availableLiquidity
            )
        ) revert RequestExceedsAvailableBalance();

        existingOrder.numberOfUnits = newTotalNumberOfUnits;
        existingOrder.pricePerUnit = updatedPricePerUnit;
    }

    // internal helpers
    function _swapWETH(uint256 wethAmount) internal {
        uint256 availableBalance = availableLiquidity(address(_WETH));

        // if existing balance is high enough just withdraw it and return early.
        if (availableBalance >= wethAmount) {
            _WETH.withdraw(wethAmount);
            return;
        }

        uint256 amountToTransfer = wethAmount;
        // if existing balance is not high enough but greater than 0, decrement the amount needed by the existing balance.
        if (availableBalance > 0 && availableBalance < wethAmount) {
            unchecked {
                amountToTransfer -= availableBalance;
            }
        }
        _WETH.transferFrom(owner, address(this), amountToTransfer);
        _WETH.withdraw(wethAmount);
    }

    // --------------------- INTERNAL VIEW ---------------------

    function _isValidSignature(PunkBid calldata bid, bytes memory signature) internal view returns (bool) {
        Order calldata order = bid.order;

        bytes32 hashStruct = keccak256(
            abi.encode(
                _PUNK_BID_TYPEHASH,
                keccak256(abi.encode(_ORDER_TYPEHASH, order.numberOfUnits, order.pricePerUnit, order.auction)),
                bid.accountNonce,
                bid.bidNonce,
                bid.expiration,
                bid.root
            )
        );

        bytes32 _domainHash = keccak256(
            abi.encode(
                keccak256("EIP712Domain(uint256 chainId,address verifyingContract)"), block.chainid, address(this)
            )
        );
        bytes32 hash = keccak256(abi.encodePacked("\x19\x01", _domainHash, hashStruct));

        // This lib does not include a malleability check, however, the bidNonce will prevent signature reuse.
        return SignatureCheckerLib.isValidSignatureNow(owner, hash, signature);
    }

    function _totalAmountBid(Order storage _order) internal view returns (uint256) {
        return uint256(_order.numberOfUnits) * _order.pricePerUnit;
    }

    function _balanceOfToken(address tokenAddress) internal view returns (uint256) {
        if (tokenAddress == address(0)) {
            return address(this).balance;
        } else {
            return IERC20(tokenAddress).balanceOf(address(this));
        }
    }

    function _totalLockedAndStaleBids(address tokenAddress)
        internal
        view
        returns (uint256 _lockedAmount, uint256 finalizedIndexes)
    {
        Order[] storage _orders = paymentTokenToOrders[tokenAddress];

        for (uint256 i = 0; i < _orders.length; ++i) {
            Order storage order = _orders[i];
            IAuction auction = IAuction(order.auction);

            if (auction.finalized()) {
                finalizedIndexes = finalizedIndexes | (1 << i);
            } else {
                (address paymentToken,) = auction.bidConfig();
                if (tokenAddress == paymentToken) {
                    unchecked {
                        _lockedAmount += _totalAmountBid(order);
                    }
                }
            }
        }
    }

    function _cleanStaleBids(address tokenAddress, uint256 finalizedIndexes) internal {
        Order[] storage _orders = paymentTokenToOrders[tokenAddress];

        // If there are no bids to process, exit early.
        if (_orders.length == 0) return;

        // Otherwise, use a while loop to iterate and clean up stale bids.
        uint256 i = _orders.length;

        while (i > 0) {
            unchecked {
                --i;
            }
            if ((finalizedIndexes & (1 << i)) != 0) {
                _removeBid(tokenAddress, i);
                // If the last bid is removed, break out of the loop.
                if (_orders.length == 0) break;
            }
        }
    }

    function _removeBid(address _key, uint256 _bidIndex) internal {
        Order[] storage _orders = paymentTokenToOrders[_key];
        Order memory orderToRemove = _orders[_bidIndex];

        if (_bidIndex != _orders.length - 1) {
            _orders[_bidIndex] = _orders[_orders.length - 1];
        }
        _orders.pop();

        emit OrderRemoved(orderToRemove);
    }

    function _bidDeltaExceedsLiquidity(uint256 existingTotal, uint256 newTotal, uint256 _availableLiquidity)
        internal
        pure
        returns (bool)
    {
        if (newTotal > existingTotal && newTotal - existingTotal > _availableLiquidity) {
            return true;
        }

        return false;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "forge-std/interfaces/IERC165.sol";
import {OrderType} from "../helpers/Enum.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IAuction is IERC165 {
    error AuctionNotOpen();
    error BidTooLow();
    error CannotFinalizeOpenAuction();

    function bidConfig() external view returns (address, OrderType);

    function open() external view returns (bool);

    function finalized() external view returns (bool);

    function withdraw() external;

    function currentPrice() external view returns (uint80);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.23;

interface IWETH {
    function transfer(address dst, uint256 wad) external;
    function transferFrom(address src, address dst, uint256 wad) external;
    function deposit() external payable;
    function withdraw(uint256 wad) external;
    function balanceOf(address user) external view returns (uint256);
    function approve(address guy, uint256 wad) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
}

File 4 of 17 : Enum.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

enum OrderType
{
    // 0: Can replace previous bid. Alters bid price and adds `numberOfUnits`
    SUBSEQUENT_BIDS_OVERWRITE_PRICE_AND_ADD_UNITS,
    // 1: Can replace previous bid if new bid has higher `pricePerUnit`
    SUBSEQUENT_BIDS_REPLACE_EXISTING_PRICE_INCREASE_REQUIRED,
    // 2: Cannot replace previous bid under any circumstance
    UNREPLACEABLE
}

File 5 of 17 : Struct.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {OrderType} from "./Enum.sol";

struct Order {
    uint16 numberOfUnits;
    uint80 pricePerUnit;
    address auction;
}

struct PunkBid {
    Order order;
    uint256 accountNonce;
    uint256 bidNonce;
    uint256 expiration;
    bytes32 root;
}

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

/// @notice Gas optimized verification of proof of inclusion for a leaf in a Merkle tree.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MerkleProof.sol)
library MerkleProofLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*            MERKLE PROOF VERIFICATION OPERATIONS            */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns whether `leaf` exists in the Merkle tree with `root`, given `proof`.
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf)
        internal
        pure
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            if mload(proof) {
                // Initialize `offset` to the offset of `proof` elements in memory.
                let offset := add(proof, 0x20)
                // Left shift by 5 is equivalent to multiplying by 0x20.
                let end := add(offset, shl(5, mload(proof)))
                // Iterate over proof elements to compute root hash.
                for {} 1 {} {
                    // Slot of `leaf` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(leaf, mload(offset)))
                    // Store elements to hash contiguously in scratch space.
                    // Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes.
                    mstore(scratch, leaf)
                    mstore(xor(scratch, 0x20), mload(offset))
                    // Reuse `leaf` to store the hash to reduce stack operations.
                    leaf := keccak256(0x00, 0x40)
                    offset := add(offset, 0x20)
                    if iszero(lt(offset, end)) { break }
                }
            }
            isValid := eq(leaf, root)
        }
    }

    /// @dev Returns whether `leaf` exists in the Merkle tree with `root`, given `proof`.
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf)
        internal
        pure
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            if proof.length {
                // Left shift by 5 is equivalent to multiplying by 0x20.
                let end := add(proof.offset, shl(5, proof.length))
                // Initialize `offset` to the offset of `proof` in the calldata.
                let offset := proof.offset
                // Iterate over proof elements to compute root hash.
                for {} 1 {} {
                    // Slot of `leaf` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(leaf, calldataload(offset)))
                    // Store elements to hash contiguously in scratch space.
                    // Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes.
                    mstore(scratch, leaf)
                    mstore(xor(scratch, 0x20), calldataload(offset))
                    // Reuse `leaf` to store the hash to reduce stack operations.
                    leaf := keccak256(0x00, 0x40)
                    offset := add(offset, 0x20)
                    if iszero(lt(offset, end)) { break }
                }
            }
            isValid := eq(leaf, root)
        }
    }

    /// @dev Returns whether all `leaves` exist in the Merkle tree with `root`,
    /// given `proof` and `flags`.
    ///
    /// Note:
    /// - Breaking the invariant `flags.length == (leaves.length - 1) + proof.length`
    ///   will always return false.
    /// - The sum of the lengths of `proof` and `leaves` must never overflow.
    /// - Any non-zero word in the `flags` array is treated as true.
    /// - The memory offset of `proof` must be non-zero
    ///   (i.e. `proof` is not pointing to the scratch space).
    function verifyMultiProof(
        bytes32[] memory proof,
        bytes32 root,
        bytes32[] memory leaves,
        bool[] memory flags
    ) internal pure returns (bool isValid) {
        // Rebuilds the root by consuming and producing values on a queue.
        // The queue starts with the `leaves` array, and goes into a `hashes` array.
        // After the process, the last element on the queue is verified
        // to be equal to the `root`.
        //
        // The `flags` array denotes whether the sibling
        // should be popped from the queue (`flag == true`), or
        // should be popped from the `proof` (`flag == false`).
        /// @solidity memory-safe-assembly
        assembly {
            // Cache the lengths of the arrays.
            let leavesLength := mload(leaves)
            let proofLength := mload(proof)
            let flagsLength := mload(flags)

            // Advance the pointers of the arrays to point to the data.
            leaves := add(0x20, leaves)
            proof := add(0x20, proof)
            flags := add(0x20, flags)

            // If the number of flags is correct.
            for {} eq(add(leavesLength, proofLength), add(flagsLength, 1)) {} {
                // For the case where `proof.length + leaves.length == 1`.
                if iszero(flagsLength) {
                    // `isValid = (proof.length == 1 ? proof[0] : leaves[0]) == root`.
                    isValid := eq(mload(xor(leaves, mul(xor(proof, leaves), proofLength))), root)
                    break
                }

                // The required final proof offset if `flagsLength` is not zero, otherwise zero.
                let proofEnd := add(proof, shl(5, proofLength))
                // We can use the free memory space for the queue.
                // We don't need to allocate, since the queue is temporary.
                let hashesFront := mload(0x40)
                // Copy the leaves into the hashes.
                // Sometimes, a little memory expansion costs less than branching.
                // Should cost less, even with a high free memory offset of 0x7d00.
                leavesLength := shl(5, leavesLength)
                for { let i := 0 } iszero(eq(i, leavesLength)) { i := add(i, 0x20) } {
                    mstore(add(hashesFront, i), mload(add(leaves, i)))
                }
                // Compute the back of the hashes.
                let hashesBack := add(hashesFront, leavesLength)
                // This is the end of the memory for the queue.
                // We recycle `flagsLength` to save on stack variables (sometimes save gas).
                flagsLength := add(hashesBack, shl(5, flagsLength))

                for {} 1 {} {
                    // Pop from `hashes`.
                    let a := mload(hashesFront)
                    // Pop from `hashes`.
                    let b := mload(add(hashesFront, 0x20))
                    hashesFront := add(hashesFront, 0x40)

                    // If the flag is false, load the next proof,
                    // else, pops from the queue.
                    if iszero(mload(flags)) {
                        // Loads the next proof.
                        b := mload(proof)
                        proof := add(proof, 0x20)
                        // Unpop from `hashes`.
                        hashesFront := sub(hashesFront, 0x20)
                    }

                    // Advance to the next flag.
                    flags := add(flags, 0x20)

                    // Slot of `a` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(a, b))
                    // Hash the scratch space and push the result onto the queue.
                    mstore(scratch, a)
                    mstore(xor(scratch, 0x20), b)
                    mstore(hashesBack, keccak256(0x00, 0x40))
                    hashesBack := add(hashesBack, 0x20)
                    if iszero(lt(hashesBack, flagsLength)) { break }
                }
                isValid :=
                    and(
                        // Checks if the last value in the queue is same as the root.
                        eq(mload(sub(hashesBack, 0x20)), root),
                        // And whether all the proofs are used, if required.
                        eq(proofEnd, proof)
                    )
                break
            }
        }
    }

    /// @dev Returns whether all `leaves` exist in the Merkle tree with `root`,
    /// given `proof` and `flags`.
    ///
    /// Note:
    /// - Breaking the invariant `flags.length == (leaves.length - 1) + proof.length`
    ///   will always return false.
    /// - Any non-zero word in the `flags` array is treated as true.
    /// - The calldata offset of `proof` must be non-zero
    ///   (i.e. `proof` is from a regular Solidity function with a 4-byte selector).
    function verifyMultiProofCalldata(
        bytes32[] calldata proof,
        bytes32 root,
        bytes32[] calldata leaves,
        bool[] calldata flags
    ) internal pure returns (bool isValid) {
        // Rebuilds the root by consuming and producing values on a queue.
        // The queue starts with the `leaves` array, and goes into a `hashes` array.
        // After the process, the last element on the queue is verified
        // to be equal to the `root`.
        //
        // The `flags` array denotes whether the sibling
        // should be popped from the queue (`flag == true`), or
        // should be popped from the `proof` (`flag == false`).
        /// @solidity memory-safe-assembly
        assembly {
            // If the number of flags is correct.
            for {} eq(add(leaves.length, proof.length), add(flags.length, 1)) {} {
                // For the case where `proof.length + leaves.length == 1`.
                if iszero(flags.length) {
                    // `isValid = (proof.length == 1 ? proof[0] : leaves[0]) == root`.
                    // forgefmt: disable-next-item
                    isValid := eq(
                        calldataload(
                            xor(leaves.offset, mul(xor(proof.offset, leaves.offset), proof.length))
                        ),
                        root
                    )
                    break
                }

                // The required final proof offset if `flagsLength` is not zero, otherwise zero.
                let proofEnd := add(proof.offset, shl(5, proof.length))
                // We can use the free memory space for the queue.
                // We don't need to allocate, since the queue is temporary.
                let hashesFront := mload(0x40)
                // Copy the leaves into the hashes.
                // Sometimes, a little memory expansion costs less than branching.
                // Should cost less, even with a high free memory offset of 0x7d00.
                calldatacopy(hashesFront, leaves.offset, shl(5, leaves.length))
                // Compute the back of the hashes.
                let hashesBack := add(hashesFront, shl(5, leaves.length))
                // This is the end of the memory for the queue.
                // We recycle `flagsLength` to save on stack variables (sometimes save gas).
                flags.length := add(hashesBack, shl(5, flags.length))

                // We don't need to make a copy of `proof.offset` or `flags.offset`,
                // as they are pass-by-value (this trick may not always save gas).

                for {} 1 {} {
                    // Pop from `hashes`.
                    let a := mload(hashesFront)
                    // Pop from `hashes`.
                    let b := mload(add(hashesFront, 0x20))
                    hashesFront := add(hashesFront, 0x40)

                    // If the flag is false, load the next proof,
                    // else, pops from the queue.
                    if iszero(calldataload(flags.offset)) {
                        // Loads the next proof.
                        b := calldataload(proof.offset)
                        proof.offset := add(proof.offset, 0x20)
                        // Unpop from `hashes`.
                        hashesFront := sub(hashesFront, 0x20)
                    }

                    // Advance to the next flag offset.
                    flags.offset := add(flags.offset, 0x20)

                    // Slot of `a` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(a, b))
                    // Hash the scratch space and push the result onto the queue.
                    mstore(scratch, a)
                    mstore(xor(scratch, 0x20), b)
                    mstore(hashesBack, keccak256(0x00, 0x40))
                    hashesBack := add(hashesBack, 0x20)
                    if iszero(lt(hashesBack, flags.length)) { break }
                }
                isValid :=
                    and(
                        // Checks if the last value in the queue is same as the root.
                        eq(mload(sub(hashesBack, 0x20)), root),
                        // And whether all the proofs are used, if required.
                        eq(proofEnd, proof.offset)
                    )
                break
            }
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   EMPTY CALLDATA HELPERS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns an empty calldata bytes32 array.
    function emptyProof() internal pure returns (bytes32[] calldata proof) {
        /// @solidity memory-safe-assembly
        assembly {
            proof.length := 0
        }
    }

    /// @dev Returns an empty calldata bytes32 array.
    function emptyLeaves() internal pure returns (bytes32[] calldata leaves) {
        /// @solidity memory-safe-assembly
        assembly {
            leaves.length := 0
        }
    }

    /// @dev Returns an empty calldata bool array.
    function emptyFlags() internal pure returns (bool[] calldata flags) {
        /// @solidity memory-safe-assembly
        assembly {
            flags.length := 0
        }
    }
}

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

/// @notice Signature verification helper that supports both ECDSA signatures from EOAs
/// and ERC1271 signatures from smart contract wallets like Argent and Gnosis safe.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SignatureCheckerLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol)
///
/// @dev Note:
/// - The signature checking functions use the ecrecover precompile (0x1).
/// - The `bytes memory signature` variants use the identity precompile (0x4)
///   to copy memory internally.
/// - Unlike ECDSA signatures, contract signatures are revocable.
/// - As of Solady version 0.0.134, all `bytes signature` variants accept both
///   regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures.
///   See: https://eips.ethereum.org/EIPS/eip-2098
///   This is for calldata efficiency on smart accounts prevalent on L2s.
///
/// WARNING! Do NOT use signatures as unique identifiers:
/// - Use a nonce in the digest to prevent replay attacks on the same contract.
/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts.
///   EIP-712 also enables readable signing of typed data for better user safety.
/// This implementation does NOT check if a signature is non-malleable.
library SignatureCheckerLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*               SIGNATURE CHECKING OPERATIONS                */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns whether `signature` is valid for `signer` and `hash`.
    /// If `signer` is a smart contract, the signature is validated with ERC1271.
    /// Otherwise, the signature is validated with `ECDSA.recover`.
    function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Clean the upper 96 bits of `signer` in case they are dirty.
            for { signer := shr(96, shl(96, signer)) } signer {} {
                let m := mload(0x40)
                mstore(0x00, hash)
                mstore(0x40, mload(add(signature, 0x20))) // `r`.
                if eq(mload(signature), 64) {
                    let vs := mload(add(signature, 0x40))
                    mstore(0x20, add(shr(255, vs), 27)) // `v`.
                    mstore(0x60, shr(1, shl(1, vs))) // `s`.
                    let t :=
                        staticcall(
                            gas(), // Amount of gas left for the transaction.
                            1, // Address of `ecrecover`.
                            0x00, // Start of input.
                            0x80, // Size of input.
                            0x01, // Start of output.
                            0x20 // Size of output.
                        )
                    // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                    if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
                        isValid := 1
                        mstore(0x60, 0) // Restore the zero slot.
                        mstore(0x40, m) // Restore the free memory pointer.
                        break
                    }
                }
                if eq(mload(signature), 65) {
                    mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`.
                    mstore(0x60, mload(add(signature, 0x40))) // `s`.
                    let t :=
                        staticcall(
                            gas(), // Amount of gas left for the transaction.
                            1, // Address of `ecrecover`.
                            0x00, // Start of input.
                            0x80, // Size of input.
                            0x01, // Start of output.
                            0x20 // Size of output.
                        )
                    // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                    if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
                        isValid := 1
                        mstore(0x60, 0) // Restore the zero slot.
                        mstore(0x40, m) // Restore the free memory pointer.
                        break
                    }
                }
                mstore(0x60, 0) // Restore the zero slot.
                mstore(0x40, m) // Restore the free memory pointer.

                let f := shl(224, 0x1626ba7e)
                mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
                mstore(add(m, 0x04), hash)
                let d := add(m, 0x24)
                mstore(d, 0x40) // The offset of the `signature` in the calldata.
                // Copy the `signature` over.
                let n := add(0x20, mload(signature))
                pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
                // forgefmt: disable-next-item
                isValid := and(
                    // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                    eq(mload(d), f),
                    // Whether the staticcall does not revert.
                    // This must be placed at the end of the `and` clause,
                    // as the arguments are evaluated from right to left.
                    staticcall(
                        gas(), // Remaining gas.
                        signer, // The `signer` address.
                        m, // Offset of calldata in memory.
                        add(returndatasize(), 0x44), // Length of calldata in memory.
                        d, // Offset of returndata.
                        0x20 // Length of returndata to write.
                    )
                )
                break
            }
        }
    }

    /// @dev Returns whether `signature` is valid for `signer` and `hash`.
    /// If `signer` is a smart contract, the signature is validated with ERC1271.
    /// Otherwise, the signature is validated with `ECDSA.recover`.
    function isValidSignatureNowCalldata(address signer, bytes32 hash, bytes calldata signature)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Clean the upper 96 bits of `signer` in case they are dirty.
            for { signer := shr(96, shl(96, signer)) } signer {} {
                let m := mload(0x40)
                mstore(0x00, hash)
                if eq(signature.length, 64) {
                    let vs := calldataload(add(signature.offset, 0x20))
                    mstore(0x20, add(shr(255, vs), 27)) // `v`.
                    mstore(0x40, calldataload(signature.offset)) // `r`.
                    mstore(0x60, shr(1, shl(1, vs))) // `s`.
                    let t :=
                        staticcall(
                            gas(), // Amount of gas left for the transaction.
                            1, // Address of `ecrecover`.
                            0x00, // Start of input.
                            0x80, // Size of input.
                            0x01, // Start of output.
                            0x20 // Size of output.
                        )
                    // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                    if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
                        isValid := 1
                        mstore(0x60, 0) // Restore the zero slot.
                        mstore(0x40, m) // Restore the free memory pointer.
                        break
                    }
                }
                if eq(signature.length, 65) {
                    mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.
                    calldatacopy(0x40, signature.offset, 0x40) // `r`, `s`.
                    let t :=
                        staticcall(
                            gas(), // Amount of gas left for the transaction.
                            1, // Address of `ecrecover`.
                            0x00, // Start of input.
                            0x80, // Size of input.
                            0x01, // Start of output.
                            0x20 // Size of output.
                        )
                    // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                    if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
                        isValid := 1
                        mstore(0x60, 0) // Restore the zero slot.
                        mstore(0x40, m) // Restore the free memory pointer.
                        break
                    }
                }
                mstore(0x60, 0) // Restore the zero slot.
                mstore(0x40, m) // Restore the free memory pointer.

                let f := shl(224, 0x1626ba7e)
                mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
                mstore(add(m, 0x04), hash)
                let d := add(m, 0x24)
                mstore(d, 0x40) // The offset of the `signature` in the calldata.
                mstore(add(m, 0x44), signature.length)
                // Copy the `signature` over.
                calldatacopy(add(m, 0x64), signature.offset, signature.length)
                // forgefmt: disable-next-item
                isValid := and(
                    // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                    eq(mload(d), f),
                    // Whether the staticcall does not revert.
                    // This must be placed at the end of the `and` clause,
                    // as the arguments are evaluated from right to left.
                    staticcall(
                        gas(), // Remaining gas.
                        signer, // The `signer` address.
                        m, // Offset of calldata in memory.
                        add(signature.length, 0x64), // Length of calldata in memory.
                        d, // Offset of returndata.
                        0x20 // Length of returndata to write.
                    )
                )
                break
            }
        }
    }

    /// @dev Returns whether the signature (`r`, `vs`) is valid for `signer` and `hash`.
    /// If `signer` is a smart contract, the signature is validated with ERC1271.
    /// Otherwise, the signature is validated with `ECDSA.recover`.
    function isValidSignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Clean the upper 96 bits of `signer` in case they are dirty.
            for { signer := shr(96, shl(96, signer)) } signer {} {
                let m := mload(0x40)
                mstore(0x00, hash)
                mstore(0x20, add(shr(255, vs), 27)) // `v`.
                mstore(0x40, r) // `r`.
                mstore(0x60, shr(1, shl(1, vs))) // `s`.
                let t :=
                    staticcall(
                        gas(), // Amount of gas left for the transaction.
                        1, // Address of `ecrecover`.
                        0x00, // Start of input.
                        0x80, // Size of input.
                        0x01, // Start of output.
                        0x20 // Size of output.
                    )
                // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
                    isValid := 1
                    mstore(0x60, 0) // Restore the zero slot.
                    mstore(0x40, m) // Restore the free memory pointer.
                    break
                }

                let f := shl(224, 0x1626ba7e)
                mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
                mstore(add(m, 0x04), hash)
                let d := add(m, 0x24)
                mstore(d, 0x40) // The offset of the `signature` in the calldata.
                mstore(add(m, 0x44), 65) // Length of the signature.
                mstore(add(m, 0x64), r) // `r`.
                mstore(add(m, 0x84), mload(0x60)) // `s`.
                mstore8(add(m, 0xa4), mload(0x20)) // `v`.
                // forgefmt: disable-next-item
                isValid := and(
                    // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                    eq(mload(d), f),
                    // Whether the staticcall does not revert.
                    // This must be placed at the end of the `and` clause,
                    // as the arguments are evaluated from right to left.
                    staticcall(
                        gas(), // Remaining gas.
                        signer, // The `signer` address.
                        m, // Offset of calldata in memory.
                        0xa5, // Length of calldata in memory.
                        d, // Offset of returndata.
                        0x20 // Length of returndata to write.
                    )
                )
                mstore(0x60, 0) // Restore the zero slot.
                mstore(0x40, m) // Restore the free memory pointer.
                break
            }
        }
    }

    /// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `signer` and `hash`.
    /// If `signer` is a smart contract, the signature is validated with ERC1271.
    /// Otherwise, the signature is validated with `ECDSA.recover`.
    function isValidSignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Clean the upper 96 bits of `signer` in case they are dirty.
            for { signer := shr(96, shl(96, signer)) } signer {} {
                let m := mload(0x40)
                mstore(0x00, hash)
                mstore(0x20, and(v, 0xff)) // `v`.
                mstore(0x40, r) // `r`.
                mstore(0x60, s) // `s`.
                let t :=
                    staticcall(
                        gas(), // Amount of gas left for the transaction.
                        1, // Address of `ecrecover`.
                        0x00, // Start of input.
                        0x80, // Size of input.
                        0x01, // Start of output.
                        0x20 // Size of output.
                    )
                // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
                    isValid := 1
                    mstore(0x60, 0) // Restore the zero slot.
                    mstore(0x40, m) // Restore the free memory pointer.
                    break
                }

                let f := shl(224, 0x1626ba7e)
                mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
                mstore(add(m, 0x04), hash)
                let d := add(m, 0x24)
                mstore(d, 0x40) // The offset of the `signature` in the calldata.
                mstore(add(m, 0x44), 65) // Length of the signature.
                mstore(add(m, 0x64), r) // `r`.
                mstore(add(m, 0x84), s) // `s`.
                mstore8(add(m, 0xa4), v) // `v`.
                // forgefmt: disable-next-item
                isValid := and(
                    // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                    eq(mload(d), f),
                    // Whether the staticcall does not revert.
                    // This must be placed at the end of the `and` clause,
                    // as the arguments are evaluated from right to left.
                    staticcall(
                        gas(), // Remaining gas.
                        signer, // The `signer` address.
                        m, // Offset of calldata in memory.
                        0xa5, // Length of calldata in memory.
                        d, // Offset of returndata.
                        0x20 // Length of returndata to write.
                    )
                )
                mstore(0x60, 0) // Restore the zero slot.
                mstore(0x40, m) // Restore the free memory pointer.
                break
            }
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     ERC1271 OPERATIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
    function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes memory signature)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            let f := shl(224, 0x1626ba7e)
            mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
            mstore(add(m, 0x04), hash)
            let d := add(m, 0x24)
            mstore(d, 0x40) // The offset of the `signature` in the calldata.
            // Copy the `signature` over.
            let n := add(0x20, mload(signature))
            pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
            // forgefmt: disable-next-item
            isValid := and(
                // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                eq(mload(d), f),
                // Whether the staticcall does not revert.
                // This must be placed at the end of the `and` clause,
                // as the arguments are evaluated from right to left.
                staticcall(
                    gas(), // Remaining gas.
                    signer, // The `signer` address.
                    m, // Offset of calldata in memory.
                    add(returndatasize(), 0x44), // Length of calldata in memory.
                    d, // Offset of returndata.
                    0x20 // Length of returndata to write.
                )
            )
        }
    }

    /// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
    function isValidERC1271SignatureNowCalldata(
        address signer,
        bytes32 hash,
        bytes calldata signature
    ) internal view returns (bool isValid) {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            let f := shl(224, 0x1626ba7e)
            mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
            mstore(add(m, 0x04), hash)
            let d := add(m, 0x24)
            mstore(d, 0x40) // The offset of the `signature` in the calldata.
            mstore(add(m, 0x44), signature.length)
            // Copy the `signature` over.
            calldatacopy(add(m, 0x64), signature.offset, signature.length)
            // forgefmt: disable-next-item
            isValid := and(
                // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                eq(mload(d), f),
                // Whether the staticcall does not revert.
                // This must be placed at the end of the `and` clause,
                // as the arguments are evaluated from right to left.
                staticcall(
                    gas(), // Remaining gas.
                    signer, // The `signer` address.
                    m, // Offset of calldata in memory.
                    add(signature.length, 0x64), // Length of calldata in memory.
                    d, // Offset of returndata.
                    0x20 // Length of returndata to write.
                )
            )
        }
    }

    /// @dev Returns whether the signature (`r`, `vs`) is valid for `hash`
    /// for an ERC1271 `signer` contract.
    function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            let f := shl(224, 0x1626ba7e)
            mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
            mstore(add(m, 0x04), hash)
            let d := add(m, 0x24)
            mstore(d, 0x40) // The offset of the `signature` in the calldata.
            mstore(add(m, 0x44), 65) // Length of the signature.
            mstore(add(m, 0x64), r) // `r`.
            mstore(add(m, 0x84), shr(1, shl(1, vs))) // `s`.
            mstore8(add(m, 0xa4), add(shr(255, vs), 27)) // `v`.
            // forgefmt: disable-next-item
            isValid := and(
                // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                eq(mload(d), f),
                // Whether the staticcall does not revert.
                // This must be placed at the end of the `and` clause,
                // as the arguments are evaluated from right to left.
                staticcall(
                    gas(), // Remaining gas.
                    signer, // The `signer` address.
                    m, // Offset of calldata in memory.
                    0xa5, // Length of calldata in memory.
                    d, // Offset of returndata.
                    0x20 // Length of returndata to write.
                )
            )
        }
    }

    /// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `hash`
    /// for an ERC1271 `signer` contract.
    function isValidERC1271SignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            let f := shl(224, 0x1626ba7e)
            mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
            mstore(add(m, 0x04), hash)
            let d := add(m, 0x24)
            mstore(d, 0x40) // The offset of the `signature` in the calldata.
            mstore(add(m, 0x44), 65) // Length of the signature.
            mstore(add(m, 0x64), r) // `r`.
            mstore(add(m, 0x84), s) // `s`.
            mstore8(add(m, 0xa4), v) // `v`.
            // forgefmt: disable-next-item
            isValid := and(
                // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                eq(mload(d), f),
                // Whether the staticcall does not revert.
                // This must be placed at the end of the `and` clause,
                // as the arguments are evaluated from right to left.
                staticcall(
                    gas(), // Remaining gas.
                    signer, // The `signer` address.
                    m, // Offset of calldata in memory.
                    0xa5, // Length of calldata in memory.
                    d, // Offset of returndata.
                    0x20 // Length of returndata to write.
                )
            )
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     HASHING OPERATIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns an Ethereum Signed Message, created from a `hash`.
    /// This produces a hash corresponding to the one signed with the
    /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
    /// JSON-RPC method as part of EIP-191.
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x20, hash) // Store into scratch space for keccak256.
            mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes.
            result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`.
        }
    }

    /// @dev Returns an Ethereum Signed Message, created from `s`.
    /// This produces a hash corresponding to the one signed with the
    /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
    /// JSON-RPC method as part of EIP-191.
    /// Note: Supports lengths of `s` up to 999999 bytes.
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) {
        /// @solidity memory-safe-assembly
        assembly {
            let sLength := mload(s)
            let o := 0x20
            mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded.
            mstore(0x00, 0x00)
            // Convert the `s.length` to ASCII decimal representation: `base10(s.length)`.
            for { let temp := sLength } 1 {} {
                o := sub(o, 1)
                mstore8(o, add(48, mod(temp, 10)))
                temp := div(temp, 10)
                if iszero(temp) { break }
            }
            let n := sub(0x3a, o) // Header length: `26 + 32 - o`.
            // Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes.
            returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20))
            mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header.
            result := keccak256(add(s, sub(0x20, n)), add(n, sLength))
            mstore(s, sLength) // Restore the length.
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   EMPTY CALLDATA HELPERS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns an empty calldata bytes.
    function emptySignature() internal pure returns (bytes calldata signature) {
        /// @solidity memory-safe-assembly
        assembly {
            signature.length := 0
        }
    }
}

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

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
/// - For ERC20s, this implementation won't check that a token has code,
///   responsibility is delegated to the caller.
library SafeTransferLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ETH transfer has failed.
    error ETHTransferFailed();

    /// @dev The ERC20 `transferFrom` has failed.
    error TransferFromFailed();

    /// @dev The ERC20 `transfer` has failed.
    error TransferFailed();

    /// @dev The ERC20 `approve` has failed.
    error ApproveFailed();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
    uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;

    /// @dev Suggested gas stipend for contract receiving ETH to perform a few
    /// storage reads and writes, but low enough to prevent griefing.
    uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       ETH OPERATIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
    //
    // The regular variants:
    // - Forwards all remaining gas to the target.
    // - Reverts if the target reverts.
    // - Reverts if the current contract has insufficient balance.
    //
    // The force variants:
    // - Forwards with an optional gas stipend
    //   (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
    // - If the target reverts, or if the gas stipend is exhausted,
    //   creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
    //   Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
    // - Reverts if the current contract has insufficient balance.
    //
    // The try variants:
    // - Forwards with a mandatory gas stipend.
    // - Instead of reverting, returns whether the transfer succeeded.

    /// @dev Sends `amount` (in wei) ETH to `to`.
    function safeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Sends all the ETH in the current contract to `to`.
    function safeTransferAllETH(address to) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer all the ETH and check if it succeeded or not.
            if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if lt(selfbalance(), amount) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
            if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
    function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
    function forceSafeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if lt(selfbalance(), amount) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
            if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
    function forceSafeTransferAllETH(address to) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // forgefmt: disable-next-item
            if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
        }
    }

    /// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
    function trySafeTransferAllETH(address to, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      ERC20 OPERATIONS                      */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x60, amount) // Store the `amount` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends all of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have their entire balance approved for
    /// the current contract to manage.
    function safeTransferAllFrom(address token, address from, address to)
        internal
        returns (uint256 amount)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            // Read the balance, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
            amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransfer(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sends all of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransferAll(address token, address to) internal returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
            mstore(0x20, address()) // Store the address of the current contract.
            // Read the balance, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x14, to) // Store the `to` argument.
            amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
            mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// Reverts upon failure.
    function safeApprove(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
            // Perform the approval, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
    /// then retries the approval again (some tokens, e.g. USDT, requires this).
    /// Reverts upon failure.
    function safeApproveWithRetry(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
            // Perform the approval, retrying upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x34, 0) // Store 0 for the `amount`.
                mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
                pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
                mstore(0x34, amount) // Store back the original `amount`.
                // Retry the approval, reverting upon failure.
                if iszero(
                    and(
                        or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                        call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                    )
                ) {
                    mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Returns the amount of ERC20 `token` owned by `account`.
    /// Returns zero if the `token` does not exist.
    function balanceOf(address token, address account) internal view returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, account) // Store the `account` argument.
            mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            amount :=
                mul(
                    mload(0x20),
                    and( // The arguments of `and` are evaluated from right to left.
                        gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                        staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
                    )
                )
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;

import "./IERC165.sol";

/// @title ERC-721 Non-Fungible Token Standard
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// Note: the ERC-165 identifier for this interface is 0x80ac58cd.
interface IERC721 is IERC165 {
    /// @dev This emits when ownership of any NFT changes by any mechanism.
    /// This event emits when NFTs are created (`from` == 0) and destroyed
    /// (`to` == 0). Exception: during contract creation, any number of NFTs
    /// may be created and assigned without emitting Transfer. At the time of
    /// any transfer, the approved address for that NFT (if any) is reset to none.
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);

    /// @dev This emits when the approved address for an NFT is changed or
    /// reaffirmed. The zero address indicates there is no approved address.
    /// When a Transfer event emits, this also indicates that the approved
    /// address for that NFT (if any) is reset to none.
    event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

    /// @dev This emits when an operator is enabled or disabled for an owner.
    /// The operator can manage all NFTs of the owner.
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

    /// @notice Count all NFTs assigned to an owner
    /// @dev NFTs assigned to the zero address are considered invalid, and this
    /// function throws for queries about the zero address.
    /// @param _owner An address for whom to query the balance
    /// @return The number of NFTs owned by `_owner`, possibly zero
    function balanceOf(address _owner) external view returns (uint256);

    /// @notice Find the owner of an NFT
    /// @dev NFTs assigned to zero address are considered invalid, and queries
    /// about them do throw.
    /// @param _tokenId The identifier for an NFT
    /// @return The address of the owner of the NFT
    function ownerOf(uint256 _tokenId) external view returns (address);

    /// @notice Transfers the ownership of an NFT from one address to another address
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    /// operator, or the approved address for this NFT. Throws if `_from` is
    /// not the current owner. Throws if `_to` is the zero address. Throws if
    /// `_tokenId` is not a valid NFT. When transfer is complete, this function
    /// checks if `_to` is a smart contract (code size > 0). If so, it calls
    /// `onERC721Received` on `_to` and throws if the return value is not
    /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    /// @param data Additional data with no specified format, sent in call to `_to`
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external payable;

    /// @notice Transfers the ownership of an NFT from one address to another address
    /// @dev This works identically to the other function with an extra data parameter,
    /// except this function just sets data to "".
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

    /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
    /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
    /// THEY MAY BE PERMANENTLY LOST
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    /// operator, or the approved address for this NFT. Throws if `_from` is
    /// not the current owner. Throws if `_to` is the zero address. Throws if
    /// `_tokenId` is not a valid NFT.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

    /// @notice Change or reaffirm the approved address for an NFT
    /// @dev The zero address indicates there is no approved address.
    /// Throws unless `msg.sender` is the current NFT owner, or an authorized
    /// operator of the current owner.
    /// @param _approved The new approved NFT controller
    /// @param _tokenId The NFT to approve
    function approve(address _approved, uint256 _tokenId) external payable;

    /// @notice Enable or disable approval for a third party ("operator") to manage
    /// all of `msg.sender`'s assets
    /// @dev Emits the ApprovalForAll event. The contract MUST allow
    /// multiple operators per owner.
    /// @param _operator Address to add to the set of authorized operators
    /// @param _approved True if the operator is approved, false to revoke approval
    function setApprovalForAll(address _operator, bool _approved) external;

    /// @notice Get the approved address for a single NFT
    /// @dev Throws if `_tokenId` is not a valid NFT.
    /// @param _tokenId The NFT to find the approved address for
    /// @return The approved address for this NFT, or the zero address if there is none
    function getApproved(uint256 _tokenId) external view returns (address);

    /// @notice Query if an address is an authorized operator for another address
    /// @param _owner The address that owns the NFTs
    /// @param _operator The address that acts on behalf of the owner
    /// @return True if `_operator` is an approved operator for `_owner`, false otherwise
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02.
interface IERC721TokenReceiver {
    /// @notice Handle the receipt of an NFT
    /// @dev The ERC721 smart contract calls this function on the recipient
    /// after a `transfer`. This function MAY throw to revert and reject the
    /// transfer. Return of other than the magic value MUST result in the
    /// transaction being reverted.
    /// Note: the contract address is always the message sender.
    /// @param _operator The address which called `safeTransferFrom` function
    /// @param _from The address which previously owned the token
    /// @param _tokenId The NFT identifier which is being transferred
    /// @param _data Additional data with no specified format
    /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
    ///  unless throwing
    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data)
        external
        returns (bytes4);
}

/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// Note: the ERC-165 identifier for this interface is 0x5b5e139f.
interface IERC721Metadata is IERC721 {
    /// @notice A descriptive name for a collection of NFTs in this contract
    function name() external view returns (string memory _name);

    /// @notice An abbreviated name for NFTs in this contract
    function symbol() external view returns (string memory _symbol);

    /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
    /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
    /// 3986. The URI may point to a JSON file that conforms to the "ERC721
    /// Metadata JSON Schema".
    function tokenURI(uint256 _tokenId) external view returns (string memory);
}

/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// Note: the ERC-165 identifier for this interface is 0x780e9d63.
interface IERC721Enumerable is IERC721 {
    /// @notice Count NFTs tracked by this contract
    /// @return A count of valid NFTs tracked by this contract, where each one of
    /// them has an assigned and queryable owner not equal to the zero address
    function totalSupply() external view returns (uint256);

    /// @notice Enumerate valid NFTs
    /// @dev Throws if `_index` >= `totalSupply()`.
    /// @param _index A counter less than `totalSupply()`
    /// @return The token identifier for the `_index`th NFT,
    /// (sort order not specified)
    function tokenByIndex(uint256 _index) external view returns (uint256);

    /// @notice Enumerate NFTs assigned to an owner
    /// @dev Throws if `_index` >= `balanceOf(_owner)` or if
    /// `_owner` is the zero address, representing invalid NFTs.
    /// @param _owner An address where we are interested in NFTs owned by them
    /// @param _index A counter less than `balanceOf(_owner)`
    /// @return The token identifier for the `_index`th NFT assigned to `_owner`,
    /// (sort order not specified)
    function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;

import "./IERC165.sol";

/// @title ERC-1155 Multi Token Standard
/// @dev See https://eips.ethereum.org/EIPS/eip-1155
/// Note: The ERC-165 identifier for this interface is 0xd9b67a26.
interface IERC1155 is IERC165 {
    /// @dev
    /// - Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard).
    /// - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender).
    /// - The `_from` argument MUST be the address of the holder whose balance is decreased.
    /// - The `_to` argument MUST be the address of the recipient whose balance is increased.
    /// - The `_id` argument MUST be the token type being transferred.
    /// - The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by.
    /// - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address).
    /// - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address).
    event TransferSingle(
        address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value
    );

    /// @dev
    /// - Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard).
    /// - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender).
    /// - The `_from` argument MUST be the address of the holder whose balance is decreased.
    /// - The `_to` argument MUST be the address of the recipient whose balance is increased.
    /// - The `_ids` argument MUST be the list of tokens being transferred.
    /// - The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by.
    /// - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address).
    /// - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address).
    event TransferBatch(
        address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values
    );

    /// @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled).
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

    /// @dev MUST emit when the URI is updated for a token ID. URIs are defined in RFC 3986.
    /// The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema".
    event URI(string _value, uint256 indexed _id);

    /// @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call).
    /// @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard).
    /// - MUST revert if `_to` is the zero address.
    /// - MUST revert if balance of holder for token `_id` is lower than the `_value` sent.
    /// - MUST revert on any other error.
    /// - MUST emit the `TransferSingle` event to reflect the balance change (see "Safe Transfer Rules" section of the standard).
    /// - After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard).
    /// @param _from Source address
    /// @param _to Target address
    /// @param _id ID of the token type
    /// @param _value Transfer amount
    /// @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to`
    function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;

    /// @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call).
    /// @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard).
    /// - MUST revert if `_to` is the zero address.
    /// - MUST revert if length of `_ids` is not the same as length of `_values`.
    /// - MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient.
    /// - MUST revert on any other error.
    /// - MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see "Safe Transfer Rules" section of the standard).
    /// - Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).
    /// - After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard).
    /// @param _from Source address
    /// @param _to Target address
    /// @param _ids IDs of each token type (order and length must match _values array)
    /// @param _values Transfer amounts per token type (order and length must match _ids array)
    /// @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to`
    function safeBatchTransferFrom(
        address _from,
        address _to,
        uint256[] calldata _ids,
        uint256[] calldata _values,
        bytes calldata _data
    ) external;

    /// @notice Get the balance of an account's tokens.
    /// @param _owner The address of the token holder
    /// @param _id ID of the token
    /// @return The _owner's balance of the token type requested
    function balanceOf(address _owner, uint256 _id) external view returns (uint256);

    /// @notice Get the balance of multiple account/token pairs
    /// @param _owners The addresses of the token holders
    /// @param _ids ID of the tokens
    /// @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair)
    function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids)
        external
        view
        returns (uint256[] memory);

    /// @notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens.
    /// @dev MUST emit the ApprovalForAll event on success.
    /// @param _operator Address to add to the set of authorized operators
    /// @param _approved True if the operator is approved, false to revoke approval
    function setApprovalForAll(address _operator, bool _approved) external;

    /// @notice Queries the approval status of an operator for a given owner.
    /// @param _owner The owner of the tokens
    /// @param _operator Address of authorized operator
    /// @return True if the operator is approved, false if not
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;

/// @dev Interface of the ERC20 standard as defined in the EIP.
/// @dev This includes the optional name, symbol, and decimals metadata.
interface IERC20 {
    /// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
    event Transfer(address indexed from, address indexed to, uint256 value);

    /// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value`
    /// is the new allowance.
    event Approval(address indexed owner, address indexed spender, uint256 value);

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

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

    /// @notice Moves `amount` tokens from the caller's account to `to`.
    function transfer(address to, uint256 amount) external returns (bool);

    /// @notice Returns the remaining number of tokens that `spender` is allowed
    /// to spend on behalf of `owner`
    function allowance(address owner, address spender) external view returns (uint256);

    /// @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
    /// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
    function approve(address spender, uint256 amount) external returns (bool);

    /// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism.
    /// `amount` is then deducted from the caller's allowance.
    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    /// @notice Returns the name of the token.
    function name() external view returns (string memory);

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

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

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

interface ILegacyWrappedPunks {
    function transferFrom(address from, address to, uint256 tokenId) external;
    function proxyInfo(address) external view returns (address);
    function registerProxy() external;
    function burn(uint256 punkId) external;
    function mint(uint256 punkId) external;
    function approve(address to, uint256 punkId) external;
    function ownerOf(uint256 punkId) external view returns (address);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

interface ICryptoPunks721 {
    function wrapPunk(uint256 punkIndex) external;

    function unwrapPunk(uint256 punkIndex) external;

    function wrapPunkBatch(uint256[] calldata punkIndexes) external;

    function unwrapPunkBatch(uint256[] calldata punkIndexes) external;

    function migrateLegacyWrappedPunks(uint256[] calldata punkIndexes) external;

    function transferFrom(address from, address to, uint256 tokenId) external;

    function approve(address to, uint256 punkId) external;

    function ownerOf(uint256 punkId) external view returns (address);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

interface ICryptoPunks {
    function punksOfferedForSale(uint256)
        external
        view
        returns (bool isForSale, uint256 punkIndex, address seller, uint256 minValue, address onlySellTo);
    function buyPunk(uint256) external payable;
    function transferPunk(address, uint256) external;
    function balanceOf(address) external view returns (uint256);
    function punkIndexToAddress(uint256) external view returns (address);
    function pendingWithdrawals(address) external view returns (uint256);
    function offerPunkForSaleToAddress(uint256, uint256, address) external;
    function getPunk(uint256 punkId) external;
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

interface IStashFactory {
    function isStash(address stash) external view returns (bool);
    function deployStash(address owner) external returns (address);
    function isAuction(address auction) external view returns (bool);
    function stashAddressFor(address owner) external view returns (address);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IPunkTransferHelper {
    function transfer721PunkToStash(bytes32 _packedData) external;
    function transferLegacyWrappedPunkToStash(bytes32 _packedData) external;
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;

interface IERC165 {
    /// @notice Query if a contract implements an interface
    /// @param interfaceID The interface identifier, as specified in ERC-165
    /// @dev Interface identification is specified in ERC-165. This function
    /// uses less than 30,000 gas.
    /// @return `true` if the contract implements `interfaceID` and
    /// `interfaceID` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

Settings
{
  "remappings": [
    "forge-std/=lib/forge-std/src/",
    "@openzeppelin/=lib/openzeppelin-contracts/",
    "ERC721A/=lib/ERC721A/contracts/",
    "solady/=lib/solady/src/",
    "soladytest/=lib/solady/test/",
    "sol-json/=lib/sol-json/src/",
    "ds-test/=lib/forge-std/lib/ds-test/src/",
    "solmate/=lib/sol-json/lib/solady/lib/solmate/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "abi"
      ]
    }
  },
  "evmVersion": "paris",
  "libraries": {}
}

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"stashFactory","type":"address"},{"internalType":"address","name":"weth","type":"address"},{"internalType":"address","name":"punks","type":"address"},{"internalType":"address","name":"legacyWrappedPunks","type":"address"},{"internalType":"address","name":"cryptoPunksWrapped","type":"address"},{"internalType":"address","name":"punkTransferHelper","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"BidCanceled","type":"error"},{"inputs":[],"name":"BidExpired","type":"error"},{"inputs":[],"name":"CallerNotAuction","type":"error"},{"inputs":[],"name":"CannotTransferMoreThanBidAmount","type":"error"},{"inputs":[],"name":"FailedToBuyPunk","type":"error"},{"inputs":[],"name":"FailedToWithdraw","type":"error"},{"inputs":[],"name":"InvalidBid","type":"error"},{"inputs":[],"name":"InvalidOrderAlteration","type":"error"},{"inputs":[],"name":"InvalidProof","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"NoBidForAuction","type":"error"},{"inputs":[],"name":"OrderNotFound","type":"error"},{"inputs":[],"name":"RequestExceedsAvailableBalance","type":"error"},{"inputs":[],"name":"TooManyOrders","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"UnknownOrderType","type":"error"},{"anonymous":false,"inputs":[],"name":"AllPunkBidsCanceled","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint16","name":"numberOfUnits","type":"uint16"},{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"address","name":"auction","type":"address"}],"indexed":false,"internalType":"struct Order","name":"order","type":"tuple"}],"name":"OrderPlaced","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint16","name":"numberOfUnits","type":"uint16"},{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"address","name":"auction","type":"address"}],"indexed":false,"internalType":"struct Order","name":"order","type":"tuple"}],"name":"OrderRemoved","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint16","name":"numberOfUnits","type":"uint16"},{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"address","name":"auction","type":"address"}],"indexed":false,"internalType":"struct Order","name":"originalOrder","type":"tuple"},{"components":[{"internalType":"uint16","name":"numberOfUnits","type":"uint16"},{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"address","name":"auction","type":"address"}],"indexed":false,"internalType":"struct Order","name":"updatedOrder","type":"tuple"}],"name":"OrderUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"price","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"punkIndex","type":"uint256"}],"name":"PunkBidAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"bidNonce","type":"uint256"}],"name":"PunkBidCanceled","type":"event"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"availableLiquidity","outputs":[{"internalType":"uint256","name":"availableAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"availableLiquidityWETHAndETH","outputs":[{"internalType":"uint256","name":"availableAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelAllPunkBids","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"bidNonce","type":"uint256"}],"name":"cancelPunkBid","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"auction","type":"address"}],"name":"getOrder","outputs":[{"components":[{"internalType":"uint16","name":"numberOfUnits","type":"uint16"},{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"address","name":"auction","type":"address"}],"internalType":"struct Order","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"paymentToken","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"paymentTokenToOrders","outputs":[{"internalType":"uint16","name":"numberOfUnits","type":"uint16"},{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"address","name":"auction","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"uint16","name":"numberOfUnits","type":"uint16"}],"name":"placeOrder","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint80","name":"costPerUnit","type":"uint80"},{"internalType":"uint16","name":"numberOfUnits","type":"uint16"}],"name":"processOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"uint16","name":"numberOfUnits","type":"uint16"},{"internalType":"uint80","name":"pricePerUnit","type":"uint80"},{"internalType":"address","name":"auction","type":"address"}],"internalType":"struct Order","name":"order","type":"tuple"},{"internalType":"uint256","name":"accountNonce","type":"uint256"},{"internalType":"uint256","name":"bidNonce","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"bytes32","name":"root","type":"bytes32"}],"internalType":"struct PunkBid","name":"bid","type":"tuple"},{"internalType":"uint256","name":"punkIndex","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"name":"processPunkBid","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"punkAccountNonce","outputs":[{"internalType":"uint56","name":"","type":"uint56"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"punkBidNonce","type":"uint256"}],"name":"punkBidNonceUsesRemaining","outputs":[{"internalType":"uint256","name":"usesRemaining","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"totalLocked","outputs":[{"internalType":"uint256","name":"lockedAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"punkBidNonce","type":"uint256"}],"name":"usedPunkBidNonces","outputs":[{"internalType":"bool","name":"isUsed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"withdrawERC1155","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"withdrawERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"withdrawPunks","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"punkIndex","type":"uint256"}],"name":"wrapPunk","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

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.