creation_sourcecode: ////////////////////////////////////////////////////
  //                                                //
  //     ____                        _______  __    //
  //    |  _ \ _   _ _ __ ___  _ __ | ____\ \/ /    //
  //    | | | | | | | '_ ` _ \| '_ \|  _|  \  /     //
  //    | |_| | |_| | | | | | | |_) | |___ /  \     //
  //    |____/ \__,_|_| |_| |_| .__/|_____/_/\_\    //
  //                          |_|                   //
  //                                                //
  //                                                //
  //                FLOOR GUARANTEED                //
  //                                                //
  ////////////////////////////////////////////////////
  
  // File: @openzeppelin/contracts/token/ERC20/IERC20.sol
  
  
  // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
  
  pragma solidity ^0.8.20;
  
  /**
   * @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 value of tokens in existence.
       */
      function totalSupply() external view returns (uint256);
  
      /**
       * @dev Returns the value of tokens owned by `account`.
       */
      function balanceOf(address account) external view returns (uint256);
  
      /**
       * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);
  
      /**
       * @dev Moves a `value` amount of tokens from `from` to `to` using the
       * allowance mechanism. `value` 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 value) external returns (bool);
  }
  
  // File: @openzeppelin/contracts/utils/introspection/IERC165.sol
  
  
  // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
  
  pragma solidity ^0.8.20;
  
  /**
   * @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);
  }
  
  // File: @openzeppelin/contracts/token/ERC721/IERC721.sol
  
  
  // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol)
  
  pragma solidity ^0.8.20;
  
  
  /**
   * @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 address zero.
       *
       * 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);
  }
  
  // File: contracts/DumpEX.sol
  
  
  pragma solidity ^0.8.24;
  
  
  
  error NotEnoughEther();
  error Payable(uint weiToPay);
  error OnlyAdmin();
  
  /// @title Dump Exchange v1
  /// @author Juuso Roinevirta
  /// @notice Use this contract to sell NFTs & tokens at a fixed price & to buy them in a Dutch auction
  /// @custom:experimental This is an experimental contract.
  contract DumpEX {
  
      event TokenSold(address indexed seller, address tokenAddress, uint256 amount, uint256 price);   // price = total price
      event TokenBought(address indexed buyer, address tokenAddress, uint256 amount, uint256 price);  // price = total price
      event NftSold(address indexed seller, address nftAddress, uint256 tokenId, uint256 price);      // price = total price
      event NftBought(address indexed buyer, address nftAddress, uint256 tokenId, uint256 price);     // price = total price
  
      // Please note that block numbers are used as the timestamping mechanism
      struct Deal { 
          uint256 timestamp;
          uint256 price;  // total deal value
      }
      struct DealWithAmount { 
          uint256 timestamp;
          uint256 price;  // total deal value
          uint256 amount;
      }
  
      mapping(address => Deal) private lastSaleNfts;                  // Timestamp + price at which NFT was last sold to contract
      mapping(address => Deal) private lastPurchaseNfts;              // Timestamp + price at which NFT was last bought from contract
      mapping(address => Deal) private lastSaleTokens;                // Timestamp + price at which token was last sold to contract
      mapping(address => DealWithAmount) private lastPurchaseTokens;  // Timestamp + price + amount at which token was last bought from contract
      
      address public admin = 0xb55d5B59f121E497b2833d658a8093B38d3A7c64;  // Hard coded initial admin wallet
      address public pendingAdmin;
  
      //////////////////////
      // HELPER FUNCTIONS //
      //////////////////////
  
      /// @notice choose max
      function max(uint256 a, uint256 b) private pure returns (uint256) {
          return a >= b ? a : b;
      }
  
      /// @notice calculates the protocol fee for a given trade - larger of protocolFeePercentage or protocolFeeMinimimum
      /// @param payableAmount the amount that is to be paid for the asset(s)
      function _getProtocolFee(uint256 payableAmount) private pure returns (uint256) {
          uint256 protocolFeeMinimum = 100000000000000; // wei --> 0.0001 ETH
          uint256 protocolFeePercentage = 3;
          if (payableAmount * protocolFeePercentage > protocolFeeMinimum * 100) {     // percentage amount is higher
              return payableAmount * protocolFeePercentage / 100;
          } else {    // flat minimum fee is higher
              return protocolFeeMinimum;
          }
      }
  
      /// @notice calculates the current value based on a certain input values
      /// @param initialValue the initial value that is adjusted for decay
      /// @param n the number of periods 
      /// @param decayRate how many wei are deducted per period
      function _calculateDecay(uint256 initialValue, uint256 n, uint decayRate) private pure returns (uint256) {
          if (n * decayRate > initialValue) {
              return 1;
          } else {
              return (initialValue - (n * decayRate));
          }
      }
  
      /// @notice Internal method that returns the price for which a given NFT can be bought from inventory
      /// @param nftAddress address of the nft collection
      function _getNftBuyPrice(address nftAddress) private view returns(uint) {
          if (lastPurchaseNfts[nftAddress].timestamp == 0) { // NFT has never been bought
              uint blocksSinceLastAction = block.number - lastSaleNfts[nftAddress].timestamp;
              return _calculateDecay(1500 * 10e17, blocksSinceLastAction, 10**15);  // Use starting price of 1500 ETH, decays 0.001ETH per block
          } else {  // NFT has been bought before
              uint blocksSinceLastAction = block.number - lastPurchaseNfts[nftAddress].timestamp;
              uint decayedPrice = _calculateDecay(lastPurchaseNfts[nftAddress].price + 10e17, blocksSinceLastAction, 10**15);  // Add 1 ETH to price, decays 0.001ETH per block
              return max(1, decayedPrice);
          } 
      }
  
      /// @notice Internal method that returns the price the contract is willing to pay for a given NFT
      /// @param nftAddress address of the nft collection
      function _getNftSellPrice(address nftAddress) private view returns(uint) {
          if (lastPurchaseNfts[nftAddress].timestamp > lastSaleNfts[nftAddress].timestamp) {  // Ensure that there's been a more recent purchase from the contract than a sale
              return lastPurchaseNfts[nftAddress].price; // We pay the price that was previously paid
          } else {    // If there have been no recent purchases
              return 1;    // We pay the fixed floor of 1 wei
          }
      }
  
      /// @notice Internal method that returns the total price for which a given number of token can be bought from inventory
      /// @param tokenAddress address of the token
      /// @param amount the amount of tokens that one wants to buy
      function _getTokenBuyPrice(address tokenAddress, uint256 amount) private view returns(uint) {
          if (lastPurchaseTokens[tokenAddress].timestamp == 0) {   // Tokens have never been bought
              uint blocksSinceLastAction = block.number - lastSaleTokens[tokenAddress].timestamp;
              return _calculateDecay(15 * amount / 10, blocksSinceLastAction, 10e11);  // Use starting price of 1.5 ETH, decays 0.000001ETH per block
          } else {   // Tokens have been bought at some point
              uint blocksSinceLastAction = block.number - lastPurchaseTokens[tokenAddress].timestamp;
              uint grossPrice = lastPurchaseTokens[tokenAddress].price * amount / lastPurchaseTokens[tokenAddress].amount;   // Use prev. buy price to determine price for this lot
              uint priceIncrease = amount / 10e2;    // Add 0.0001 ETH to price per token
              uint decayedPrice = _calculateDecay(grossPrice + priceIncrease, blocksSinceLastAction, 10e11);  // decays 0.000001ETH per block
              return max(1, decayedPrice);    // Never sell for less than 1 wei
          } 
      }
  
      /// @notice Internal method that returns the price the contract is willing to pay for a given token
      /// @param tokenAddress address of the token
      /// @param amount the amount of tokens that one wants to sell
      function _getTokenSellPrice(address tokenAddress, uint256 amount) private view returns(uint) {
          if (lastPurchaseTokens[tokenAddress].timestamp > lastSaleTokens[tokenAddress].timestamp) {  // Ensure that there's been a more recent purchase from the contract than a sale
              if (lastPurchaseTokens[tokenAddress].amount >= amount) {    // We can pay the same price as the previous buyer paid if the amount of tokens are same or less than previously
                  return lastPurchaseTokens[tokenAddress].price * amount / lastPurchaseTokens[tokenAddress].amount;  // We adjust such that per token value is same
              } else {    // If the seller is selling more, then we won't pay more to prevent draining
                  return lastPurchaseTokens[tokenAddress].price; // We pay the price that was previously paid for less tokens
              }
          } else {    // If there have been no recent purchases
              return 1;    // We pay the fixed floor of 1 wei
          }
      }
  
      ////////////////////
      // USER FUNCTIONS //
      ////////////////////
  
      /// @notice Sell an NFT
      /// @dev There must be an existing approval and some wei in the contract. Payment is sent to caller.
      /// @param nftAddress the address of the NFT you are selling
      /// @param tokenId the ID of the NFT you are selling
      function sellNft(address nftAddress, uint256 tokenId) external {
          IERC721 nft = IERC721(nftAddress);
  
          // requirements
          if (nft.getApproved(tokenId) != address(this)) { revert ("Not approved"); }
  
          // transfer the NFT to the pool
          nft.transferFrom(msg.sender, address(this), tokenId);
  
          // get price
          uint price = _getNftSellPrice(nftAddress);
          if (address(this).balance < price) { revert NotEnoughEther(); }
  
          // transfer the payment
          (bool success, ) = payable(msg.sender).call{value: price}("");
          require(success, "Transfer failed.");
  
          // update last NFT sale data
          lastSaleNfts[nftAddress] = Deal(block.number, price);
  
          emit NftSold(msg.sender, nftAddress, tokenId, price);
      }
  
      /// @notice Sell a collection of NFTs
      /// @dev There must be an existing approval and some wei in the contract. Payment is sent to caller.
      /// @param nftAddress the address of the NFT you are selling
      /// @param tokenIds the IDs of the NFTs you are selling
      function multisellNft(address nftAddress, uint256[] calldata tokenIds) external {
          IERC721 nft = IERC721(nftAddress);
  
          // get current price of the NFT
          uint itemsToSell = tokenIds.length;
          uint price = _getNftSellPrice(nftAddress);
  
          // requirements
          if (address(this).balance < price) { revert NotEnoughEther(); }
  
          // transfer the NFT to the pool
          for (uint256 i = 0; i < itemsToSell; ++i) {
              if (nft.getApproved(tokenIds[i]) != address(this)) { revert ("Not approved"); }
              nft.transferFrom(msg.sender, address(this), tokenIds[i]);
              emit NftSold(msg.sender, nftAddress, tokenIds[i], price);
          }
  
          // transfer the payment
          (bool success, ) = payable(msg.sender).call{value: price}("");
          require(success, "Transfer failed.");
  
          // update last NFT sale data
          lastSaleNfts[nftAddress] = Deal(block.number, price);
      }
  
      /// @notice Buy an NFT at a price determined by a Dutch auction
      /// @param nftAddress the address of the NFT you are buying
      /// @param tokenId the ID of the NFT you are buying
      function buyNft(address nftAddress, uint256 tokenId) external payable {
          IERC721 nft = IERC721(nftAddress);
  
          // get current price of the NFT
          uint price = _getNftBuyPrice(nftAddress);
          uint protocolFee = _getProtocolFee(price);
  
          // requirements
          if (msg.value < price + protocolFee) { revert Payable(price + protocolFee); }
  
          // transfer NFT to buyer
          nft.safeTransferFrom(address(this), msg.sender, tokenId);
  
          // transfer protocol fee to current admin
          payable(admin).transfer(protocolFee);
  
          // update NFT purchase data
          lastPurchaseNfts[nftAddress] = Deal(block.number, price);
  
          emit NftBought(msg.sender, nftAddress, tokenId, price);
      }
  
      /// @notice Buy multiple NFTs from a single collection without resetting the price ("sweep inventory")
      /// @param nftAddress the address of the NFT collection you are buying
      /// @param tokenIds the IDs of the NFT you are buying
      function multibuyNft(address nftAddress, uint256[] calldata tokenIds) external payable {
          IERC721 nft = IERC721(nftAddress);
  
          // get current price of the NFT
          uint itemsToBuy = tokenIds.length;
          uint price = _getNftBuyPrice(nftAddress) * itemsToBuy;
          uint protocolFee = _getProtocolFee(price);
  
          // requirements
          if (msg.value < price + protocolFee) { revert Payable(price + protocolFee); }
  
          // transfer NFTs to buyer
          for (uint256 i = 0; i < itemsToBuy; ++i) {
              nft.safeTransferFrom(address(this), msg.sender, tokenIds[i]);
              emit NftBought(msg.sender, nftAddress, tokenIds[i], price);
          }
  
          // transfer protocol fee to current admin
          payable(admin).transfer(protocolFee);
  
          // update NFT purchase data
          lastPurchaseNfts[nftAddress] = Deal(block.number, price);
      }
  
      /// @notice Get the price to pay for a given NFT
      /// @param nftAddress the address of the NFT collection
      function getNftBuyPrice(address nftAddress) external view returns(uint) {
          return _getNftBuyPrice(nftAddress);
      }
  
      /// @notice Returns the total fee the protocol charges for a purchase of a certain size
      /// @param payableAmount the value of the purchase prior to fees
      function getProtocolFee(uint256 payableAmount) external pure returns (uint256) {
          return _getProtocolFee(payableAmount);
      }
  
      /// @notice Get the price to pay for a given NFT
      /// @param nftAddress the address of the NFT collection
      function getNftNetBuyPrice(address nftAddress) external view returns(uint) {
          uint grossPrice = _getNftBuyPrice(nftAddress);
          return grossPrice + _getProtocolFee(grossPrice);
      }
  
      /// @notice Get the current price the contract pays for a given NFT
      /// @param nftAddress the address of the NFT collection
      function getNftSellPrice(address nftAddress) external view returns(uint) {
          return _getNftSellPrice(nftAddress);
      }
  
      /// @notice Sell token(s) for fixed price or best possible price
      /// @dev There must be an existing approval and some wei in the contract. Payment is sent to caller.
      /// @param tokenAddress address of the token you are selling
      /// @param amount number of tokens to sell
      function sellToken(address tokenAddress, uint256 amount) external {
          IERC20 token = IERC20(tokenAddress);
  
          // requirements
          if (token.allowance(msg.sender, address(this)) < amount) { revert ("Allowance is not sufficient"); }
  
          // transfer Tokens to the pool
          token.transferFrom(msg.sender, address(this), amount);
  
          // transfer Ether to the seller        
          uint sellPrice = _getTokenSellPrice(tokenAddress, amount);
          if (address(this).balance < sellPrice) { revert NotEnoughEther(); }
          payable(msg.sender).transfer(sellPrice);
  
          // update last Token sale data
          lastSaleTokens[tokenAddress] = Deal(block.number, sellPrice);
  
          emit TokenSold(msg.sender, tokenAddress, amount, sellPrice);
      }
  
      /// @notice Buy tokens at a price determined by a Dutch auction
      /// @param tokenAddress address of the token you are buying
      /// @param amount number of tokens to buy
      function buyToken(address tokenAddress, uint256 amount) external payable {
          IERC20 token = IERC20(tokenAddress);
  
          //get current price of Tokens
          uint buyPrice = _getTokenBuyPrice(tokenAddress, amount);
          uint protocolFee = _getProtocolFee(buyPrice);
          uint netPrice = buyPrice + protocolFee;
  
          // requirements
          if (msg.value < netPrice) { revert Payable(netPrice); }
  
          // transfer Tokens to the buyer
          token.transfer(msg.sender, amount);
  
          // transfer protocol fee to current admin
          payable(admin).transfer(protocolFee);
  
          // set new purchase price
          lastPurchaseTokens[tokenAddress] = DealWithAmount(block.number, buyPrice, amount);
  
          emit TokenBought(msg.sender, tokenAddress, amount, buyPrice);
      }
  
      /// @notice Get the price to pay for a given token
      /// @param tokenAddress the address of the token
      /// @param amount the amount of tokens to buy
      function getTokenBuyPrice(address tokenAddress, uint256 amount) external view returns(uint) {
          return _getTokenBuyPrice(tokenAddress, amount);
      }
  
      /// @notice Get the price to pay for a given token
      /// @param tokenAddress the address of the token
      /// @param amount the amount of tokens to buy
      function getTokenNetBuyPrice(address tokenAddress, uint256 amount) external view returns(uint) {
          uint grossPrice = _getTokenBuyPrice(tokenAddress, amount);
          return grossPrice + _getProtocolFee(grossPrice);
      }
  
      /// @notice Get the current price the contract pays for a given token
      /// @param tokenAddress the address of the token
      /// @param amount the amount of tokens to sell
      function getTokenSellPrice(address tokenAddress, uint256 amount) external view returns(uint) {
          return _getTokenSellPrice(tokenAddress, amount);
      }
  
      receive() external payable {}
  
      //////////////////////
      /// ADMIN FUNCTIONS //
      //////////////////////
  
      /// @notice Withdraw Ether from the contract to the admin
      /// @param amount wei
      function adminWithdrawEther(uint256 amount) external {
          if (amount > address(this).balance - 100) { revert ("Withdrawing too much"); } 
          payable(admin).transfer(amount);
      }
  
      /// @notice Only admin: set a new admin
      /// @param newAdmin address of the new admin
      function setAdmin(address newAdmin) external {
          if (msg.sender != admin) { revert OnlyAdmin(); }
          pendingAdmin = newAdmin;
      }
  
      /// @notice Pending admin only: accept new admin
      function acceptAdmin() external {
          if (msg.sender != pendingAdmin) { revert ("Not pending admin"); }
          admin = pendingAdmin;
      }
  }