ETH Price: $2,078.92 (-3.39%)

Transaction Decoder

Block:
16986221 at Apr-06-2023 12:54:47 AM +UTC
Transaction Fee:
0.001804293418310774 ETH $3.75
Gas Used:
76,577 Gas / 23.561819062 Gwei

Emitted Events:

205 BPX.Transfer( from=[Sender] 0xcc64bc21be22a5207639593bfba4a971e70c7745, to=Escrow, amount=420000000000 )
206 Escrow.Deposit( amount=420000000000, depositer=[Sender] 0xcc64bc21be22a5207639593bfba4a971e70c7745 )
207 BPX.Transfer( from=Escrow, to=0xBEEA79Aa02534D1a7466cb49447B62308750C95e, amount=324000000000 )
208 Escrow.Withdrawal( amount=324000000000, withdrawer=0xBEEA79Aa02534D1a7466cb49447B62308750C95e )
209 Auction.Bid( auctionId=40, when=1680742487, bidder=[Sender] 0xcc64bc21be22a5207639593bfba4a971e70c7745, amount=350000000000 )

Account State Difference:

  Address   Before After State Difference Code
0x313408eb...845D0992F
(Lido: Execution Layer Rewards Vault)
120.316101378096105081 Eth120.316109035796105081 Eth0.0000076577
0xbB37a885...883aE0ef9
0xCc64bc21...1E70c7745
0.095045500464147449 Eth
Nonce: 37
0.093241207045836675 Eth
Nonce: 38
0.001804293418310774

Execution Trace

Auction.bid( auctionId=40, amount=350000000000, revertOnFail=True )
  • Escrow.deposit( spender=0xCc64bc21bE22A5207639593BfBa4a971E70c7745, amount=420000000000 )
    • BPX.transferFrom( from=0xCc64bc21bE22A5207639593BfBa4a971E70c7745, to=0xB8fFbE08694B845e66b436B3805d8c3E1C3c4373, amount=420000000000 ) => ( True )
    • Escrow.withdraw( recipient=0xBEEA79Aa02534D1a7466cb49447B62308750C95e, amount=324000000000 )
      • BPX.transfer( to=0xBEEA79Aa02534D1a7466cb49447B62308750C95e, amount=324000000000 ) => ( True )
        File 1 of 3: Auction
        // SPDX-License-Identifier: UNLICENSED
        pragma solidity ^0.8.9;
        import { AuctionOwnable } from "./utils/AuctionOwnable.sol";
        import { ERC165Checker } from "./oz-simplified/ERC165Checker.sol";
        import { ECDSA } from "./oz-simplified/ECDSA.sol";
        import { IEscrow } from "./interfaces/IEscrow.sol";
        import { ISellerToken } from './interfaces/ISellerToken.sol';
        import { ReentrancyGuard } from 'solmate/src/utils/ReentrancyGuard.sol';
        import { Errors } from "./library/errors/Errors.sol";
        enum BidReturnValue {
            Success,
            BidTooLow,
            AuctionClosed,
            ExtendedBidding
        }
        enum AuctionStatus {
            Closed,
            Open,
            InExtended,
            Ended,
            DoesntExist,
            Cancelled
        }
        // Uncomment this line to use console.log
        // import "hardhat/console.sol";
        contract Auction is AuctionOwnable, ReentrancyGuard {
            event AuctionAdded(uint256 indexed auctionId, uint48 startTime, uint48 endTime, uint256 indexed claimId, uint256 indexed tokenId);
            event AuctionChanged(uint256 indexed auctionId, uint48 startTime, uint48 endTime, uint256 indexed claimId);
            event AuctionEnded(uint256 indexed auctionId, address indexed winner, uint128 indexed bid);
            event AuctionAborted(uint256 indexed auctionId, bool indexed refunded, string reason);
            event AuctionProceedsClaimed(uint256 indexed auctionId, address indexed seller, address recipient, uint128 indexed amount);
            event AuctionLotClaimed(uint256 indexed auctionId, uint256 indexed claimId, address indexed winner, address recipient);
            event AuctionClosed(uint256 indexed auctionId);
            event AuctionInExtendedBidding(uint indexed auctionId);
            event BidTooLow(uint256 indexed auctionId, uint128 indexed bid, uint128 indexed minHighBid);
            event Bid(
                uint256 indexed auctionId,
                uint256 when,
                address indexed bidder,
                uint128 indexed amount
            );
            struct AuctionData {
                uint256 claimId;
                // token that grants seller claim rights to this auction
                uint256 tokenId;
                // when can bidding start
                uint48 startTime;
                // when is auction over
                uint48 endTime;
                // time in seconds that extended bidding lasts
                uint32 extendedBiddingTime;
                // how much does each bid have to increment over the last bid
                uint64 minBidIncrement;
                // is auction active (accepting bids)
                bool active;
                // has the winner claimed
                bool claimed;
                // was the auction cancelled
                bool cancelled;
                // basis points for buyer's premium
                uint8 basis;
                // high bidder
                address highBidder;
                // amount of high bid
                uint64 highBid;
                // last bid timestamp
                // storing as a delta value so we can fit it in fewer bits, optimizing
                // the cost of reading/writing it from storage
                uint32 lastBidDelta;
            }
            IEscrow private _escrow;
            ISellerToken public _sellerToken;
            uint256 private _lastId;
            mapping(uint256 => AuctionData) private _auctions;
            mapping(uint256 => uint256) public _tokenAuctionMap;
            bool private _requireAuctionVerification = true;
            constructor() {
                __Ownable_init();
            }
            // function initialize() public initializer {
            // \t__Ownable_init();
            // }
            function setup(address escrow, address sellerToken_, bool requireAuctionVerification_) public onlyOwner {
                if (!ERC165Checker.supportsInterface(escrow, type(IEscrow).interfaceId)) {
                    revert Errors.InterfaceNotSupported();
                }
                _escrow = IEscrow(escrow);
                _requireAuctionVerification = requireAuctionVerification_;
                _sellerToken = ISellerToken(sellerToken_);
            }
            function addAuction(
                uint48 startTime,
                uint48 endTime,
                uint32 extendedBiddingTime,
                uint64 startBid,
                uint64 increment,
                uint8 basis,
                uint256 claimId,
                address seller
            ) public onlyAuctioneer nonReentrant {
                if (endTime < block.timestamp) {
                    revert Errors.OutOfRange(endTime);
                }
                uint256 auctionId = ++_lastId;
                uint256 tokenId = _sellerToken.mint(seller, auctionId);
                _auctions[ auctionId ] = AuctionData({
                    claimId: claimId,
                    tokenId: tokenId,
                    startTime: startTime,
                    endTime: endTime,
                    extendedBiddingTime: extendedBiddingTime,
                    minBidIncrement: increment,
                    active: true,
                    basis: basis,
                    lastBidDelta: uint32(0),
                    highBidder: address(0),
                    highBid: startBid,
                    claimed: false,
                    cancelled: false
                });
                emit AuctionAdded(auctionId, startTime, endTime, claimId, tokenId);
            }
            function editAuction(
                uint256 auctionId,
                uint48 startTime,
                uint48 endTime,
                uint32 extendedBiddingTime,
                uint64 increment,
                uint8 basis,
                uint256 claimId
            ) public onlyAuctioneer {
                _auctions[auctionId].startTime = startTime;
                _auctions[auctionId].endTime = endTime;
                _auctions[auctionId].extendedBiddingTime = extendedBiddingTime;
                _auctions[auctionId].minBidIncrement = increment;
                _auctions[auctionId].basis = basis;
                _auctions[auctionId].claimId = claimId;
                emit AuctionChanged(auctionId, startTime, endTime, claimId);
            }
            function abortAuction(uint256 auctionId, bool issueRefund, string memory reason) public onlyAuctioneer {
                _auctions[auctionId].active = false;
                _auctions[auctionId].cancelled = true;
                _sellerToken.burn(_auctions[auctionId].tokenId);
                if (issueRefund) {
                    address highBidder = _auctions[auctionId].highBidder;
                    if (highBidder != address(0)) {
                        uint8 basis = _auctions[auctionId].basis;
                        uint256 premium = basis == 0 ? 0 : _auctions[auctionId].highBid * basis / 100;
                        _escrow.withdraw(highBidder, _auctions[auctionId].highBid + premium);
                    }
                }
                emit AuctionAborted(auctionId, issueRefund, reason);
            }
            function claimLot(uint256 auctionId, address deliverTo) public nonReentrant {
                AuctionData storage auction = _auctions[ auctionId ];
                if (_requireAuctionVerification) {
                    if ( auction.active ) {
                        revert Errors.AuctionActive(auctionId);
                    }
                }
                if (block.timestamp < auction.endTime + 1) {
                    revert Errors.AuctionActive(auctionId);
                }
                if (_auctionInExtendedBidding(auction)) {
                    revert Errors.AuctionActive(auctionId);
                }
                if (auction.cancelled) {
                    revert Errors.AuctionAborted(auctionId);
                }
                if (auction.claimed) {
                    revert Errors.AlreadyClaimed(auctionId);
                }
                if (_msgSender() != auction.highBidder) {
                    revert Errors.BadSender(auction.highBidder, _msgSender());
                }
                if (false == _requireAuctionVerification) {
                    _escrow.authorizeClaim(auction.claimId, auction.highBidder);
                }
                auction.claimed = true;
                _escrow.claimFor(_msgSender(), auction.claimId, deliverTo);
                emit AuctionLotClaimed(auctionId, auction.claimId, _msgSender(), deliverTo);
            }
            function claimProceeds(uint256 auctionId, address deliverTo) public nonReentrant {
                AuctionData storage auction = _auctions[ auctionId ];
                if (_requireAuctionVerification) {
                    if ( true == auction.active ) {
                        revert Errors.AuctionActive(auctionId);
                    }
                }
                if (block.timestamp < auction.endTime + 1) {
                    revert Errors.AuctionActive(auctionId);
                }
                if (_auctionInExtendedBidding(auction)) {
                    revert Errors.AuctionActive(auctionId);
                }
                if (auction.cancelled) {
                    revert Errors.AuctionAborted(auctionId);
                }
                address tokenOwner = _sellerToken.ownerOf(auction.tokenId);
                if ( _msgSender() != tokenOwner) {
                    revert Errors.BadSender(tokenOwner, _msgSender());
                }
                _sellerToken.burn(auction.tokenId);
                _escrow.withdraw(deliverTo, auction.highBid);
                emit AuctionProceedsClaimed(auctionId, _msgSender(), deliverTo, auction.highBid);
            }
            function confirmAuctions(uint256[] calldata auctionIds, address[] calldata premiumRecipients) public nonReentrant onlyAuctioneer {
                uint256 auctionLength = auctionIds.length;
                if (auctionLength != premiumRecipients.length) {
                    revert Errors.ArrayMismatch();
                }
                for (uint i = 0; i < auctionLength;) {
                    confirmAuction(auctionIds[ i ], premiumRecipients[ i ]);
                    unchecked {
                        ++i;
                    }
                }
            }
            function confirmAuction(uint256 auctionId, address premiumRecipient) public nonReentrant onlyAuctioneer {
                AuctionData storage auction = _auctions[auctionId];
                if (block.timestamp < auction.endTime + 1) {
                    revert Errors.AuctionActive(auctionId);
                }
                if (_auctionInExtendedBidding(auction)) {
                    revert Errors.AuctionActive(auctionId);
                }
                if (auction.cancelled) {
                    revert Errors.AuctionAborted(auctionId);
                }
                // require auctions to be active to call this method, so we don't
                // double-widthdraw the buyer premium
                if (false == auction.active) {
                    revert Errors.AuctionInactive(auctionId);
                }
                auction.active = false;
                emit AuctionEnded(auctionId, auction.highBidder, auction.highBid);
                if (auction.highBidder != address(0)) {
                    if (false == auction.claimed && _requireAuctionVerification) {
                        _escrow.authorizeClaim(auction.claimId, auction.highBidder);
                    }
                    if (auction.basis > 0) {
                        if (address(0) == premiumRecipient) {
                            revert Errors.AddressTarget(premiumRecipient);
                        }
                        _escrow.withdraw(premiumRecipient, auction.highBid * auction.basis / 100);
                    }
                }
            }
            function getAuctionMetadata(uint256 auctionId) public view returns (AuctionData memory) {
                return _auctions[auctionId];
            }
            function bid(
                uint256 auctionId,
                uint64 amount,
                bool revertOnFail
            ) public nonReentrant {
                _bid(_msgSender(), auctionId, amount, revertOnFail);
            }
            function multiBid(
                uint256[] memory auctionIds,
                uint64[] memory amounts,
                bool revertOnFail
            ) public nonReentrant {
                uint256 arrayLength = auctionIds.length;
                if (arrayLength != amounts.length) {
                    revert Errors.ArrayMismatch();
                }
                address bidder = _msgSender();
                for (uint256 i = 0; i < arrayLength;) {
                    _bid(bidder, auctionIds[i], amounts[i], revertOnFail);
                    unchecked {
                        ++i;
                    }
                }
            }
            function auctionStatus(uint256 auctionId) public view returns(AuctionStatus) {
                AuctionData storage a = _auctions[ auctionId ];
                if (a.startTime == 0) {
                    return AuctionStatus.DoesntExist;
                }
                if (a.cancelled) {
                    return AuctionStatus.Cancelled;
                }
                if (block.timestamp < a.startTime) {
                    return AuctionStatus.Closed;
                }
                if (block.timestamp < a.endTime + 1) {
                    return AuctionStatus.Open;
                }
                if (_auctionInExtendedBidding(a)) {
                    return AuctionStatus.InExtended;
                }
                return AuctionStatus.Ended;
            }
            /**
             *  ================== INTERNAL METHODS ====================
             */
            function _bid(
                address bidder,
                uint256 auctionId,
                uint64 amount,
                bool revertOnError
            ) internal returns (BidReturnValue) {
                uint256 timestamp = block.timestamp;
                AuctionData storage auction = _auctions[ auctionId ];
                if (timestamp > auction.endTime) {
                    if ( false == _auctionInExtendedBidding(auction)) {
                       // auction is over
                        if (revertOnError) {
                            revert Errors.AuctionClosed(auctionId);
                        }
                        emit AuctionClosed(auctionId);
                        return BidReturnValue.AuctionClosed;
                    }
                }
                if (false == auction.active) {
                    if (revertOnError) {
                        revert Errors.AuctionClosed(auctionId);
                    }
                    emit AuctionClosed(auctionId);
                    return BidReturnValue.AuctionClosed;
                }
                if (timestamp < auction.startTime) {
                    if (revertOnError) {
                        revert Errors.AuctionClosed(auctionId);
                    }
                    emit AuctionClosed(auctionId);
                    return BidReturnValue.AuctionClosed;
                }
                uint64 previousAmount = auction.highBid;
                // bid is too low
                if (amount < previousAmount + auction.minBidIncrement) {
                    if (revertOnError) {
                        revert Errors.BidTooLow(auctionId, amount, previousAmount + auction.minBidIncrement);
                    }
                    emit BidTooLow(auctionId, amount, previousAmount + auction.minBidIncrement);
                    return BidReturnValue.BidTooLow;
                }
                uint256 premium = auction.basis == 0 ? 0 : amount * auction.basis / 100;
                _escrow.deposit(bidder, amount + premium);
                address prevBidder = auction.highBidder;
                if (prevBidder != address(0)) {
                    uint256 prevPremium = auction.basis == 0 ? 0 : previousAmount * auction.basis / 100;
                    _escrow.withdraw(prevBidder, previousAmount + prevPremium);
                }
                /**
                 * There needs to be 2 bidders on a Lot to send it into extended bidding
                 * when no bids, bidder == 0x0. first bidder != 0x0, so decrement required count
                 * if second bidder != first bidder, decrement required count
                 * once we get to zero, we know we'll be going into extended bidding.
                 */
                // if (0 < auction.extendedBiddingTime) {
                //     if (0 < auction.extBidRequiredBids) {
                //         if (prevBidder != bidder) {
                //             unchecked {
                //                 // can't overflow, value checked to be above zero, above.
                //                 --auction.extBidRequiredBids;
                //             }
                //         }
                //     }
                // }
                auction.highBidder = bidder;
                auction.highBid = amount;
                // only write the bid-time delta if we're in extended bidding
                // it's irrelevant, otherwise.
                if (auction.endTime < timestamp) {
                    auction.lastBidDelta = uint32(timestamp - auction.endTime);
                }
                emit Bid(auctionId, timestamp, bidder, amount);
                return BidReturnValue.Success;
            }
            function _auctionInExtendedBidding(AuctionData storage auction) internal view returns(bool) {
                uint tmpEndTime = auction.endTime;
                if (block.timestamp > auction.endTime) {
                    uint tmpExtTime = auction.extendedBiddingTime;
                    if (0 < tmpExtTime) {
                        uint extendedEndTime = tmpEndTime + tmpExtTime;
                        // uint tmpDelta = auction.lastBidDelta;
                        if (0 < auction.lastBidDelta) {
                            extendedEndTime = tmpEndTime + auction.lastBidDelta + tmpExtTime;
                        }
                        /*
                        * auction is over if we're past the extended bidding time, no matter what
                        *
                        * or
                        *
                        * we require 2+ bids to enter extended bidding. so, if we're past the auction endTime (tested above)
                        * and we need more than 0 bids to enter extended bidding
                        * (required bids is decremented from 2 to 0 on the first two bids)
                        * then we never went into extended bidding, and thus, the auction is over
                        **/
                        if (block.timestamp > extendedEndTime) {
                            // auction is over
                            return false;
                        }
                        // if we get here, then current timestamp is between endTime and extendedEndTime
                        // and aucton.extBidRequireBids == 0
                        return true;
                    }
                }
                return false;
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
        pragma solidity ^0.8.0;
        /**
         * @dev Interface of the ERC165 standard, as defined in the
         * https://eips.ethereum.org/EIPS/eip-165[EIP].
         *
         * Implementers can declare support of contract interfaces, which can then be
         * queried by others ({ERC165Checker}).
         *
         * For an implementation, see {ERC165}.
         */
        interface IERC165 {
            /**
             * @dev Returns true if this contract implements the interface defined by
             * `interfaceId`. See the corresponding
             * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
             * to learn more about how these ids are created.
             *
             * This function call must use less than 30 000 gas.
             */
            function supportsInterface(bytes4 interfaceId) external view returns (bool);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)
        pragma solidity ^0.8.0;
        import "./IERC165.sol";
        /**
         * @dev Required interface of an ERC721 compliant contract.
         */
        interface IERC721 is IERC165 {
            /**
             * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
             */
            event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
            /**
             * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
             */
            event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
            /**
             * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
             */
            event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
            /**
             * @dev Returns the number of tokens in ``owner``'s account.
             */
            function balanceOf(address owner) external view returns (uint256 balance);
            /**
             * @dev Returns the owner of the `tokenId` token.
             *
             * Requirements:
             *
             * - `tokenId` must exist.
             */
            function ownerOf(uint256 tokenId) external view returns (address owner);
            /**
             * @dev Safely transfers `tokenId` token from `from` to `to`.
             *
             * Requirements:
             *
             * - `from` cannot be the zero address.
             * - `to` cannot be the zero address.
             * - `tokenId` token must exist and be owned by `from`.
             * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
             * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received},
             * which is called upon a safe transfer.
             *
             * Emits a {Transfer} event.
             */
            function safeTransferFrom(
                address from,
                address to,
                uint256 tokenId,
                bytes calldata data
            ) external;
            /**
             * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
             * are aware of the ERC721 protocol to prevent tokens from being forever locked.
             *
             * Requirements:
             *
             * - `from` cannot be the zero address.
             * - `to` cannot be the zero address.
             * - `tokenId` token must exist and be owned by `from`.
             * - If the caller is not `from`, it must have been allowed to move this token by either
             * {approve} or {setApprovalForAll}.
             * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received},
             * which is called upon a safe transfer.
             *
             * Emits a {Transfer} event.
             */
            function safeTransferFrom(
                address from,
                address to,
                uint256 tokenId
            ) external;
            /**
             * @dev Transfers `tokenId` token from `from` to `to`.
             *
             * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
             * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
             * understand this adds an external call which potentially creates a reentrancy vulnerability.
             *
             * Requirements:
             *
             * - `from` cannot be the zero address.
             * - `to` cannot be the zero address.
             * - `tokenId` token must be owned by `from`.
             * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
             *
             * Emits a {Transfer} event.
             */
            function transferFrom(
                address from,
                address to,
                uint256 tokenId
            ) external;
            /**
             * @dev Gives permission to `to` to transfer `tokenId` token to another account.
             * The approval is cleared when the token is transferred.
             *
             * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
             *
             * Requirements:
             *
             * - The caller must own the token or be an approved operator.
             * - `tokenId` must exist.
             *
             * Emits an {Approval} event.
             */
            function approve(address to, uint256 tokenId) external;
            /**
             * @dev Approve or remove `operator` as an operator for the caller.
             * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
             *
             * Requirements:
             *
             * - The `operator` cannot be the caller.
             *
             * Emits an {ApprovalForAll} event.
             */
            function setApprovalForAll(address operator, bool _approved) external;
            /**
             * @dev Returns the account approved for `tokenId` token.
             *
             * Requirements:
             *
             * - `tokenId` must exist.
             */
            function getApproved(uint256 tokenId) external view returns (address operator);
            /**
             * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
             *
             * See {setApprovalForAll}
             */
            function isApprovedForAll(address owner, address operator) external view returns (bool);
        }
        // SPDX-License-Identifier: UNLICENSED
        pragma solidity ^0.8.9;
        interface IEscrow {
            event Withdrawal(uint256 indexed amount, address indexed withdrawer);
            event Deposit(uint256 indexed amount, address indexed depositer);
            event ClaimAuthorized(uint256 indexed claimId, address indexed claimant);
            event PrizeAdded(uint256 indexed claimId);
            event PrizeRemoved(uint256 indexed claimId, address indexed recipient);
            event PrizeReceived(uint256 indexed claimId, address indexed recipient);
            function currencyBalance() external returns (uint256);
            function deposit(address spender, uint256 amount) external;
            function withdraw(address recipient, uint256 amount) external;
            function authorizeClaim(uint256 claimId, address claimant) external;
            function claimFor(address claimant, uint256 claimId, address recipient) external;
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)
        pragma solidity ^0.8.0;
        import './IERC721.sol';
        interface ISellerToken is IERC721 {
            function mint(address dest, uint256 tokenId) external returns (uint256);
            function burn(uint256 tokenId) external;
        }
        // SPDX-License-Identifier: UNLICENSED
        pragma solidity >=0.8.4 <0.9.0;
        library Errors {
            error LinkError();
            error ArrayMismatch();
            error OutOfRange(uint256 value);
            error OutOfRangeSigned(int256 value);
            error UnsignedOverflow(uint256 value);
            error SignedOverflow(int256 value);
            error DuplicateCall();
            error NotAContract();
            error InterfaceNotSupported();
            error NotInitialized();
            error AlreadyInitialized();
            error BadSender(address expected, address caller);
            error AddressTarget(address target);
            error UserPermissions();
            error InvalidHash();
            error InvalidSignature();
            error InvalidSignatureLength();
            error InvalidSignatureS();
            error InsufficientBalance(uint256 available, uint256 required);
            error InsufficientSupply(uint256 supply, uint256 available, int256 requested);  // 0x5437b336
            error InsufficientAvailable(uint256 available, uint256 requested);
            error InvalidToken(uint256 tokenId);                                            // 0x925d6b18
            error TokenNotMintable(uint256 tokenId);
            error InvalidTokenType();
            error ERC1155Receiver();
            error ContractPaused();
            error PaymentFailed(uint256 amount);
            error IncorrectPayment(uint256 required, uint256 provided);                     // 0x0d35e921
        \terror TooManyForTransaction(uint256 mintLimit, uint256 amount);
            error AuctionInactive(uint256 auctionId);
            error AuctionActive(uint256 auctionId);
            error InvalidBid(uint256 auctionId, uint256 amount);
            error BidTooLow(uint256 auctionId, uint256 bid, uint256 minBid);
            error AuctionClosed(uint256 auctionId);
            error AuctionInExtendedBidding(uint256 auctionId);
            error AuctionAborted(uint256 auctionId);
            error AlreadyClaimed(uint256 lotId);
        }
        // SPDX-License-Identifier: MIT
        pragma solidity >=0.8.0 <0.9.0;
        import { Initializable } from "./Initializable.sol";
        /**
         * @dev Provides information about the current execution context, including the
         * sender of the transaction and its data. While these are generally available
         * via msg.sender and msg.data, they should not be accessed in such a direct
         * manner, since when dealing with meta-transactions the account sending and
         * paying for execution may not be the actual sender (as far as an application
         * is concerned).
         *
         * This contract is only required for intermediate, library-like contracts.
         */
        abstract contract Context is Initializable {
            function __Context_init() internal initializer {
                __Context_init_unchained();
            }
            function __Context_init_unchained() internal initializer {
            }
            function _msgSender() internal view virtual returns (address) {
                return msg.sender;
            }
            function _msgData() internal view virtual returns (bytes calldata) {
                return msg.data;
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)
        pragma solidity ^0.8.0;
        import { Errors } from "../library/errors/Errors.sol";
        /**
         * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
         *
         * These functions can be used to verify that a message was signed by the holder
         * of the private keys of a given address.
         */
        library ECDSA {
            enum RecoverError {
                NoError,
                InvalidSignature,
                InvalidSignatureLength,
                InvalidSignatureS,
                InvalidSignatureV // Deprecated in v4.8
            }
            function _throwError(RecoverError error) private pure {
                if (error == RecoverError.NoError) {
                    return; // no error: do nothing
                } else if (error == RecoverError.InvalidSignature) {
                    revert Errors.InvalidSignature();
                } else if (error == RecoverError.InvalidSignatureLength) {
                    revert Errors.InvalidSignatureLength();
                } else if (error == RecoverError.InvalidSignatureS) {
                    revert Errors.InvalidSignatureS();
                }
            }
            /**
             * @dev Returns the address that signed a hashed message (`hash`) with
             * `signature` or error string. This address can then be used for verification purposes.
             *
             * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
             * this function rejects them by requiring the `s` value to be in the lower
             * half order, and the `v` value to be either 27 or 28.
             *
             * IMPORTANT: `hash` _must_ be the result of a hash operation for the
             * verification to be secure: it is possible to craft signatures that
             * recover to arbitrary addresses for non-hashed data. A safe way to ensure
             * this is by receiving a hash of the original message (which may otherwise
             * be too long), and then calling {toEthSignedMessageHash} on it.
             *
             * Documentation for signature generation:
             * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
             * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
             *
             * _Available since v4.3._
             */
            function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
                if (signature.length == 65) {
                    bytes32 r;
                    bytes32 s;
                    uint8 v;
                    // ecrecover takes the signature parameters, and the only way to get them
                    // currently is to use assembly.
                    /// @solidity memory-safe-assembly
                    assembly {
                        r := mload(add(signature, 0x20))
                        s := mload(add(signature, 0x40))
                        v := byte(0, mload(add(signature, 0x60)))
                    }
                    return tryRecover(hash, v, r, s);
                } else {
                    return (address(0), RecoverError.InvalidSignatureLength);
                }
            }
            /**
             * @dev Returns the address that signed a hashed message (`hash`) with
             * `signature`. This address can then be used for verification purposes.
             *
             * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
             * this function rejects them by requiring the `s` value to be in the lower
             * half order, and the `v` value to be either 27 or 28.
             *
             * IMPORTANT: `hash` _must_ be the result of a hash operation for the
             * verification to be secure: it is possible to craft signatures that
             * recover to arbitrary addresses for non-hashed data. A safe way to ensure
             * this is by receiving a hash of the original message (which may otherwise
             * be too long), and then calling {toEthSignedMessageHash} on it.
             */
            function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
                (address recovered, RecoverError error) = tryRecover(hash, signature);
                _throwError(error);
                return recovered;
            }
            /**
             * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
             * `r` and `s` signature fields separately.
             *
             * _Available since v4.3._
             */
            function tryRecover(
                bytes32 hash,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) internal pure returns (address, RecoverError) {
                // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
                // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                //
                // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                // these malleable signatures as well.
                if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                    return (address(0), RecoverError.InvalidSignatureS);
                }
                // If the signature is valid (and not malleable), return the signer address
                address signer = ecrecover(hash, v, r, s);
                if (signer == address(0)) {
                    return (address(0), RecoverError.InvalidSignature);
                }
                return (signer, RecoverError.NoError);
            }
            /**
             * @dev Returns an Ethereum Signed Message, created from a `hash`. This
             * produces hash corresponding to the one signed with the
             * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
             * JSON-RPC method as part of EIP-191.
             *
             * See {recover}.
             */
            function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
                // 32 is the length in bytes of hash,
                // enforced by the type signature above
                return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
        32", hash));
            }
            /**
             * @dev Returns an Ethereum Signed Typed Data, created from a
             * `domainSeparator` and a `structHash`. This produces hash corresponding
             * to the one signed with the
             * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
             * JSON-RPC method as part of EIP-712.
             *
             * See {recover}.
             */
            function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
                return keccak256(abi.encodePacked("\\x19\\x01", domainSeparator, structHash));
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.8.0) (utils/introspection/ERC165Checker.sol)
        pragma solidity ^0.8.0;
        import "../interfaces/IERC165.sol";
        /**
         * @dev Library used to query support of an interface declared via {IERC165}.
         *
         * Note that these functions return the actual result of the query: they do not
         * `revert` if an interface is not supported. It is up to the caller to decide
         * what to do in these cases.
         */
        library ERC165Checker {
            // As per the EIP-165 spec, no interface should ever match 0xffffffff
            bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff;
            /**
             * @dev Returns true if `account` supports the {IERC165} interface.
             */
            function supportsERC165(address account) internal view returns (bool) {
                // Any contract that implements ERC165 must explicitly indicate support of
                // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
                return
                    supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
                    !supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID);
            }
            /**
             * @dev Returns true if `account` supports the interface defined by
             * `interfaceId`. Support for {IERC165} itself is queried automatically.
             *
             * See {IERC165-supportsInterface}.
             */
            function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
                // query support of both ERC165 as per the spec and support of _interfaceId
                return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
            }
            /**
             * @notice Query if a contract implements an interface, does not check ERC165 support
             * @param account The address of the contract to query for support of an interface
             * @param interfaceId The interface identifier, as specified in ERC-165
             * @return true if the contract at account indicates support of the interface with
             * identifier interfaceId, false otherwise
             * @dev Assumes that account contains a contract that supports ERC165, otherwise
             * the behavior of this method is undefined. This precondition can be checked
             * with {supportsERC165}.
             * Interface identification is specified in ERC-165.
             */
            function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
                // prepare call
                bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId);
                // perform static call
                bool success;
                uint256 returnSize;
                uint256 returnValue;
                assembly {
                    success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
                    returnSize := returndatasize()
                    returnValue := mload(0x00)
                }
                return success && returnSize >= 0x20 && returnValue > 0;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity >=0.8.4 <0.9.0;
        import { Errors } from "../library/errors/Errors.sol";
        /**
         * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
         * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an
         * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
         * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
         *
         * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
         * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
         *
         * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
         * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
         */
        abstract contract Initializable {
            /**
             * @dev Indicates that the contract has been initialized.
             */
            bool private _initialized;
            /**
             * @dev Indicates that the contract is in the process of being initialized.
             */
            bool private _initializing;
            /**
             * @dev Modifier to protect an initializer function from being invoked twice.
             */
            modifier initializer() {
                // require(_initializing || !_initialized, "Initializable: contract is already initialized");
                if (!_initializing && _initialized) revert Errors.AlreadyInitialized();
                bool isTopLevelCall = !_initializing;
                if (isTopLevelCall) {
                    _initializing = true;
                    _initialized = true;
                }
                _;
                if (isTopLevelCall) {
                    _initializing = false;
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity >=0.8.4 <0.9.0;
        import { Context } from "../oz-simplified/Context.sol";
        import { Initializable } from "../oz-simplified/Initializable.sol";
        import { Errors } from "../library/errors/Errors.sol";
        /**
         * @dev Contract module which provides a basic access control mechanism, where
         * there are two accounts (an owner and a proxy) that can be granted exclusive
         * access to specific functions. Only the owner can set the proxy.
         *
         * By default, the owner account will be the one that deploys the contract. This
         * can later be changed with {transferOwnership}.
         *
         * This module is used through inheritance. It will make available the modifier
         * `onlyOwner`, which can be applied to your functions to restrict their use to
         * the owner.
         */
        abstract contract AuctionOwnable is Initializable, Context {
            address private _owner;
            address private _auctioneer;
            address private _broker;
            event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
            /**
             * @dev Initializes the contract setting the deployer as the initial owner.
             */
            function __Ownable_init() internal initializer {
                __Context_init_unchained();
                __Ownable_init_unchained();
            }
            function __Ownable_init_unchained() internal initializer {
                _setOwner(_msgSender());
            }
            /**
             * @dev Returns the address of the current owner.
             */
            function owner() public view virtual returns (address) {
                return _owner;
            }
            // /**
            //  * @dev Returns the address of the current auctioneer.
            //  */
            // function auctioneer() public view virtual returns (address) {
            //     return _auctioneer;
            // }
            // /**
            //  * @dev Returns the address of the current broker.
            //  */
            // function broker() public view virtual returns (address) {
            //     return _broker;
            // }
            /**
             * @dev Returns true if the account has the auctioneer role.
             */
            function isAuctioneer(address account) public view virtual returns (bool) {
                return account == _auctioneer;
            }
            /**
             * @dev Returns true if the account has the broker role.
             */
            function isBroker(address account) public view virtual returns (bool) {
                return account == _broker;
            }
            /**
             * @dev Throws if called by any account other than the owner.
             */
            modifier onlyOwner() {
                if (_owner != _msgSender()) revert Errors.UserPermissions();
                _;
            }
            /**
             * @dev Throws if called by any account other than the auctioneer.
             */
            modifier onlyAuctioneer() {
                if (
                    _auctioneer != _msgSender()
                    && _owner != _msgSender()
                ) revert Errors.UserPermissions();
                _;
            }
            /**
             * @dev Throws if called by any account other than the broker.
             */
            modifier onlyBroker() {
                if (
                    _broker != _msgSender()
                    && _owner != _msgSender()
                ) revert Errors.UserPermissions();
                _;
            }
            /**
             * @dev Transfers ownership of the contract to a new account (`newOwner`).
             * Can only be called by the current owner.
             */
            function transferOwnership(address newOwner) public virtual onlyOwner {
                if (newOwner == address(0)) revert Errors.AddressTarget(newOwner);
                _setOwner(newOwner);
            }
            /**
             * @dev Sets the auctioneer for the contract to a new account (`newAuctioneer`).
             * Can only be called by the current owner.
             */
            function setAuctioneer(address newAuctioneer) public virtual onlyOwner {
                _auctioneer = newAuctioneer;
            }
            /**
             * @dev Sets the auctioneer for the contract to a new account (`newAuctioneer`).
             * Can only be called by the current owner.
             */
            function setBroker(address newBroker) public virtual onlyOwner {
                _broker = newBroker;
            }
            function _setOwner(address newOwner) internal {
                address oldOwner = _owner;
                _owner = newOwner;
                emit OwnershipTransferred(oldOwner, newOwner);
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        /// @notice Gas optimized reentrancy protection for smart contracts.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
        /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
        abstract contract ReentrancyGuard {
            uint256 private locked = 1;
            modifier nonReentrant() virtual {
                require(locked == 1, "REENTRANCY");
                locked = 2;
                _;
                locked = 1;
            }
        }
        

        File 2 of 3: Escrow
        // SPDX-License-Identifier: UNLICENSED
        pragma solidity ^0.8.9;
        import { IERC20 } from "./interfaces/IERC20.sol";
        import { IERC721 } from "./interfaces/IERC721.sol";
        import { IERC1155 } from "./interfaces/IERC1155.sol";
        import { IERC721Receiver } from "./interfaces/IERC721Receiver.sol";
        import { IERC1155Receiver } from "./interfaces/IERC1155Receiver.sol";
        import { IERC165 } from "./interfaces/IERC165.sol";
        import { EscrowOwnable } from "./utils/EscrowOwnable.sol";
        import { Context } from "./oz-simplified/Context.sol";
        import { Initializable } from "./oz-simplified/Initializable.sol";
        import { IEscrow } from "./interfaces/IEscrow.sol";
        import { Errors } from "./library/errors/Errors.sol";
        // Uncomment this line to use console.log
        // import "hardhat/console.sol";
        contract Escrow is IEscrow, IERC165, IERC721Receiver, IERC1155Receiver, EscrowOwnable {
            struct PrizeToken {
                uint256 tokenId;
                address token;
                uint8 tokenType;
                uint16 quantity;
            }
            uint8 constant TYPE_ERC721 = 2;
            uint8 constant TYPE_ERC1155 = 3;
            IERC20 private _currencyContract;
            uint256 private _lastId;
            mapping(uint256 => PrizeToken[]) private _prizes;
            mapping(uint256 => address) private _claims;
            constructor(address currency) {
                // confirm currency is a contract
                if (currency.code.length == 0) {
                    revert Errors.NotAContract();
                }
                _currencyContract = IERC20(currency);
            }
            /**
             * @dev See {IERC165-supportsInterface}.
             */
            function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165) returns (bool) {
                return (
                    interfaceId == type(IEscrow).interfaceId
                    || interfaceId == type(IERC721Receiver).interfaceId
                    || interfaceId == type(IERC1155Receiver).interfaceId
                    || interfaceId == type(IERC165).interfaceId
                );
            }
            function updateCurrency(address newCurrencyAddress) external onlyOwner {
               if (newCurrencyAddress.code.length == 0) {
                    revert Errors.NotAContract();
                }
                _currencyContract = IERC20(newCurrencyAddress);
            }
            function currencyBalance() public view returns (uint256) {
                return _currencyContract.balanceOf(address(this));
            }
            function deposit(address spender, uint256 amount) public onlyAuthorized {
                _currencyContract.transferFrom(spender, address(this), amount);
                emit Deposit(amount, spender);
            }
            function withdraw(address recipient, uint256 amount) public onlyAuthorized {
                _currencyContract.transfer(recipient, amount);
                emit Withdrawal(amount, recipient);
            }
            function getPrizeInfo(uint256 claimId) public view returns (PrizeToken[] memory) {
                return _prizes[claimId];
            }
            function addPrize(
                address[] calldata tokens,
                uint256[] calldata tokenIds,
                uint8[] calldata tokenTypes,
                uint16[] calldata quantities
            ) public onlyAuthorized {
                uint256 arrayLength = tokens.length;
                if (
                    arrayLength != tokenIds.length
                    || arrayLength != tokenTypes.length
                    || arrayLength != quantities.length
                ) {
                    revert Errors.ArrayMismatch();
                }
                uint256 claimId = ++_lastId;
                for (uint256 i = 0; i < arrayLength;) {
                    PrizeToken storage prize = _prizes[claimId].push();
                    prize.token = tokens[i];
                    prize.tokenId = tokenIds[i];
                    prize.tokenType = tokenTypes[i];
                    prize.quantity = quantities[i];
                    unchecked {
                        ++i;
                    }
                }
                _transferPrize(claimId, msg.sender, address(this));
                emit PrizeAdded(claimId);
            }
            function removePrize(uint256 claimId, address to) public onlyAuthorized {
                _transferPrize(claimId, address(this), to);
                // delete the PrizeToken array to get a gas refund
                // iterating to delete each struct costs more than we save
                delete _prizes[claimId];
                emit PrizeRemoved(claimId, to);
            }
            function authorizeClaim(uint256 claimId, address claimant) public onlyAuthorized {
                if ( _prizes[ claimId ].length == 0) {
                    revert Errors.AlreadyClaimed(claimId);
                }
                _claims[claimId] = claimant;
                emit ClaimAuthorized(claimId, claimant);
            }
            function authorizedClaimant(uint256 claimId) public view returns (address) {
                return _claims[claimId];
            }
            function claim(uint256 claimId, address destination) public {
                _claim(claimId, msg.sender, destination);
            }
            function claimFor(address claimant, uint256 claimId, address destination) onlyAuthorized public {
                _claim(claimId, claimant, destination);
            }
            function _claim(uint256 claimId, address claimant, address recipient) internal {
               if (claimant != _claims[claimId]) {
                    revert Errors.BadSender(_claims[claimId], claimant);
                }
                _transferPrize(claimId, address(this), recipient);
                emit PrizeReceived(claimId, recipient);
                // cancel the authorization and receive a gas refund
                _claims[claimId] = address(0);
                // delete the PrizeToken array to get a gas refund
                // iterating to delete each struct costs more than we save
                delete _prizes[claimId];
            }
            function _transferPrize(uint256 claimId, address from, address to) internal {
                PrizeToken[] memory prize = _prizes[claimId];
                for (uint256 i = 0; i < prize.length;) {
                    if (prize[i].tokenType == TYPE_ERC721) {
                        IERC721 ct = IERC721(prize[i].token);
                        ct.safeTransferFrom(from, to, prize[i].tokenId);
                    } else if (prize[i].tokenType == TYPE_ERC1155) {
                        IERC1155 ct = IERC1155(prize[i].token);
                        ct.safeTransferFrom(from, to, prize[i].tokenId, prize[i].quantity, new bytes(0));
                    } else {
                        revert Errors.InvalidTokenType();
                    }
                    unchecked {
                        ++i;
                    }
                }
            }
            function onERC721Received(
                address, // operator,
                address from,
                uint256, // tokenId,
                bytes calldata // data
            ) public view returns (bytes4 selector) {
                // for safety, only allow transfer of ERC721 tokens from the banker
                if (banker() == from) {
                    selector = IERC721Receiver.onERC721Received.selector;
                }
            }
            function onERC1155Received(
                address, // operator,
                address from,
                uint256, // id,
                uint256, // value,
                bytes calldata // data
            ) public view returns (bytes4 selector) {
                // for safety, only allow transfer of ERC1155 tokens from the banker
                if (banker() == from) {
                    selector = IERC1155Receiver.onERC1155Received.selector;
                }
            }
            function onERC1155BatchReceived(
                address, // operator,
                address from,
                uint256[] calldata, // ids,
                uint256[] calldata, // values,
                bytes calldata // data
            ) public view returns (bytes4 selector) {
                // for safety, only allow transfer of ERC1155 tokens from the banker
                if (banker() == from) {
                    selector = IERC1155Receiver.onERC1155BatchReceived.selector;
                }
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC1155/IERC1155.sol)
        pragma solidity ^0.8.0;
        import "./IERC165.sol";
        /**
         * @dev Required interface of an ERC1155 compliant contract, as defined in the
         * https://eips.ethereum.org/EIPS/eip-1155[EIP].
         *
         * _Available since v3.1._
         */
        interface IERC1155 is IERC165 {
            /**
             * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
             */
            event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
            /**
             * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
             * transfers.
             */
            event TransferBatch(
                address indexed operator,
                address indexed from,
                address indexed to,
                uint256[] ids,
                uint256[] values
            );
            /**
             * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
             * `approved`.
             */
            event ApprovalForAll(address indexed account, address indexed operator, bool approved);
            /**
             * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
             *
             * If an {URI} event was emitted for `id`, the standard
             * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
             * returned by {IERC1155MetadataURI-uri}.
             */
            event URI(string value, uint256 indexed id);
            /**
             * @dev Returns the amount of tokens of token type `id` owned by `account`.
             *
             * Requirements:
             *
             * - `account` cannot be the zero address.
             */
            function balanceOf(address account, uint256 id) external view returns (uint256);
            /**
             * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
             *
             * Requirements:
             *
             * - `accounts` and `ids` must have the same length.
             */
            function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
                external
                view
                returns (uint256[] memory);
            /**
             * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
             *
             * Emits an {ApprovalForAll} event.
             *
             * Requirements:
             *
             * - `operator` cannot be the caller.
             */
            function setApprovalForAll(address operator, bool approved) external;
            /**
             * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
             *
             * See {setApprovalForAll}.
             */
            function isApprovedForAll(address account, address operator) external view returns (bool);
            /**
             * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
             *
             * Emits a {TransferSingle} event.
             *
             * Requirements:
             *
             * - `to` cannot be the zero address.
             * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
             * - `from` must have a balance of tokens of type `id` of at least `amount`.
             * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
             * acceptance magic value.
             */
            function safeTransferFrom(
                address from,
                address to,
                uint256 id,
                uint256 amount,
                bytes calldata data
            ) external;
            /**
             * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
             *
             * Emits a {TransferBatch} event.
             *
             * Requirements:
             *
             * - `ids` and `amounts` must have the same length.
             * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
             * acceptance magic value.
             */
            function safeBatchTransferFrom(
                address from,
                address to,
                uint256[] calldata ids,
                uint256[] calldata amounts,
                bytes calldata data
            ) external;
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)
        pragma solidity ^0.8.0;
        import "./IERC165.sol";
        /**
         * @dev _Available since v3.1._
         */
        interface IERC1155Receiver is IERC165 {
            /**
             * @dev Handles the receipt of a single ERC1155 token type. This function is
             * called at the end of a `safeTransferFrom` after the balance has been updated.
             *
             * NOTE: To accept the transfer, this must return
             * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
             * (i.e. 0xf23a6e61, or its own function selector).
             *
             * @param operator The address which initiated the transfer (i.e. msg.sender)
             * @param from The address which previously owned the token
             * @param id The ID of the token being transferred
             * @param value The amount of tokens being transferred
             * @param data Additional data with no specified format
             * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
             */
            function onERC1155Received(
                address operator,
                address from,
                uint256 id,
                uint256 value,
                bytes calldata data
            ) external returns (bytes4);
            /**
             * @dev Handles the receipt of a multiple ERC1155 token types. This function
             * is called at the end of a `safeBatchTransferFrom` after the balances have
             * been updated.
             *
             * NOTE: To accept the transfer(s), this must return
             * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
             * (i.e. 0xbc197c81, or its own function selector).
             *
             * @param operator The address which initiated the batch transfer (i.e. msg.sender)
             * @param from The address which previously owned the token
             * @param ids An array containing ids of each token being transferred (order and length must match values array)
             * @param values An array containing amounts of each token being transferred (order and length must match ids array)
             * @param data Additional data with no specified format
             * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
             * if transfer is allowed
             */
            function onERC1155BatchReceived(
                address operator,
                address from,
                uint256[] calldata ids,
                uint256[] calldata values,
                bytes calldata data
            ) external returns (bytes4);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
        pragma solidity ^0.8.0;
        /**
         * @dev Interface of the ERC165 standard, as defined in the
         * https://eips.ethereum.org/EIPS/eip-165[EIP].
         *
         * Implementers can declare support of contract interfaces, which can then be
         * queried by others ({ERC165Checker}).
         *
         * For an implementation, see {ERC165}.
         */
        interface IERC165 {
            /**
             * @dev Returns true if this contract implements the interface defined by
             * `interfaceId`. See the corresponding
             * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
             * to learn more about how these ids are created.
             *
             * This function call must use less than 30 000 gas.
             */
            function supportsInterface(bytes4 interfaceId) external view returns (bool);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
        pragma solidity ^0.8.0;
        /**
         * @dev Interface of the ERC20 standard as defined in the EIP.
         */
        interface IERC20 {
            /**
             * @dev Emitted when `value` tokens are moved from one account (`from`) to
             * another (`to`).
             *
             * Note that `value` may be zero.
             */
            event Transfer(address indexed from, address indexed to, uint256 value);
            /**
             * @dev Emitted when the allowance of a `spender` for an `owner` is set by
             * a call to {approve}. `value` is the new allowance.
             */
            event Approval(address indexed owner, address indexed spender, uint256 value);
            /**
             * @dev Returns the amount of tokens in existence.
             */
            function totalSupply() external view returns (uint256);
            /**
             * @dev Returns the amount of tokens owned by `account`.
             */
            function balanceOf(address account) external view returns (uint256);
            /**
             * @dev Moves `amount` tokens from the caller's account to `to`.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * Emits a {Transfer} event.
             */
            function transfer(address to, uint256 amount) external returns (bool);
            /**
             * @dev Returns the remaining number of tokens that `spender` will be
             * allowed to spend on behalf of `owner` through {transferFrom}. This is
             * zero by default.
             *
             * This value changes when {approve} or {transferFrom} are called.
             */
            function allowance(address owner, address spender) external view returns (uint256);
            /**
             * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * IMPORTANT: Beware that changing an allowance with this method brings the risk
             * that someone may use both the old and the new allowance by unfortunate
             * transaction ordering. One possible solution to mitigate this race
             * condition is to first reduce the spender's allowance to 0 and set the
             * desired value afterwards:
             * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
             *
             * Emits an {Approval} event.
             */
            function approve(address spender, uint256 amount) external returns (bool);
            /**
             * @dev Moves `amount` tokens from `from` to `to` using the
             * allowance mechanism. `amount` is then deducted from the caller's
             * allowance.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * Emits a {Transfer} event.
             */
            function transferFrom(
                address from,
                address to,
                uint256 amount
            ) external returns (bool);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)
        pragma solidity ^0.8.0;
        import "./IERC165.sol";
        /**
         * @dev Required interface of an ERC721 compliant contract.
         */
        interface IERC721 is IERC165 {
            /**
             * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
             */
            event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
            /**
             * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
             */
            event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
            /**
             * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
             */
            event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
            /**
             * @dev Returns the number of tokens in ``owner``'s account.
             */
            function balanceOf(address owner) external view returns (uint256 balance);
            /**
             * @dev Returns the owner of the `tokenId` token.
             *
             * Requirements:
             *
             * - `tokenId` must exist.
             */
            function ownerOf(uint256 tokenId) external view returns (address owner);
            /**
             * @dev Safely transfers `tokenId` token from `from` to `to`.
             *
             * Requirements:
             *
             * - `from` cannot be the zero address.
             * - `to` cannot be the zero address.
             * - `tokenId` token must exist and be owned by `from`.
             * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
             * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received},
             * which is called upon a safe transfer.
             *
             * Emits a {Transfer} event.
             */
            function safeTransferFrom(
                address from,
                address to,
                uint256 tokenId,
                bytes calldata data
            ) external;
            /**
             * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
             * are aware of the ERC721 protocol to prevent tokens from being forever locked.
             *
             * Requirements:
             *
             * - `from` cannot be the zero address.
             * - `to` cannot be the zero address.
             * - `tokenId` token must exist and be owned by `from`.
             * - If the caller is not `from`, it must have been allowed to move this token by either
             * {approve} or {setApprovalForAll}.
             * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received},
             * which is called upon a safe transfer.
             *
             * Emits a {Transfer} event.
             */
            function safeTransferFrom(
                address from,
                address to,
                uint256 tokenId
            ) external;
            /**
             * @dev Transfers `tokenId` token from `from` to `to`.
             *
             * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
             * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
             * understand this adds an external call which potentially creates a reentrancy vulnerability.
             *
             * Requirements:
             *
             * - `from` cannot be the zero address.
             * - `to` cannot be the zero address.
             * - `tokenId` token must be owned by `from`.
             * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
             *
             * Emits a {Transfer} event.
             */
            function transferFrom(
                address from,
                address to,
                uint256 tokenId
            ) external;
            /**
             * @dev Gives permission to `to` to transfer `tokenId` token to another account.
             * The approval is cleared when the token is transferred.
             *
             * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
             *
             * Requirements:
             *
             * - The caller must own the token or be an approved operator.
             * - `tokenId` must exist.
             *
             * Emits an {Approval} event.
             */
            function approve(address to, uint256 tokenId) external;
            /**
             * @dev Approve or remove `operator` as an operator for the caller.
             * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
             *
             * Requirements:
             *
             * - The `operator` cannot be the caller.
             *
             * Emits an {ApprovalForAll} event.
             */
            function setApprovalForAll(address operator, bool _approved) external;
            /**
             * @dev Returns the account approved for `tokenId` token.
             *
             * Requirements:
             *
             * - `tokenId` must exist.
             */
            function getApproved(uint256 tokenId) external view returns (address operator);
            /**
             * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
             *
             * See {setApprovalForAll}
             */
            function isApprovedForAll(address owner, address operator) external view returns (bool);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
        pragma solidity ^0.8.0;
        /**
         * @title ERC721 token receiver interface
         * @dev Interface for any contract that wants to support safeTransfers
         * from ERC721 asset contracts.
         */
        interface IERC721Receiver {
            /**
             * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
             * by `operator` from `from`, this function is called.
             *
             * It must return its Solidity selector to confirm the token transfer.
             * If any other value is returned or the interface is not implemented by the recipient, the transfer will
             * be reverted.
             *
             * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
             */
            function onERC721Received(
                address operator,
                address from,
                uint256 tokenId,
                bytes calldata data
            ) external returns (bytes4);
        }
        // SPDX-License-Identifier: UNLICENSED
        pragma solidity ^0.8.9;
        interface IEscrow {
            event Withdrawal(uint256 indexed amount, address indexed withdrawer);
            event Deposit(uint256 indexed amount, address indexed depositer);
            event ClaimAuthorized(uint256 indexed claimId, address indexed claimant);
            event PrizeAdded(uint256 indexed claimId);
            event PrizeRemoved(uint256 indexed claimId, address indexed recipient);
            event PrizeReceived(uint256 indexed claimId, address indexed recipient);
            function currencyBalance() external returns (uint256);
            function deposit(address spender, uint256 amount) external;
            function withdraw(address recipient, uint256 amount) external;
            function authorizeClaim(uint256 claimId, address claimant) external;
            function claimFor(address claimant, uint256 claimId, address recipient) external;
        }
        // SPDX-License-Identifier: UNLICENSED
        pragma solidity >=0.8.4 <0.9.0;
        library Errors {
            error LinkError();
            error ArrayMismatch();
            error OutOfRange(uint256 value);
            error OutOfRangeSigned(int256 value);
            error UnsignedOverflow(uint256 value);
            error SignedOverflow(int256 value);
            error DuplicateCall();
            error NotAContract();
            error InterfaceNotSupported();
            error NotInitialized();
            error AlreadyInitialized();
            error BadSender(address expected, address caller);
            error AddressTarget(address target);
            error UserPermissions();
            error InvalidHash();
            error InvalidSignature();
            error InvalidSignatureLength();
            error InvalidSignatureS();
            error InsufficientBalance(uint256 available, uint256 required);
            error InsufficientSupply(uint256 supply, uint256 available, int256 requested);  // 0x5437b336
            error InsufficientAvailable(uint256 available, uint256 requested);
            error InvalidToken(uint256 tokenId);                                            // 0x925d6b18
            error TokenNotMintable(uint256 tokenId);
            error InvalidTokenType();
            error ERC1155Receiver();
            error ContractPaused();
            error PaymentFailed(uint256 amount);
            error IncorrectPayment(uint256 required, uint256 provided);                     // 0x0d35e921
        \terror TooManyForTransaction(uint256 mintLimit, uint256 amount);
            error AuctionInactive(uint256 auctionId);
            error AuctionActive(uint256 auctionId);
            error InvalidBid(uint256 auctionId, uint256 amount);
            error BidTooLow(uint256 auctionId, uint256 bid, uint256 minBid);
            error AuctionClosed(uint256 auctionId);
            error AuctionInExtendedBidding(uint256 auctionId);
            error AuctionAborted(uint256 auctionId);
            error AlreadyClaimed(uint256 lotId);
        }
        // SPDX-License-Identifier: MIT
        pragma solidity >=0.8.0 <0.9.0;
        import { Initializable } from "./Initializable.sol";
        /**
         * @dev Provides information about the current execution context, including the
         * sender of the transaction and its data. While these are generally available
         * via msg.sender and msg.data, they should not be accessed in such a direct
         * manner, since when dealing with meta-transactions the account sending and
         * paying for execution may not be the actual sender (as far as an application
         * is concerned).
         *
         * This contract is only required for intermediate, library-like contracts.
         */
        abstract contract Context is Initializable {
            function __Context_init() internal initializer {
                __Context_init_unchained();
            }
            function __Context_init_unchained() internal initializer {
            }
            function _msgSender() internal view virtual returns (address) {
                return msg.sender;
            }
            function _msgData() internal view virtual returns (bytes calldata) {
                return msg.data;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity >=0.8.4 <0.9.0;
        import { Errors } from "../library/errors/Errors.sol";
        /**
         * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
         * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an
         * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
         * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
         *
         * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
         * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
         *
         * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
         * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
         */
        abstract contract Initializable {
            /**
             * @dev Indicates that the contract has been initialized.
             */
            bool private _initialized;
            /**
             * @dev Indicates that the contract is in the process of being initialized.
             */
            bool private _initializing;
            /**
             * @dev Modifier to protect an initializer function from being invoked twice.
             */
            modifier initializer() {
                // require(_initializing || !_initialized, "Initializable: contract is already initialized");
                if (!_initializing && _initialized) revert Errors.AlreadyInitialized();
                bool isTopLevelCall = !_initializing;
                if (isTopLevelCall) {
                    _initializing = true;
                    _initialized = true;
                }
                _;
                if (isTopLevelCall) {
                    _initializing = false;
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity >=0.8.4 <0.9.0;
        import { Errors } from "../library/errors/Errors.sol";
        /**
         * @dev Contract module which provides a basic access control mechanism, where
         * there are two accounts (an owner and a proxy) that can be granted exclusive
         * access to specific functions. Only the owner can set the proxy.
         *
         * By default, the owner account will be the one that deploys the contract. This
         * can later be changed with {transferOwnership}.
         *
         * This module is used through inheritance. It will make available the modifier
         * `onlyOwner`, which can be applied to your functions to restrict their use to
         * the owner.
         */
        abstract contract EscrowOwnable {
            address private _owner;
            address private _proxy;
            address private _banker;
            event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
            /**
             * @dev Initializes the contract setting the deployer as the initial owner.
             */
            constructor() {
                _setOwner(msg.sender);
            }
            /**
             * @dev Returns the address of the current owner.
             */
            function owner() public view virtual returns (address) {
                return _owner;
            }
            /**
             * @dev Returns the address of the current proxy.
             */
            function proxy() public view virtual returns (address) {
                return _proxy;
            }
            /**
             * @dev Returns the address of the current proxy.
             */
            function banker() public view virtual returns (address) {
                return _banker;
            }
            /**
             * @dev Throws if called by any account other than the owner.
             */
            modifier onlyOwner() {
                if (owner() != msg.sender) revert Errors.UserPermissions();
                _;
            }
            /**
             * @dev Throws if called by any account other than the proxy or the owner.
             */
            modifier onlyAuthorized() {
                if (
                    proxy() != msg.sender &&
                    banker() != msg.sender &&
                    owner() != msg.sender
                ) revert Errors.UserPermissions();
                _;
            }
            /**
             * @dev Transfers ownership of the contract to a new account (`newOwner`).
             * Can only be called by the current owner.
             */
            function transferOwnership(address newOwner) public virtual onlyOwner {
                if (newOwner == address(0)) revert Errors.AddressTarget(newOwner);
                _setOwner(newOwner);
            }
            /**
             * @dev Sets the proxy for the contract to a new account (`newProxy`).
             * Can only be called by the current owner.
             */
            function setProxy(address newProxy) public virtual onlyOwner {
                _proxy = newProxy;
            }
            /**
             * @dev Sets the proxy for the contract to a new account (`newProxy`).
             * Can only be called by the current owner.
             */
            function setBanker(address newBanker) public virtual onlyOwner {
                _banker = newBanker;
            }
            function _setOwner(address newOwner) internal {
                address oldOwner = _owner;
                _owner = newOwner;
                emit OwnershipTransferred(oldOwner, newOwner);
            }
        }
        

        File 3 of 3: BPX
        // SPDX-License-Identifier: UNLICENSED
        pragma solidity >=0.8.0 <0.9.0;
        import "solmate/src/tokens/ERC20.sol";
        contract BPX is ERC20 {
        \t/**
        \t * string name name of token
        \t * string symbol token symbol shown on etherscan/uniswap/etc
        \t * address banker address to receive all initial supply
        \t * uint totalSupply total number of tokens to create (ignoring decimals -
        \t * those are calculated and total amount is math'd during construction)
        \t */
        \tconstructor(
        \t\tstring memory name_,
        \t\tstring memory symbol_,
        \t\taddress banker_,
        \t\tuint256 totalSupply_,
        \t\tuint8 decimals_
        \t) ERC20(name_, symbol_, decimals_) {
        \t\t_mint(banker_, totalSupply_ * (10 ** decimals_));
        \t}
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
        /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
        /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
        abstract contract ERC20 {
            /*//////////////////////////////////////////////////////////////
                                         EVENTS
            //////////////////////////////////////////////////////////////*/
            event Transfer(address indexed from, address indexed to, uint256 amount);
            event Approval(address indexed owner, address indexed spender, uint256 amount);
            /*//////////////////////////////////////////////////////////////
                                    METADATA STORAGE
            //////////////////////////////////////////////////////////////*/
            string public name;
            string public symbol;
            uint8 public immutable decimals;
            /*//////////////////////////////////////////////////////////////
                                      ERC20 STORAGE
            //////////////////////////////////////////////////////////////*/
            uint256 public totalSupply;
            mapping(address => uint256) public balanceOf;
            mapping(address => mapping(address => uint256)) public allowance;
            /*//////////////////////////////////////////////////////////////
                                    EIP-2612 STORAGE
            //////////////////////////////////////////////////////////////*/
            uint256 internal immutable INITIAL_CHAIN_ID;
            bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
            mapping(address => uint256) public nonces;
            /*//////////////////////////////////////////////////////////////
                                       CONSTRUCTOR
            //////////////////////////////////////////////////////////////*/
            constructor(
                string memory _name,
                string memory _symbol,
                uint8 _decimals
            ) {
                name = _name;
                symbol = _symbol;
                decimals = _decimals;
                INITIAL_CHAIN_ID = block.chainid;
                INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
            }
            /*//////////////////////////////////////////////////////////////
                                       ERC20 LOGIC
            //////////////////////////////////////////////////////////////*/
            function approve(address spender, uint256 amount) public virtual returns (bool) {
                allowance[msg.sender][spender] = amount;
                emit Approval(msg.sender, spender, amount);
                return true;
            }
            function transfer(address to, uint256 amount) public virtual returns (bool) {
                balanceOf[msg.sender] -= amount;
                // Cannot overflow because the sum of all user
                // balances can't exceed the max uint256 value.
                unchecked {
                    balanceOf[to] += amount;
                }
                emit Transfer(msg.sender, to, amount);
                return true;
            }
            function transferFrom(
                address from,
                address to,
                uint256 amount
            ) public virtual returns (bool) {
                uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
                if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
                balanceOf[from] -= amount;
                // Cannot overflow because the sum of all user
                // balances can't exceed the max uint256 value.
                unchecked {
                    balanceOf[to] += amount;
                }
                emit Transfer(from, to, amount);
                return true;
            }
            /*//////////////////////////////////////////////////////////////
                                     EIP-2612 LOGIC
            //////////////////////////////////////////////////////////////*/
            function permit(
                address owner,
                address spender,
                uint256 value,
                uint256 deadline,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) public virtual {
                require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
                // Unchecked because the only math done is incrementing
                // the owner's nonce which cannot realistically overflow.
                unchecked {
                    address recoveredAddress = ecrecover(
                        keccak256(
                            abi.encodePacked(
                                "\\x19\\x01",
                                DOMAIN_SEPARATOR(),
                                keccak256(
                                    abi.encode(
                                        keccak256(
                                            "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                        ),
                                        owner,
                                        spender,
                                        value,
                                        nonces[owner]++,
                                        deadline
                                    )
                                )
                            )
                        ),
                        v,
                        r,
                        s
                    );
                    require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
                    allowance[recoveredAddress][spender] = value;
                }
                emit Approval(owner, spender, value);
            }
            function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
                return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
            }
            function computeDomainSeparator() internal view virtual returns (bytes32) {
                return
                    keccak256(
                        abi.encode(
                            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                            keccak256(bytes(name)),
                            keccak256("1"),
                            block.chainid,
                            address(this)
                        )
                    );
            }
            /*//////////////////////////////////////////////////////////////
                                INTERNAL MINT/BURN LOGIC
            //////////////////////////////////////////////////////////////*/
            function _mint(address to, uint256 amount) internal virtual {
                totalSupply += amount;
                // Cannot overflow because the sum of all user
                // balances can't exceed the max uint256 value.
                unchecked {
                    balanceOf[to] += amount;
                }
                emit Transfer(address(0), to, amount);
            }
            function _burn(address from, uint256 amount) internal virtual {
                balanceOf[from] -= amount;
                // Cannot underflow because a user's balance
                // will never be larger than the total supply.
                unchecked {
                    totalSupply -= amount;
                }
                emit Transfer(from, address(0), amount);
            }
        }