Contract 0x7eb6d38935a334aba9a27d200b39a49a3a8e30dc

 

Contract Overview

Balance:
0 Ether

EtherValue:
$0.00

Token:

Txn Hash Method
Block
From
To
Value
Latest 6 internal transactions
Parent Txn Hash Block From To Value
0xfc3c320cb07f3b118d8d043b27453e5c54c0512ea8324aee431069231dcaced39479132023-01-23 17:30:2265 days 17 hrs ago 0x7eb6d38935a334aba9a27d200b39a49a3a8e30dc 0xb05c4cf6cf5989ca95273a2667515bd68ac8b0290 Ether
0xfc3c320cb07f3b118d8d043b27453e5c54c0512ea8324aee431069231dcaced39479132023-01-23 17:30:2265 days 17 hrs ago 0x7eb6d38935a334aba9a27d200b39a49a3a8e30dc 0xfbf1124c6de0a09f4be4d62c90d95fa7ab9eea2e0 Ether
0xfc3c320cb07f3b118d8d043b27453e5c54c0512ea8324aee431069231dcaced39479132023-01-23 17:30:2265 days 17 hrs ago 0xfbf1124c6de0a09f4be4d62c90d95fa7ab9eea2e 0x7eb6d38935a334aba9a27d200b39a49a3a8e30dc0 Ether
0xe89613dfbd26920e1399aaacbf794e7dff2a7de58ddaf520a25b5f62bf82a4d99479112023-01-23 17:30:0365 days 17 hrs ago 0x7eb6d38935a334aba9a27d200b39a49a3a8e30dc 0xfbf1124c6de0a09f4be4d62c90d95fa7ab9eea2e0 Ether
0xe89613dfbd26920e1399aaacbf794e7dff2a7de58ddaf520a25b5f62bf82a4d99479112023-01-23 17:30:0365 days 17 hrs ago 0x7eb6d38935a334aba9a27d200b39a49a3a8e30dc0x507e3722e4420071d7f05f6983596ed4a0afd0060 Ether
0xe89613dfbd26920e1399aaacbf794e7dff2a7de58ddaf520a25b5f62bf82a4d99479112023-01-23 17:30:0365 days 17 hrs ago 0x507e3722e4420071d7f05f6983596ed4a0afd006  Contract Creation0 Ether
[ Download CSV Export 
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
FnbswapExchange20

Compiler Version
v0.7.4+commit.3f05b770

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
File 1 of 18 : FnbswapExchange20.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.7.4;
pragma experimental ABIEncoderV2;
import "./swap/contracts/interfaces/IFnbswapExchange20.sol";
import "./swap/contracts/utils/ReentrancyGuard.sol";
import "./swap/contracts/utils/DelegatedOwnable.sol";
import "./swap/contracts/interfaces/IERC2981.sol";
import "./swap/contracts/interfaces/IERC1155Metadata.sol";
import "./swap/contracts/interfaces/IDelegatedERC1155Metadata.sol";
import "./erc-1155/contracts/interfaces/IERC20.sol";
import "./erc-1155/contracts/interfaces/IERC165.sol";
import "./erc-1155/contracts/interfaces/IERC1155.sol";
import "./erc-1155/contracts/interfaces/IERC1155TokenReceiver.sol";
import "./erc-1155/contracts/tokens/ERC1155/ERC1155MintBurn.sol";
import "./TransferHelper.sol";

/**
 * This Uniswap-like implementation supports ERC-1155 standard tokens
 * with an ERC-20 based token used as a currency instead of Ether.
 *
 * Liquidity tokens are also ERC-1155 tokens you can find the ERC-1155
 * implementation used here:
 *    https://github.com/horizon-games/multi-token-standard/tree/master/contracts/tokens/ERC1155
 *
 * @dev Like Uniswap, tokens with 0 decimals and low supply are susceptible to significant rounding
 *      errors when it comes to removing liquidity, possibly preventing them to be withdrawn without
 *      some collaboration between liquidity providers.
 * 
 * @dev ERC-777 tokens may be vulnerable if used as currency in Fnbswap. Please review the code 
 *      carefully before using it with ERC-777 tokens.
 */
contract FnbswapExchange20 is ReentrancyGuard, ERC1155MintBurn, IFnbswapExchange20, IERC1155Metadata, DelegatedOwnable {
  using SafeMath for uint256;

  /***********************************|
  |       Variables & Constants       |
  |__________________________________*/

  // Variables
  IERC1155 internal immutable token;         // address of the ERC-1155 token contract
  address internal immutable currency;       // address of the ERC-20 currency used for exchange
  address internal immutable factory;        // address for the factory that created this contract
  uint256 internal immutable FEE_MULTIPLIER; // multiplier that calculates the LP fee (1.0%)

  // Royalty variables
  bool internal immutable IS_ERC2981; // whether token contract supports ERC-2981
  uint256 internal globalRoyaltyFee;        // global royalty fee multiplier if ERC2981 is not used
  address internal globalRoyaltyRecipient;  // global royalty fee recipient if ERC2981 is not used

  // Mapping variables
  mapping(uint256 => uint256) internal totalSupplies;      // Liquidity pool token supply per Token id
  mapping(uint256 => uint256) internal currencyReserves;   // currency Token reserve per Token id
  mapping(address => uint256) internal royaltiesNumerator; // Mapping tracking how much royalties can be claimed per address

  uint256 internal constant ROYALTIES_DENOMINATOR = 10000;
  uint256 internal constant MAX_ROYALTY = ROYALTIES_DENOMINATOR / 4;

  bool public test = true;

  /***********************************|
  |            Constructor           |
  |__________________________________*/

  /**
   * @notice Create instance of exchange contract with respective token and currency token
   * @dev If token supports ERC-2981, then royalty fee will be queried per token on the 
   *      token contract. Else royalty fee will need to be manually set by admin.
   * @param _tokenAddr     The address of the ERC-1155 Token
   * @param _currencyAddr  The address of the ERC-20 currency Token
   * @param _currencyAddr  Address of the admin, which should be the same as the factory owner
   * @param _lpFee    Fee that will go to LPs.
   *                  Number between 0 and 1000, where 10 is 1.0% and 100 is 10%.
   */
  constructor(address _tokenAddr, address _currencyAddr, uint256 _lpFee) DelegatedOwnable(msg.sender) {
    require(
      _tokenAddr != address(0) && _currencyAddr != address(0),
      "NE20#1" // FnbswapExchange20#constructor:INVALID_INPUT
    );
    require(
      _lpFee >= 0 && _lpFee <= 1000,  
      "NE20#2" // FnbswapExchange20#constructor:INVALID_LP_FEE
    );

    factory = msg.sender;
    token = IERC1155(_tokenAddr);
    currency = _currencyAddr;
    FEE_MULTIPLIER = 1000 - _lpFee;

    // If global royalty, lets check for ERC-2981 support
    try IERC1155(_tokenAddr).supportsInterface(type(IERC2981).interfaceId) returns (bool supported) {
      IS_ERC2981 = supported;
    } catch {}
  }

  /***********************************|
  |        Metadata Functions         |
  |__________________________________*/

  /**
      @notice A distinct Uniform Resource Identifier (URI) for a given token.
      @dev URIs are defined in RFC 3986.
      The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema".        
      @return URI string
  */
  function uri(uint256 _id) external override view returns (string memory) {
    return IDelegatedERC1155Metadata(factory).metadataProvider().uri(_id);
  }

  /***********************************|
  |        Exchange Functions         |
  |__________________________________*/

  /**
   * @notice Convert currency tokens to Tokens _id and transfers Tokens to recipient.
   */
  function _currencyToToken(
    uint256[] memory _tokenIds,
    uint256[] memory _tokensBoughtAmounts,
    uint256 _maxCurrency,
    uint256 _deadline,
    address _recipient
  )
    internal nonReentrant() returns (uint256[] memory currencySold)
  {
    // Input validation
    require(_deadline >= block.timestamp, "NE20#3"); // FnbswapExchange20#_currencyToToken: DEADLINE_EXCEEDED

    // Number of Token IDs to deposit
    uint256 nTokens = _tokenIds.length;
    uint256 totalRefundCurrency = _maxCurrency;

    // Initialize variables
    currencySold = new uint256[](nTokens); // Amount of currency tokens sold per ID

    // Get token reserves
    uint256[] memory tokenReserves = _getTokenReserves(_tokenIds);

    // Assumes the currency Tokens are already received by contract, but not
    // the Tokens Ids

    // Remove liquidity for each Token ID in _tokenIds
    for (uint256 i = 0; i < nTokens; i++) {
      // Store current id and amount from argument arrays
      uint256 idBought = _tokenIds[i];
      uint256 amountBought = _tokensBoughtAmounts[i];
      uint256 tokenReserve = tokenReserves[i];

      require(amountBought > 0, "NE20#4"); // FnbswapExchange20#_currencyToToken: NULL_TOKENS_BOUGHT

      // Load currency token and Token _id reserves
      uint256 currencyReserve = currencyReserves[idBought];

      // Get amount of currency tokens to send for purchase
      // Neither reserves amount have been changed so far in this transaction, so
      // no adjustment to the inputs is needed
      uint256 currencyAmount = getBuyPrice(amountBought, currencyReserve, tokenReserve);

      // If royalty, increase amount buyer will need to pay after LP fees were calculated
      // Note: Royalty will be a bit higher since LF fees are added first
      (address royaltyRecipient, uint256 royaltyAmount) = getRoyaltyInfo(idBought, currencyAmount);
      if (royaltyAmount > 0) {
        royaltiesNumerator[royaltyRecipient] = royaltiesNumerator[royaltyRecipient].add(royaltyAmount.mul(ROYALTIES_DENOMINATOR));
      }

      // Calculate currency token amount to refund (if any) where whatever is not used will be returned
      // Will throw if total cost exceeds _maxCurrency
      totalRefundCurrency = totalRefundCurrency.sub(currencyAmount).sub(royaltyAmount);

      // Append Token id, Token id amount and currency token amount to tracking arrays
      currencySold[i] = currencyAmount.add(royaltyAmount);

      // Update individual currency reseve amount (royalty is not added to liquidity)
      currencyReserves[idBought] = currencyReserve.add(currencyAmount);
    }

    // Send Tokens all tokens purchased
    token.safeBatchTransferFrom(address(this), _recipient, _tokenIds, _tokensBoughtAmounts, "");
    
    // Refund currency token if any
    if (totalRefundCurrency > 0) {
      TransferHelper.safeTransfer(currency, _recipient, totalRefundCurrency);
    }

    return currencySold;
  }

  /**
   * @dev Pricing function used for converting between currency token to Tokens.
   * @param _assetBoughtAmount  Amount of Tokens being bought.
   * @param _assetSoldReserve   Amount of currency tokens in exchange reserves.
   * @param _assetBoughtReserve Amount of Tokens (output type) in exchange reserves.
   * @return price Amount of currency tokens to send to Fnbswap.
   */
  function getBuyPrice(
    uint256 _assetBoughtAmount,
    uint256 _assetSoldReserve,
    uint256 _assetBoughtReserve)
    override public view returns (uint256 price)
  {
    // Reserves must not be empty
    require(_assetSoldReserve > 0 && _assetBoughtReserve > 0, "NE20#5"); // FnbswapExchange20#getBuyPrice: EMPTY_RESERVE

    // Calculate price with fee
    uint256 numerator = _assetSoldReserve.mul(_assetBoughtAmount).mul(1000);
    uint256 denominator = (_assetBoughtReserve.sub(_assetBoughtAmount)).mul(FEE_MULTIPLIER);
    (price, ) = divRound(numerator, denominator);
    return price; // Will add 1 if rounding error
  }

  /**
   * @dev Pricing function used for converting Tokens to currency token (including royalty fee)
   * @param _tokenId            Id ot token being sold
   * @param _assetBoughtAmount  Amount of Tokens being bought.
   * @param _assetSoldReserve   Amount of currency tokens in exchange reserves.
   * @param _assetBoughtReserve Amount of Tokens (output type) in exchange reserves.
   * @return price Amount of currency tokens to send to Fnbswap.
   */
  function getBuyPriceWithRoyalty(
    uint256 _tokenId,
    uint256 _assetBoughtAmount,
    uint256 _assetSoldReserve,
    uint256 _assetBoughtReserve)
    override public view returns (uint256 price)
  {
    uint256 cost = getBuyPrice(_assetBoughtAmount, _assetSoldReserve, _assetBoughtReserve);
    (, uint256 royaltyAmount) = getRoyaltyInfo(_tokenId, cost);
    return cost.add(royaltyAmount);
  }

  /**
   * @notice Convert Tokens _id to currency tokens and transfers Tokens to recipient.
   * @dev User specifies EXACT Tokens _id sold and MINIMUM currency tokens received.
   * @dev Assumes that all trades will be valid, or the whole tx will fail
   * @dev Sorting _tokenIds is mandatory for efficient way of preventing duplicated IDs (which would lead to errors)
   * @param _tokenIds           Array of Token IDs that are sold
   * @param _tokensSoldAmounts  Array of Amount of Tokens sold for each id in _tokenIds.
   * @param _minCurrency        Minimum amount of currency tokens to receive
   * @param _deadline           Timestamp after which this transaction will be reverted
   * @param _recipient          The address that receives output currency tokens.
   * @param _extraFeeRecipients  Array of addresses that will receive extra fee
   * @param _extraFeeAmounts     Array of amounts of currency that will be sent as extra fee
   * @return currencyBought How much currency was actually purchased.
   */
  function _tokenToCurrency(
    uint256[] memory _tokenIds,
    uint256[] memory _tokensSoldAmounts,
    uint256 _minCurrency,
    uint256 _deadline,
    address _recipient,
    address[] memory _extraFeeRecipients,
    uint256[] memory _extraFeeAmounts
  )
    internal nonReentrant() returns (uint256[] memory currencyBought)
  {
    // Number of Token IDs to deposit
    uint256 nTokens = _tokenIds.length;

    // Input validation
    require(_deadline >= block.timestamp, "NE20#6"); // FnbswapExchange20#_tokenToCurrency: DEADLINE_EXCEEDED

    // Initialize variables
    uint256 totalCurrency = 0; // Total amount of currency tokens to transfer
    currencyBought = new uint256[](nTokens);

    // Get token reserves
    uint256[] memory tokenReserves = _getTokenReserves(_tokenIds);

    // Assumes the Tokens ids are already received by contract, but not
    // the Tokens Ids. Will return cards not sold if invalid price.

    // Remove liquidity for each Token ID in _tokenIds
    for (uint256 i = 0; i < nTokens; i++) {
      // Store current id and amount from argument arrays
      uint256 idSold = _tokenIds[i];
      uint256 amountSold = _tokensSoldAmounts[i];
      uint256 tokenReserve = tokenReserves[i];

      // If 0 tokens send for this ID, revert
      require(amountSold > 0, "NE20#7"); // FnbswapExchange20#_tokenToCurrency: NULL_TOKENS_SOLD

      // Load currency token and Token _id reserves
      uint256 currencyReserve = currencyReserves[idSold];

      // Get amount of currency that will be received
      // Need to sub amountSold because tokens already added in reserve, which would bias the calculation
      // Don't need to add it for currencyReserve because the amount is added after this calculation
      uint256 currencyAmount = getSellPrice(amountSold, tokenReserve.sub(amountSold), currencyReserve);

      // If royalty, substract amount seller will receive after LP fees were calculated
      // Note: Royalty will be a bit lower since LF fees are substracted first
      (address royaltyRecipient, uint256 royaltyAmount) = getRoyaltyInfo(idSold, currencyAmount);
      if (royaltyAmount > 0) {
        royaltiesNumerator[royaltyRecipient] = royaltiesNumerator[royaltyRecipient].add(royaltyAmount.mul(ROYALTIES_DENOMINATOR));
      }

      // Increase total amount of currency to receive (minus royalty to pay)
      totalCurrency = totalCurrency.add(currencyAmount.sub(royaltyAmount));

      // Update individual currency reseve amount
      currencyReserves[idSold] = currencyReserve.sub(currencyAmount);

      // Append Token id, Token id amount and currency token amount to tracking arrays
      currencyBought[i] = currencyAmount.sub(royaltyAmount);
    }

    // Set the extra fees aside to recipients after sale
    for (uint256 i = 0; i < _extraFeeAmounts.length; i++) {
      if (_extraFeeAmounts[i] > 0) {
        totalCurrency = totalCurrency.sub(_extraFeeAmounts[i]);
        royaltiesNumerator[_extraFeeRecipients[i]] = royaltiesNumerator[_extraFeeRecipients[i]].add(_extraFeeAmounts[i].mul(ROYALTIES_DENOMINATOR));
      }
    }

    // If minCurrency is not met
    require(totalCurrency >= _minCurrency, "NE20#8"); // FnbswapExchange20#_tokenToCurrency: INSUFFICIENT_CURRENCY_AMOUNT

    // Transfer currency here
    TransferHelper.safeTransfer(currency, _recipient, totalCurrency);
    return currencyBought;
  }

  /**
   * @dev Pricing function used for converting Tokens to currency token.
   * @param _assetSoldAmount    Amount of Tokens being sold.
   * @param _assetSoldReserve   Amount of Tokens in exchange reserves.
   * @param _assetBoughtReserve Amount of currency tokens in exchange reserves.
   * @return price Amount of currency tokens to receive from Fnbswap.
   */
  function getSellPrice(
    uint256 _assetSoldAmount,
    uint256 _assetSoldReserve,
    uint256 _assetBoughtReserve)
    override public view returns (uint256 price)
  {
    //Reserves must not be empty
    require(_assetSoldReserve > 0 && _assetBoughtReserve > 0, "NE20#9"); // FnbswapExchange20#getSellPrice: EMPTY_RESERVE

    // Calculate amount to receive (with fee) before royalty
    uint256 _assetSoldAmount_withFee = _assetSoldAmount.mul(FEE_MULTIPLIER);
    uint256 numerator = _assetSoldAmount_withFee.mul(_assetBoughtReserve);
    uint256 denominator = _assetSoldReserve.mul(1000).add(_assetSoldAmount_withFee);
    return numerator / denominator; //Rounding errors will favor Fnbswap, so nothing to do
  }

  /**
   * @dev Pricing function used for converting Tokens to currency token (including royalty fee)
   * @param _tokenId            Id ot token being sold
   * @param _assetSoldAmount    Amount of Tokens being sold.
   * @param _assetSoldReserve   Amount of Tokens in exchange reserves.
   * @param _assetBoughtReserve Amount of currency tokens in exchange reserves.
   * @return price Amount of currency tokens to receive from Fnbswap.
   */
  function getSellPriceWithRoyalty(
    uint256 _tokenId,
    uint256 _assetSoldAmount,
    uint256 _assetSoldReserve,
    uint256 _assetBoughtReserve)
    override public view returns (uint256 price)
  {
    uint256 sellAmount = getSellPrice(_assetSoldAmount, _assetSoldReserve, _assetBoughtReserve);
    (, uint256 royaltyAmount) = getRoyaltyInfo(_tokenId, sellAmount);
    return sellAmount.sub(royaltyAmount);
  }

  /***********************************|
  |        Liquidity Functions        |
  |__________________________________*/

  /**
   * @notice Deposit less than max currency tokens && exact Tokens (token ID) at current ratio to mint liquidity pool tokens.
   * @dev min_liquidity does nothing when total liquidity pool token supply is 0.
   * @dev Assumes that sender approved this contract on the currency
   * @dev Sorting _tokenIds is mandatory for efficient way of preventing duplicated IDs (which would lead to errors)
   * @param _provider      Address that provides liquidity to the reserve
   * @param _tokenIds      Array of Token IDs where liquidity is added
   * @param _tokenAmounts  Array of amount of Tokens deposited corresponding to each ID provided in _tokenIds
   * @param _maxCurrency   Array of maximum number of tokens deposited for each ID provided in _tokenIds.
   *                       Deposits max amount if total liquidity pool token supply is 0.
   * @param _deadline      Timestamp after which this transaction will be reverted
   */
  function _addLiquidity(
    address _provider,
    uint256[] memory _tokenIds,
    uint256[] memory _tokenAmounts,
    uint256[] memory _maxCurrency,
    uint256 _deadline)
    internal nonReentrant()
  {
    // Requirements
    require(_deadline >= block.timestamp, "NE20#10"); // FnbswapExchange20#_addLiquidity: DEADLINE_EXCEEDED

    // Initialize variables
    uint256 nTokens = _tokenIds.length; // Number of Token IDs to deposit
    uint256 totalCurrency = 0;          // Total amount of currency tokens to transfer

    // Initialize arrays
    uint256[] memory liquiditiesToMint = new uint256[](nTokens);
    uint256[] memory currencyAmounts = new uint256[](nTokens);

    // Get token reserves
    uint256[] memory tokenReserves = _getTokenReserves(_tokenIds);

    // Assumes tokens _ids are deposited already, but not currency tokens
    // as this is calculated and executed below.

    // Loop over all Token IDs to deposit
    for (uint256 i = 0; i < nTokens; i ++) {
      // Store current id and amount from argument arrays
      uint256 tokenId = _tokenIds[i];
      uint256 amount = _tokenAmounts[i];

      // Check if input values are acceptable
      require(_maxCurrency[i] > 0, "NE20#11"); // FnbswapExchange20#_addLiquidity: NULL_MAX_CURRENCY
      require(amount > 0, "NE20#12"); // FnbswapExchange20#_addLiquidity: NULL_TOKENS_AMOUNT

      // Current total liquidity calculated in currency token
      uint256 totalLiquidity = totalSupplies[tokenId];

      // When reserve for this token already exists
      if (totalLiquidity > 0) {

        // Load currency token and Token reserve's supply of Token id
        uint256 currencyReserve = currencyReserves[tokenId]; // Amount not yet in reserve
        uint256 tokenReserve = tokenReserves[i];

        /**
        * Amount of currency tokens to send to token id reserve:
        * X/Y = dx/dy
        * dx = X*dy/Y
        * where
        *   X:  currency total liquidity
        *   Y:  Token _id total liquidity (before tokens were received)
        *   dy: Amount of token _id deposited
        *   dx: Amount of currency to deposit
        *
        * Adding .add(1) if rounding errors so to not favor users incorrectly
        */
        (uint256 currencyAmount, bool rounded) = divRound(amount.mul(currencyReserve), tokenReserve.sub(amount));
        require(_maxCurrency[i] >= currencyAmount, "NE20#13"); // FnbswapExchange20#_addLiquidity: MAX_CURRENCY_AMOUNT_EXCEEDED

        // Update currency reserve size for Token id before transfer
        currencyReserves[tokenId] = currencyReserve.add(currencyAmount);

        // Update totalCurrency
        totalCurrency = totalCurrency.add(currencyAmount);

        // Proportion of the liquidity pool to give to current liquidity provider
        // If rounding error occured, round down to favor previous liquidity providers
        liquiditiesToMint[i] = (currencyAmount.sub(rounded ? 1 : 0)).mul(totalLiquidity) / currencyReserve;
        currencyAmounts[i] = currencyAmount;

        // Mint liquidity ownership tokens and increase liquidity supply accordingly
        totalSupplies[tokenId] = totalLiquidity.add(liquiditiesToMint[i]);

      } else {
        uint256 maxCurrency = _maxCurrency[i];

        // Otherwise rounding error could end up being significant on second deposit
        require(maxCurrency >= 1000, "NE20#14"); // FnbswapExchange20#_addLiquidity: INVALID_CURRENCY_AMOUNT

        // Update currency  reserve size for Token id before transfer
        currencyReserves[tokenId] = maxCurrency;

        // Update totalCurrency
        totalCurrency = totalCurrency.add(maxCurrency);

        // Initial liquidity is amount deposited (Incorrect pricing will be arbitraged)
        // uint256 initialLiquidity = _maxCurrency;
        totalSupplies[tokenId] = maxCurrency;

        // Liquidity to mints
        liquiditiesToMint[i] = maxCurrency;
        currencyAmounts[i] = maxCurrency;
      }
    }

    // Transfer all currency to this contract
    TransferHelper.safeTransferFrom(currency, _provider, address(this), totalCurrency);

    // Mint liquidity pool tokens
    _batchMint(_provider, _tokenIds, liquiditiesToMint, "");


    // Emit event
    emit LiquidityAdded(_provider, _tokenIds, _tokenAmounts, currencyAmounts);
  }

  /**
   * @dev Convert pool participation into amounts of token and currency.
   * @dev Rounding error of the asset with lower resolution is traded for the other asset.
   * @param _amountPool       Participation to be converted to tokens and currency.
   * @param _tokenReserve     Amount of tokens on the AMM reserve.
   * @param _currencyReserve  Amount of currency on the AMM reserve.
   * @param _totalLiquidity   Total liquidity on the pool.
   *
   * @return currencyAmount Currency corresponding to pool amount plus rounded tokens.
   * @return tokenAmount    Token corresponding to pool amount plus rounded currency.
   */
  function _toRoundedLiquidity(
    uint256 _tokenId,
    uint256 _amountPool,
    uint256 _tokenReserve,
    uint256 _currencyReserve,
    uint256 _totalLiquidity
  ) internal view returns (
    uint256 currencyAmount,
    uint256 tokenAmount,
    uint256 soldTokenNumerator,
    uint256 boughtCurrencyNumerator,
    address royaltyRecipient,
    uint256 royaltyNumerator
  ) {
    uint256 currencyNumerator = _amountPool.mul(_currencyReserve);
    uint256 tokenNumerator = _amountPool.mul(_tokenReserve);

    // Convert all tokenProduct rest to currency
    soldTokenNumerator = tokenNumerator % _totalLiquidity;

    if (soldTokenNumerator != 0) {
      // The trade happens "after" funds are out of the pool
      // so we need to remove these funds before computing the rate
      uint256 virtualTokenReserve = _tokenReserve.sub(tokenNumerator / _totalLiquidity).mul(_totalLiquidity);
      uint256 virtualCurrencyReserve = _currencyReserve.sub(currencyNumerator / _totalLiquidity).mul(_totalLiquidity);

      // Skip process if any of the two reserves is left empty
      // this step is important to avoid an error withdrawing all left liquidity
      if (virtualCurrencyReserve != 0 && virtualTokenReserve != 0) {
        boughtCurrencyNumerator = getSellPrice(soldTokenNumerator, virtualTokenReserve, virtualCurrencyReserve);

        // Discount royalty currency
        (royaltyRecipient, royaltyNumerator) = getRoyaltyInfo(_tokenId, boughtCurrencyNumerator);
        boughtCurrencyNumerator = boughtCurrencyNumerator.sub(royaltyNumerator);

        currencyNumerator = currencyNumerator.add(boughtCurrencyNumerator);

        // Add royalty numerator (needs to be converted to ROYALTIES_DENOMINATOR)
        royaltyNumerator = royaltyNumerator.mul(ROYALTIES_DENOMINATOR) / _totalLiquidity;
      }
    }

    // Calculate amounts
    currencyAmount = currencyNumerator / _totalLiquidity;
    tokenAmount = tokenNumerator / _totalLiquidity;
  }

  /**
   * @dev Burn liquidity pool tokens to withdraw currency  && Tokens at current ratio.
   * @dev Sorting _tokenIds is mandatory for efficient way of preventing duplicated IDs (which would lead to errors)
   * @param _provider         Address that removes liquidity to the reserve
   * @param _tokenIds         Array of Token IDs where liquidity is removed
   * @param _poolTokenAmounts Array of Amount of liquidity pool tokens burned for each Token id in _tokenIds.
   * @param _minCurrency      Minimum currency withdrawn for each Token id in _tokenIds.
   * @param _minTokens        Minimum Tokens id withdrawn for each Token id in _tokenIds.
   * @param _deadline         Timestamp after which this transaction will be reverted
   */
  function _removeLiquidity(
    address _provider,
    uint256[] memory _tokenIds,
    uint256[] memory _poolTokenAmounts,
    uint256[] memory _minCurrency,
    uint256[] memory _minTokens,
    uint256 _deadline)
    internal nonReentrant()
  {
    // Input validation
    require(_deadline > block.timestamp, "NE20#15"); // FnbswapExchange20#_removeLiquidity: DEADLINE_EXCEEDED

    // Initialize variables
    uint256 nTokens = _tokenIds.length;                        // Number of Token IDs to deposit
    uint256 totalCurrency = 0;                                 // Total amount of currency  to transfer
    uint256[] memory tokenAmounts = new uint256[](nTokens);    // Amount of Tokens to transfer for each id
 
    // Structs contain most information for the event
    // notice: tokenAmounts and tokenIds are absent because we already
    // either have those arrays constructed or we need to construct them for other reasons
    LiquidityRemovedEventObj[] memory eventObjs = new LiquidityRemovedEventObj[](nTokens);

    // Get token reserves
    uint256[] memory tokenReserves = _getTokenReserves(_tokenIds);

    // Assumes NIFTY liquidity tokens are already received by contract, but not
    // the currency nor the Tokens Ids

    // Remove liquidity for each Token ID in _tokenIds
    for (uint256 i = 0; i < nTokens; i++) {
      // Store current id and amount from argument arrays
      uint256 id = _tokenIds[i];
      uint256 amountPool = _poolTokenAmounts[i];

      // Load total liquidity pool token supply for Token _id
      uint256 totalLiquidity = totalSupplies[id];
      require(totalLiquidity > 0, "NE20#16"); // FnbswapExchange20#_removeLiquidity: NULL_TOTAL_LIQUIDITY

      // Load currency and Token reserve's supply of Token id
      uint256 currencyReserve = currencyReserves[id];

      // Calculate amount to withdraw for currency  and Token _id
      uint256 currencyAmount;
      uint256 tokenAmount;

      {
        uint256 tokenReserve = tokenReserves[i];
        uint256 soldTokenNumerator;
        uint256 boughtCurrencyNumerator;
        address royaltyRecipient;
        uint256 royaltyNumerator;

        (
          currencyAmount,
          tokenAmount,
          soldTokenNumerator,
          boughtCurrencyNumerator,
          royaltyRecipient,
          royaltyNumerator
        ) = _toRoundedLiquidity(id, amountPool, tokenReserve, currencyReserve, totalLiquidity);

        // Add royalties
        royaltiesNumerator[royaltyRecipient] = royaltiesNumerator[royaltyRecipient].add(royaltyNumerator);

        // Add trade info to event
        eventObjs[i].soldTokenNumerator = soldTokenNumerator;
        eventObjs[i].boughtCurrencyNumerator = boughtCurrencyNumerator;
        eventObjs[i].totalSupply = totalLiquidity;
      }

      // Verify if amounts to withdraw respect minimums specified
      require(currencyAmount >= _minCurrency[i], "NE20#17"); // FnbswapExchange20#_removeLiquidity: INSUFFICIENT_CURRENCY_AMOUNT
      require(tokenAmount >= _minTokens[i], "NE20#18"); // FnbswapExchange20#_removeLiquidity: INSUFFICIENT_TOKENS

      // Update total liquidity pool token supply of Token _id
      totalSupplies[id] = totalLiquidity.sub(amountPool);

      // Update currency reserve size for Token id
      currencyReserves[id] = currencyReserve.sub(currencyAmount);

      // Update totalCurrency and tokenAmounts
      totalCurrency = totalCurrency.add(currencyAmount);
      tokenAmounts[i] = tokenAmount;

      eventObjs[i].currencyAmount = currencyAmount;
    }

    // Burn liquidity pool tokens for offchain supplies
    _batchBurn(address(this), _tokenIds, _poolTokenAmounts);

    // Transfer total currency and all Tokens ids
    TransferHelper.safeTransfer(currency, _provider, totalCurrency);
    token.safeBatchTransferFrom(address(this), _provider, _tokenIds, tokenAmounts, "");

    // Emit event
    emit LiquidityRemoved(_provider, _tokenIds, tokenAmounts, eventObjs);
  }

  /***********************************|
  |     Receiver Methods Handler      |
  |__________________________________*/

  // Method signatures for onReceive control logic

  // bytes4(keccak256(
  //   "_tokenToCurrency(uint256[],uint256[],uint256,uint256,address,address[],uint256[])"
  // ));
  bytes4 internal constant SELLTOKENS_SIG = 0xade79c7a;

  //  bytes4(keccak256(
  //   "_addLiquidity(address,uint256[],uint256[],uint256[],uint256)"
  // ));
  bytes4 internal constant ADDLIQUIDITY_SIG = 0x82da2b73;

  // bytes4(keccak256(
  //    "_removeLiquidity(address,uint256[],uint256[],uint256[],uint256[],uint256)"
  // ));
  bytes4 internal constant REMOVELIQUIDITY_SIG = 0x5c0bf259;

  // bytes4(keccak256(
  //   "DepositTokens()"
  // ));
  bytes4 internal constant DEPOSIT_SIG = 0xc8c323f9;

  /***********************************|
  |           Buying Tokens           |
  |__________________________________*/

  /**
   * @notice Convert currency tokens to Tokens _id and transfers Tokens to recipient.
   * @dev User specifies MAXIMUM inputs (_maxCurrency) and EXACT outputs.
   * @dev Assumes that all trades will be successful, or revert the whole tx
   * @dev Exceeding currency tokens sent will be refunded to recipient
   * @dev Sorting IDs is mandatory for efficient way of preventing duplicated IDs (which would lead to exploit)
   * @param _tokenIds            Array of Tokens ID that are bought
   * @param _tokensBoughtAmounts Amount of Tokens id bought for each corresponding Token id in _tokenIds
   * @param _maxCurrency         Total maximum amount of currency tokens to spend for all Token ids
   * @param _deadline            Timestamp after which this transaction will be reverted
   * @param _recipient           The address that receives output Tokens and refund
   * @param _extraFeeRecipients  Array of addresses that will receive extra fee
   * @param _extraFeeAmounts     Array of amounts of currency that will be sent as extra fee
   * @return currencySold How much currency was actually sold.
   */
  function buyTokens(
    uint256[] memory _tokenIds,
    uint256[] memory _tokensBoughtAmounts,
    uint256 _maxCurrency,
    uint256 _deadline,
    address _recipient,
    address[] memory _extraFeeRecipients,
    uint256[] memory _extraFeeAmounts
  )
    override external returns (uint256[] memory)
  {
    require(_deadline >= block.timestamp, "NE20#19"); // FnbswapExchange20#buyTokens: DEADLINE_EXCEEDED
    require(_tokenIds.length > 0, "NE20#20"); // FnbswapExchange20#buyTokens: INVALID_CURRENCY_IDS_AMOUNT

    // Transfer the tokens for purchase
    TransferHelper.safeTransferFrom(currency, msg.sender, address(this), _maxCurrency);

    address recipient = _recipient == address(0x0) ? msg.sender : _recipient;

    // Set the extra fee aside to recipients ahead of purchase, if any.
    uint256 maxCurrency = _maxCurrency;
    uint256 nExtraFees = _extraFeeRecipients.length;
    require(nExtraFees == _extraFeeAmounts.length, "NE20#21"); // FnbswapExchange20#buyTokens: EXTRA_FEES_ARRAYS_ARE_NOT_SAME_LENGTH
    
    for (uint256 i = 0; i < nExtraFees; i++) {
      if (_extraFeeAmounts[i] > 0) {
        maxCurrency = maxCurrency.sub(_extraFeeAmounts[i]);
        royaltiesNumerator[_extraFeeRecipients[i]] = royaltiesNumerator[_extraFeeRecipients[i]].add(_extraFeeAmounts[i].mul(ROYALTIES_DENOMINATOR));
      }
    }

    // Execute trade and retrieve amount of currency spent
    uint256[] memory currencySold = _currencyToToken(_tokenIds, _tokensBoughtAmounts, maxCurrency, _deadline, recipient);
    emit TokensPurchase(msg.sender, recipient, _tokenIds, _tokensBoughtAmounts, currencySold, _extraFeeRecipients, _extraFeeAmounts);

    return currencySold;
  }

  /**
   * @notice Handle which method is being called on transfer
   * @dev `_data` must be encoded as follow: abi.encode(bytes4, MethodObj)
   *   where bytes4 argument is the MethodObj object signature passed as defined
   *   in the `Signatures for onReceive control logic` section above
   * @param _from     The address which previously owned the Token
   * @param _ids      An array containing ids of each Token being transferred
   * @param _amounts  An array containing amounts of each Token being transferred
   * @param _data     Method signature and corresponding encoded arguments for method to call on *this* contract
   * @return bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")
   */
  function onERC1155BatchReceived(
    address, // _operator,
    address _from,
    uint256[] memory _ids,
    uint256[] memory _amounts,
    bytes memory _data)
    override public returns(bytes4)
  {
    // This function assumes that the ERC-1155 token contract can
    // only call `onERC1155BatchReceived()` via a valid token transfer.
    // Users must be responsible and only use this Fnbswap exchange
    // contract with ERC-1155 compliant token contracts.

    // Obtain method to call via object signature
    bytes4 functionSignature = abi.decode(_data, (bytes4));

    /***********************************|
    |           Selling Tokens          |
    |__________________________________*/

    if (functionSignature == SELLTOKENS_SIG) {

      // Tokens received need to be Token contract
      require(msg.sender == address(token), "NE20#22"); // FnbswapExchange20#onERC1155BatchReceived: INVALID_TOKENS_TRANSFERRED

      // Decode SellTokensObj from _data to call _tokenToCurrency()
      SellTokensObj memory obj;
      (, obj) = abi.decode(_data, (bytes4, SellTokensObj));
      address recipient = obj.recipient == address(0x0) ? _from : obj.recipient;

      // Validate fee arrays
      require(obj.extraFeeRecipients.length == obj.extraFeeAmounts.length, "NE20#23"); // FnbswapExchange20#buyTokens: EXTRA_FEES_ARRAYS_ARE_NOT_SAME_LENGTH
    
      // Execute trade and retrieve amount of currency received
      uint256[] memory currencyBought = _tokenToCurrency(_ids, _amounts, obj.minCurrency, obj.deadline, recipient, obj.extraFeeRecipients, obj.extraFeeAmounts);
      emit CurrencyPurchase(_from, recipient, _ids, _amounts, currencyBought, obj.extraFeeRecipients, obj.extraFeeAmounts);

    /***********************************|
    |      Adding Liquidity Tokens      |
    |__________________________________*/

    } else if (functionSignature == ADDLIQUIDITY_SIG) {
      // Only allow to receive ERC-1155 tokens from `token` contract
      require(msg.sender == address(token), "NE20#24"); // FnbswapExchange20#onERC1155BatchReceived: INVALID_TOKEN_TRANSFERRED

      // Decode AddLiquidityObj from _data to call _addLiquidity()
      AddLiquidityObj memory obj;
      (, obj) = abi.decode(_data, (bytes4, AddLiquidityObj));
      _addLiquidity(_from, _ids, _amounts, obj.maxCurrency, obj.deadline);

    /***********************************|
    |      Removing iquidity Tokens     |
    |__________________________________*/

    } else if (functionSignature == REMOVELIQUIDITY_SIG) {
      // Tokens received need to be NIFTY-1155 tokens
      require(msg.sender == address(this), "NE20#25"); // FnbswapExchange20#onERC1155BatchReceived: INVALID_NIFTY_TOKENS_TRANSFERRED

      // Decode RemoveLiquidityObj from _data to call _removeLiquidity()
      RemoveLiquidityObj memory obj;
      (, obj) = abi.decode(_data, (bytes4, RemoveLiquidityObj));
      _removeLiquidity(_from, _ids, _amounts, obj.minCurrency, obj.minTokens, obj.deadline);

    /***********************************|
    |      Deposits & Invalid Calls     |
    |__________________________________*/

    } else if (functionSignature == DEPOSIT_SIG) {
      // Do nothing for when contract is self depositing
      // This could be use to deposit currency "by accident", which would be locked
      require(msg.sender == address(currency), "NE20#26"); // FnbswapExchange20#onERC1155BatchReceived: INVALID_TOKENS_DEPOSITED

    } else {
      revert("NE20#27"); // FnbswapExchange20#onERC1155BatchReceived: INVALID_METHOD
    }

    return ERC1155_BATCH_RECEIVED_VALUE;
  }

  /**
   * @dev Will pass to onERC115Batch5Received
   */
  function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _amount, bytes memory _data)
    override public returns(bytes4)
  {
    uint256[] memory ids = new uint256[](1);
    uint256[] memory amounts = new uint256[](1);

    ids[0] = _id;
    amounts[0] = _amount;

    require(
      ERC1155_BATCH_RECEIVED_VALUE == onERC1155BatchReceived(_operator, _from, ids, amounts, _data),
      "NE20#28" // FnbswapExchange20#onERC1155Received: INVALID_ONRECEIVED_MESSAGE
    );

    return ERC1155_RECEIVED_VALUE;
  }

  /**
   * @notice Prevents receiving Ether or calls to unsuported methods
   */
  fallback () external {
    revert("NE20#29"); // FnbswapExchange20:UNSUPPORTED_METHOD
  }

  /***********************************|
  |         Royalty Functions         |
  |__________________________________*/

  /**
   * @notice Will set the royalties fees and recipient for contracts that don't support ERC-2981
   * @param _fee       Fee pourcentage with a 10000 basis (e.g. 0.3% is 3 and 1% is 10 and 100% is 1000)
   * @param _recipient Address where to send the fees to
   */
  function setRoyaltyInfo(uint256 _fee, address _recipient) onlyOwner public {
    // Don't use IS_ERC2981 in case token contract was updated
    bool isERC2981 = token.supportsInterface(type(IERC2981).interfaceId);
    require(!isERC2981, "NE20#30"); // FnbswapExchange20#setRoyaltyInfo: TOKEN SUPPORTS ERC-2981
    require(_fee <= MAX_ROYALTY, "NE20#31"); // FnbswapExchange20#setRoyaltyInfo: ROYALTY_FEE_IS_TOO_HIGH

    globalRoyaltyFee = _fee;
    globalRoyaltyRecipient = _recipient;
    emit RoyaltyChanged(_recipient, _fee);
  }

  /**
   * @notice Will send the royalties that _royaltyRecipient can claim, if any 
   * @dev Anyone can call this function such that payout could be distributed 
   *      regularly instead of being claimed. 
   * @param _royaltyRecipient Address that is able to claim royalties
   */
  function sendRoyalties(address _royaltyRecipient) override external {
    uint256 royaltyAmount = royaltiesNumerator[_royaltyRecipient] / ROYALTIES_DENOMINATOR;
    royaltiesNumerator[_royaltyRecipient] = royaltiesNumerator[_royaltyRecipient] % ROYALTIES_DENOMINATOR;
    TransferHelper.safeTransfer(currency, _royaltyRecipient, royaltyAmount);
  }

  /**
   * @notice Will return how much of currency need to be paid for the royalty
   * @notice Royalty is capped at 25% of the total amount of currency
   * @param _tokenId ID of the erc-1155 token being traded
   * @param _cost    Amount of currency sent/received for the trade
   * @return recipient Address that will be able to claim the royalty
   * @return royalty Amount of currency that will be sent to royalty recipient
   */
  function getRoyaltyInfo(uint256 _tokenId, uint256 _cost) public view returns (address recipient, uint256 royalty) {
    if (IS_ERC2981) {
      // Add a try/catch in-case token *removed* ERC-2981 support
      try IERC2981(address(token)).royaltyInfo(_tokenId, _cost) returns(address _r, uint256 _c) {
        // Cap to 25% of the total amount of currency
        uint256 max = _cost.mul(MAX_ROYALTY) / ROYALTIES_DENOMINATOR;
        return (_r, _c > max ? max : _c);
      } catch {
        // Default back to global setting if error occurs
        return (globalRoyaltyRecipient, (_cost.mul(globalRoyaltyFee)) / ROYALTIES_DENOMINATOR);
      }

    } else {
      return (globalRoyaltyRecipient, (_cost.mul(globalRoyaltyFee)) / ROYALTIES_DENOMINATOR);
    }
  }


  /***********************************|
  |         Getter Functions          |
  |__________________________________*/

  /**
   * @notice Get amount of currency in reserve for each Token _id in _ids
   * @param _ids Array of ID sto query currency reserve of
   * @return amount of currency in reserve for each Token _id
   */
  function getCurrencyReserves(
    uint256[] calldata _ids)
    override external view returns (uint256[] memory)
  {
    uint256 nIds = _ids.length;
    uint256[] memory currencyReservesReturn = new uint256[](nIds);
    for (uint256 i = 0; i < nIds; i++) {
      currencyReservesReturn[i] = currencyReserves[_ids[i]];
    }
    return currencyReservesReturn;
  }

  /**
   * @notice Return price for `currency => Token _id` trades with an exact token amount.
   * @param _ids           Array of ID of tokens bought.
   * @param _tokensBought Amount of Tokens bought.
   * @return Amount of currency needed to buy Tokens in _ids for amounts in _tokensBought
   */
  function getPrice_currencyToToken(
    uint256[] calldata _ids,
    uint256[] calldata _tokensBought)
    override external view returns (uint256[] memory)
  {
    uint256 nIds = _ids.length;
    uint256[] memory prices = new uint256[](nIds);

    for (uint256 i = 0; i < nIds; i++) {
      // Load Token id reserve
      uint256 tokenReserve = token.balanceOf(address(this), _ids[i]);
      prices[i] = getBuyPriceWithRoyalty(_ids[i], _tokensBought[i], currencyReserves[_ids[i]], tokenReserve);
    }

    // Return prices
    return prices;
  }

  /**
   * @notice Return price for `Token _id => currency` trades with an exact token amount.
   * @param _ids        Array of IDs  token sold.
   * @param _tokensSold Array of amount of each Token sold.
   * @return Amount of currency that can be bought for Tokens in _ids for amounts in _tokensSold
   */
  function getPrice_tokenToCurrency(
    uint256[] calldata _ids,
    uint256[] calldata _tokensSold)
    override external view returns (uint256[] memory)
  {
    uint256 nIds = _ids.length;
    uint256[] memory prices = new uint256[](nIds);

    for (uint256 i = 0; i < nIds; i++) {
      // Load Token id reserve
      uint256 tokenReserve = token.balanceOf(address(this), _ids[i]);
      prices[i] = getSellPriceWithRoyalty(_ids[i], _tokensSold[i], tokenReserve, currencyReserves[_ids[i]]);
    }

    // Return price
    return prices;
  }

  /**
   * @return Address of Token that is sold on this exchange.
   */
  function getTokenAddress() override external view returns (address) {
    return address(token);
  }

  /**
   * @return LP fee per 1000 units
   */
  function getLPFee() override external view returns (uint256) {
    return 1000-FEE_MULTIPLIER;
  }

  /**
   * @return Address of the currency contract that is used as currency
   */
  function getCurrencyInfo() override external view returns (address) {
    return (address(currency));
  }

  /**
   * @notice Get total supply of liquidity tokens
   * @param _ids ID of the Tokens
   * @return The total supply of each liquidity token id provided in _ids
   */
  function getTotalSupply(uint256[] calldata _ids)
    override external view returns (uint256[] memory)
  {
    // Number of ids
    uint256 nIds = _ids.length;

    // Variables
    uint256[] memory batchTotalSupplies = new uint256[](nIds);

    // Iterate over each owner and token ID
    for (uint256 i = 0; i < nIds; i++) {
      batchTotalSupplies[i] = totalSupplies[_ids[i]];
    }

    return batchTotalSupplies;
  }

  /**
   * @return Address of factory that created this exchange.
   */
  function getFactoryAddress() override external view returns (address) {
    return factory;
  }

  /**
   * @return Global royalty fee % if not supporting ERC-2981
   */
  function getGlobalRoyaltyFee() override external view returns (uint256) {
    return globalRoyaltyFee;
  }

  /**
   * @return Global royalty recipient if token not supporting ERC-2981
   */
  function getGlobalRoyaltyRecipient() override external view returns (address) {
    return globalRoyaltyRecipient;
  }

  /**
   * @return Get amount of currency in royalty an address can claim
   * @param _royaltyRecipient Address to check the claimable royalties
   */
  function getRoyalties(address _royaltyRecipient) override external view returns (uint256) {
    return royaltiesNumerator[_royaltyRecipient] / ROYALTIES_DENOMINATOR;
  }

  function getRoyaltiesNumerator(address _royaltyRecipient) override external view returns (uint256) {
    return royaltiesNumerator[_royaltyRecipient];
  }

  /***********************************|
  |         Utility Functions         |
  |__________________________________*/

  /**
   * @notice Divides two numbers and add 1 if there is a rounding error
   * @param a Numerator
   * @param b Denominator
   */
  function divRound(uint256 a, uint256 b) internal pure returns (uint256, bool) {
    return a % b == 0 ? (a/b, false) : ((a/b).add(1), true);
  }

  /**
   * @notice Return Token reserves for given Token ids
   * @dev Assumes that ids are sorted from lowest to highest with no duplicates.
   *      This assumption allows for checking the token reserves only once, otherwise
   *      token reserves need to be re-checked individually or would have to do more expensive
   *      duplication checks.
   * @param _tokenIds Array of IDs to query their Reserve balance.
   * @return Array of Token ids' reserves
   */
  function _getTokenReserves(
    uint256[] memory _tokenIds)
    internal view returns (uint256[] memory)
  {
    uint256 nTokens = _tokenIds.length;

    // Regular balance query if only 1 token, otherwise batch query
    if (nTokens == 1) {
      uint256[] memory tokenReserves = new uint256[](1);
      tokenReserves[0] = token.balanceOf(address(this), _tokenIds[0]);
      return tokenReserves;

    } else {
      // Lazy check preventing duplicates & build address array for query
      address[] memory thisAddressArray = new address[](nTokens);
      thisAddressArray[0] = address(this);

      for (uint256 i = 1; i < nTokens; i++) {
        require(_tokenIds[i-1] < _tokenIds[i], "NE20#32"); // FnbswapExchange20#_getTokenReserves: UNSORTED_OR_DUPLICATE_TOKEN_IDS
        thisAddressArray[i] = address(this);
      }
      return token.balanceOfBatch(thisAddressArray, _tokenIds);
    }
  }

  /**
   * @notice Indicates whether a contract implements the `ERC1155TokenReceiver` functions and so can accept ERC1155 token types.
   * @param  interfaceID The ERC-165 interface ID that is queried for support.s
   * @dev This function MUST return true if it implements the ERC1155TokenReceiver interface and ERC-165 interface.
   *      This function MUST NOT consume more thsan 5,000 gas.
   * @return Whether a given interface is supported
   */
  function supportsInterface(bytes4 interfaceID) public override pure returns (bool) {
    return interfaceID == type(IERC20).interfaceId ||
      interfaceID == type(IERC165).interfaceId || 
      interfaceID == type(IERC1155).interfaceId || 
      interfaceID == type(IERC1155TokenReceiver).interfaceId ||
      interfaceID == type(IERC1155Metadata).interfaceId;
  }
}

File 2 of 18 : TransferHelper.sol
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity >=0.6.0;

// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
    function safeApprove(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeApprove: approve failed'
        );
    }

    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeTransfer: transfer failed'
        );
    }

    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::transferFrom: transferFrom failed'
        );
    }

    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
    }
}

File 3 of 18 : ReentrancyGuard.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)

pragma solidity 0.7.4;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

File 4 of 18 : DelegatedOwnable.sol
pragma solidity 0.7.4;
pragma experimental ABIEncoderV2;

import "../interfaces/IOwnable.sol";

/**
 * @title Ownable
 * @dev The Ownable contract inherits the owner of a parent contract as its owner, 
 * and provides basic authorization control functions, this simplifies the 
 * implementation of "user permissions".
 */
contract DelegatedOwnable {
  address internal ownableParent;

  event ParentOwnerChanged(address indexed previousParent, address indexed newParent);

  /**
   * @dev The Ownable constructor sets the original `ownableParent` of the contract to the specied address
   * @param _firstOwnableParent Address of the first ownable parent contract
   */
  constructor (address _firstOwnableParent) {
    try IOwnable(_firstOwnableParent).getOwner() {
      // Do nothing if parent has ownable function
    } catch {
      revert("DO#1"); // PARENT IS NOT OWNABLE
    }
    ownableParent = _firstOwnableParent;
    emit ParentOwnerChanged(address(0), _firstOwnableParent);
  }

  /**
   * @dev Throws if called by any account other than the master owner.
   */
  modifier onlyOwner() {
    require(msg.sender == getOwner(), "DO#2"); // DelegatedOwnable#onlyOwner: SENDER_IS_NOT_OWNER
    _;
  }

  /**
   * @notice Will use the owner address of another parent contract
   * @param _newParent Address of the new owner
   */
  function changeOwnableParent(address _newParent) public onlyOwner {
    require(_newParent != address(0), "D3"); // DelegatedOwnable#changeOwnableParent: INVALID_ADDRESS
    ownableParent = _newParent;
    emit ParentOwnerChanged(ownableParent, _newParent);
  }

  /**
   * @notice Returns the address of the owner.
   */
  function getOwner() public view returns (address) {
    return IOwnable(ownableParent).getOwner();
  }
}

File 5 of 18 : IFnbswapExchange20.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.7.4;
pragma experimental ABIEncoderV2;

interface IFnbswapExchange20 {

  /***********************************|
  |               Events              |
  |__________________________________*/

  event TokensPurchase(
    address indexed buyer,
    address indexed recipient,
    uint256[] tokensBoughtIds,
    uint256[] tokensBoughtAmounts,
    uint256[] currencySoldAmounts,
    address[] extraFeeRecipients,
    uint256[] extraFeeAmounts
  );

  event CurrencyPurchase(
    address indexed buyer,
    address indexed recipient,
    uint256[] tokensSoldIds,
    uint256[] tokensSoldAmounts,
    uint256[] currencyBoughtAmounts,
    address[] extraFeeRecipients,
    uint256[] extraFeeAmounts
  );

  event LiquidityAdded(
    address indexed provider,
    uint256[] tokenIds,
    uint256[] tokenAmounts,
    uint256[] currencyAmounts
  );

  struct LiquidityRemovedEventObj {
    uint256 currencyAmount;
    uint256 soldTokenNumerator;
    uint256 boughtCurrencyNumerator;
    uint256 totalSupply;
  }

  event LiquidityRemoved(
    address indexed provider,
    uint256[] tokenIds,
    uint256[] tokenAmounts,
    LiquidityRemovedEventObj[] details
  );

  event RoyaltyChanged(
    address indexed royaltyRecipient,
    uint256 royaltyFee
  );

  struct SellTokensObj {
    address recipient;            // Who receives the currency
    uint256 minCurrency;          // Total minimum number of currency  expected for all tokens sold
    address[] extraFeeRecipients; // Array of addresses that will receive extra fee
    uint256[] extraFeeAmounts;    // Array of amounts of currency that will be sent as extra fee
    uint256 deadline;             // Timestamp after which the tx isn't valid anymore
  }

  struct AddLiquidityObj {
    uint256[] maxCurrency; // Maximum number of currency to deposit with tokens
    uint256 deadline;      // Timestamp after which the tx isn't valid anymore
  }

  struct RemoveLiquidityObj {
    uint256[] minCurrency; // Minimum number of currency to withdraw
    uint256[] minTokens;   // Minimum number of tokens to withdraw
    uint256 deadline;      // Timestamp after which the tx isn't valid anymore
  }


  /***********************************|
  |        Purchasing Functions       |
  |__________________________________*/
  
  /**
   * @notice Convert currency tokens to Tokens _id and transfers Tokens to recipient.
   * @dev User specifies MAXIMUM inputs (_maxCurrency) and EXACT outputs.
   * @dev Assumes that all trades will be successful, or revert the whole tx
   * @dev Exceeding currency tokens sent will be refunded to recipient
   * @dev Sorting IDs is mandatory for efficient way of preventing duplicated IDs (which would lead to exploit)
   * @param _tokenIds            Array of Tokens ID that are bought
   * @param _tokensBoughtAmounts Amount of Tokens id bought for each corresponding Token id in _tokenIds
   * @param _maxCurrency         Total maximum amount of currency tokens to spend for all Token ids
   * @param _deadline            Timestamp after which this transaction will be reverted
   * @param _recipient           The address that receives output Tokens and refund
   * @param _extraFeeRecipients  Array of addresses that will receive extra fee
   * @param _extraFeeAmounts     Array of amounts of currency that will be sent as extra fee
   * @return currencySold How much currency was actually sold.
   */
  function buyTokens(
    uint256[] memory _tokenIds,
    uint256[] memory _tokensBoughtAmounts,
    uint256 _maxCurrency,
    uint256 _deadline,
    address _recipient,
    address[] memory _extraFeeRecipients,
    uint256[] memory _extraFeeAmounts
  ) external returns (uint256[] memory);

  /***********************************|
  |         Royalties Functions       |
  |__________________________________*/

  /**
   * @notice Will send the royalties that _royaltyRecipient can claim, if any 
   * @dev Anyone can call this function such that payout could be distributed 
   *      regularly instead of being claimed. 
   * @param _royaltyRecipient Address that is able to claim royalties
   */
  function sendRoyalties(address _royaltyRecipient) external;

  /***********************************|
  |        OnReceive Functions        |
  |__________________________________*/

  /**
   * @notice Handle which method is being called on Token transfer
   * @dev `_data` must be encoded as follow: abi.encode(bytes4, MethodObj)
   *   where bytes4 argument is the MethodObj object signature passed as defined
   *   in the `Signatures for onReceive control logic` section above
   * @param _operator The address which called the `safeTransferFrom` function
   * @param _from     The address which previously owned the token
   * @param _id       The id of the token being transferred
   * @param _amount   The amount of tokens being transferred
   * @param _data     Method signature and corresponding encoded arguments for method to call on *this* contract
   * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
   */
  function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _amount, bytes calldata _data) external returns(bytes4);

  /**
   * @notice Handle which method is being called on transfer
   * @dev `_data` must be encoded as follow: abi.encode(bytes4, MethodObj)
   *   where bytes4 argument is the MethodObj object signature passed as defined
   *   in the `Signatures for onReceive control logic` section above
   * @param _from     The address which previously owned the Token
   * @param _ids      An array containing ids of each Token being transferred
   * @param _amounts  An array containing amounts of each Token being transferred
   * @param _data     Method signature and corresponding encoded arguments for method to call on *this* contract
   * @return bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")
   */
  function onERC1155BatchReceived(address, address _from, uint256[] calldata _ids, uint256[] calldata _amounts, bytes calldata _data) external returns(bytes4);


  /***********************************|
  |         Getter Functions          |
  |__________________________________*/

  /**
   * @dev Pricing function used for converting between currency token to Tokens.
   * @param _assetBoughtAmount  Amount of Tokens being bought.
   * @param _assetSoldReserve   Amount of currency tokens in exchange reserves.
   * @param _assetBoughtReserve Amount of Tokens (output type) in exchange reserves.
   * @return Amount of currency tokens to send to Niftyswap.
   */
  function getBuyPrice(uint256 _assetBoughtAmount, uint256 _assetSoldReserve, uint256 _assetBoughtReserve) external view returns (uint256);

  /**
   * @dev Pricing function used for converting Tokens to currency token (including royalty fee)
   * @param _tokenId            Id ot token being sold
   * @param _assetBoughtAmount  Amount of Tokens being bought.
   * @param _assetSoldReserve   Amount of currency tokens in exchange reserves.
   * @param _assetBoughtReserve Amount of Tokens (output type) in exchange reserves.
   * @return price Amount of currency tokens to send to Niftyswap.
   */
  function getBuyPriceWithRoyalty(uint256 _tokenId, uint256 _assetBoughtAmount, uint256 _assetSoldReserve, uint256 _assetBoughtReserve) external view returns (uint256 price);

  /**
   * @dev Pricing function used for converting Tokens to currency token.
   * @param _assetSoldAmount    Amount of Tokens being sold.
   * @param _assetSoldReserve   Amount of Tokens in exchange reserves.
   * @param _assetBoughtReserve Amount of currency tokens in exchange reserves.
   * @return Amount of currency tokens to receive from Niftyswap.
   */
  function getSellPrice(uint256 _assetSoldAmount,uint256 _assetSoldReserve, uint256 _assetBoughtReserve) external view returns (uint256);

  /**
   * @dev Pricing function used for converting Tokens to currency token (including royalty fee)
   * @param _tokenId            Id ot token being sold
   * @param _assetSoldAmount    Amount of Tokens being sold.
   * @param _assetSoldReserve   Amount of Tokens in exchange reserves.
   * @param _assetBoughtReserve Amount of currency tokens in exchange reserves.
   * @return price Amount of currency tokens to receive from Niftyswap.
   */
  function getSellPriceWithRoyalty(uint256 _tokenId, uint256 _assetSoldAmount, uint256 _assetSoldReserve, uint256 _assetBoughtReserve) external view returns (uint256 price);

  /**
   * @notice Get amount of currency in reserve for each Token _id in _ids
   * @param _ids Array of ID sto query currency reserve of
   * @return amount of currency in reserve for each Token _id
   */
  function getCurrencyReserves(uint256[] calldata _ids) external view returns (uint256[] memory);

  /**
   * @notice Return price for `currency => Token _id` trades with an exact token amount.
   * @param _ids          Array of ID of tokens bought.
   * @param _tokensBought Amount of Tokens bought.
   * @return Amount of currency needed to buy Tokens in _ids for amounts in _tokensBought
   */
  function getPrice_currencyToToken(uint256[] calldata _ids, uint256[] calldata _tokensBought) external view returns (uint256[] memory);

  /**
   * @notice Return price for `Token _id => currency` trades with an exact token amount.
   * @param _ids        Array of IDs  token sold.
   * @param _tokensSold Array of amount of each Token sold.
   * @return Amount of currency that can be bought for Tokens in _ids for amounts in _tokensSold
   */
  function getPrice_tokenToCurrency(uint256[] calldata _ids, uint256[] calldata _tokensSold) external view returns (uint256[] memory);

  /**
   * @notice Get total supply of liquidity tokens
   * @param _ids ID of the Tokens
   * @return The total supply of each liquidity token id provided in _ids
   */
  function getTotalSupply(uint256[] calldata _ids) external view returns (uint256[] memory);

  /**
   * @return Address of Token that is sold on this exchange.
   */
  function getTokenAddress() external view returns (address);

  /**
   * @return LP fee per 1000 units
   */
  function getLPFee() external view returns (uint256);

  /**
   * @return Address of the currency contract that is used as currency
   */
  function getCurrencyInfo() external view returns (address);

  /**
   * @return Address of factory that created this exchange.
   */
  function getFactoryAddress() external view returns (address);

  /**
   * @return Global royalty fee % if not supporting ERC-2981
   */
  function getGlobalRoyaltyFee() external view returns (uint256);  

  /**
   * @return Global royalty recipient if token not supporting ERC-2981
   */
  function getGlobalRoyaltyRecipient() external view returns (address);

  /**
   * @return Get amount of currency in royalty an address can claim
   * @param _royaltyRecipient Address to check the claimable royalties
   */
  function getRoyalties(address _royaltyRecipient) external view returns (uint256);

  function getRoyaltiesNumerator(address _royaltyRecipient) external view returns (uint256);
}

File 6 of 18 : IERC2981.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.7.4;
import "contracts/FnbswapExchange/erc-1155/contracts/interfaces/IERC165.sol";

/** 
 * @dev Interface for the NFT Royalty Standard
 */
interface IERC2981 is IERC165 {
  /** 
   * @notice Called with the sale price to determine how much royalty
   *         is owed and to whom.
   * @param _tokenId - the NFT asset queried for royalty information
   * @param _salePrice - the sale price of the NFT asset specified by _tokenId
   * @return receiver - address of who should be sent the royalty payment
   * @return royaltyAmount - the royalty payment amount for _salePrice
   */
  function royaltyInfo(
      uint256 _tokenId,
      uint256 _salePrice
  ) external view returns (
      address receiver,
      uint256 royaltyAmount
  );
}

File 7 of 18 : IERC1155TokenReceiver.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.7.4;

/**
 * @dev ERC-1155 interface for accepting safe transfers.
 */
interface IERC1155TokenReceiver {

  /**
   * @notice Handle the receipt of a single ERC1155 token type
   * @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeTransferFrom` after the balance has been updated
   * This function MAY throw to revert and reject the transfer
   * Return of other amount than the magic value MUST result in the transaction being reverted
   * Note: The token contract address is always the message sender
   * @param _operator  The address which called the `safeTransferFrom` function
   * @param _from      The address which previously owned the token
   * @param _id        The id of the token being transferred
   * @param _amount    The amount of tokens being transferred
   * @param _data      Additional data with no specified format
   * @return           `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
   */
  function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _amount, bytes calldata _data) external returns(bytes4);

  /**
   * @notice Handle the receipt of multiple ERC1155 token types
   * @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated
   * This function MAY throw to revert and reject the transfer
   * Return of other amount than the magic value WILL result in the transaction being reverted
   * Note: The token contract address is always the message sender
   * @param _operator  The address which called the `safeBatchTransferFrom` function
   * @param _from      The address which previously owned the token
   * @param _ids       An array containing ids of each token being transferred
   * @param _amounts   An array containing amounts of each token being transferred
   * @param _data      Additional data with no specified format
   * @return           `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
   */
  function onERC1155BatchReceived(address _operator, address _from, uint256[] calldata _ids, uint256[] calldata _amounts, bytes calldata _data) external returns(bytes4);
}

File 8 of 18 : IDelegatedERC1155Metadata.sol
pragma solidity ^0.7.4;

import "./IERC1155Metadata.sol";


interface IDelegatedERC1155Metadata {
  function metadataProvider() external view returns (IERC1155Metadata);
}

File 9 of 18 : IERC1155Metadata.sol
pragma solidity ^0.7.4;

/**
    Note: The ERC-165 identifier for this interface is 0x0e89341c.
*/
interface IERC1155Metadata {
    /**
        @notice A distinct Uniform Resource Identifier (URI) for a given token.
        @dev URIs are defined in RFC 3986.
        The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema".        
        @return URI string
    */
    function uri(uint256 _id) external view returns (string memory);
}

File 10 of 18 : IERC20.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.7.4;

/**
 * @title ERC20 interface
 * @dev see https://eips.ethereum.org/EIPS/eip-20
 */
interface IERC20 {
  function transfer(address to, uint256 value) external returns (bool);
  function approve(address spender, uint256 value) external returns (bool);
  function transferFrom(address from, address to, uint256 value) external returns (bool);
  function totalSupply() external view returns (uint256);
  function balanceOf(address who) external view returns (uint256);
  function allowance(address owner, address spender) external view returns (uint256);
  event Transfer(address indexed from, address indexed to, uint256 value);
  event Approval(address indexed owner, address indexed spender, uint256 value);
}

File 11 of 18 : IERC165.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.7.4;


/**
 * @title ERC165
 * @dev https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md
 */
interface IERC165 {

    /**
     * @notice Query if a contract implements an interface
     * @dev Interface identification is specified in ERC-165. This function
     * uses less than 30,000 gas
     * @param _interfaceId The interface identifier, as specified in ERC-165
     */
    function supportsInterface(bytes4 _interfaceId)
    external
    view
    returns (bool);
}

File 12 of 18 : IERC1155.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.7.4;
import './IERC165.sol';


interface IERC1155 is IERC165 {

  /****************************************|
  |                 Events                 |
  |_______________________________________*/

  /**
   * @dev Either TransferSingle or TransferBatch MUST emit when tokens are transferred, including zero amount transfers as well as minting or burning
   *   Operator MUST be msg.sender
   *   When minting/creating tokens, the `_from` field MUST be set to `0x0`
   *   When burning/destroying tokens, the `_to` field MUST be set to `0x0`
   *   The total amount transferred from address 0x0 minus the total amount transferred to 0x0 may be used by clients and exchanges to be added to the "circulating supply" for a given token ID
   *   To broadcast the existence of a token ID with no initial balance, the contract SHOULD emit the TransferSingle event from `0x0` to `0x0`, with the token creator as `_operator`, and a `_amount` of 0
   */
  event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _amount);

  /**
   * @dev Either TransferSingle or TransferBatch MUST emit when tokens are transferred, including zero amount transfers as well as minting or burning
   *   Operator MUST be msg.sender
   *   When minting/creating tokens, the `_from` field MUST be set to `0x0`
   *   When burning/destroying tokens, the `_to` field MUST be set to `0x0`
   *   The total amount transferred from address 0x0 minus the total amount transferred to 0x0 may be used by clients and exchanges to be added to the "circulating supply" for a given token ID
   *   To broadcast the existence of multiple token IDs with no initial balance, this SHOULD emit the TransferBatch event from `0x0` to `0x0`, with the token creator as `_operator`, and a `_amount` of 0
   */
  event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts);

  /**
   * @dev MUST emit when an approval is updated
   */
  event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);


  /****************************************|
  |                Functions               |
  |_______________________________________*/

  /**
    * @notice Transfers amount of an _id from the _from address to the _to address specified
    * @dev MUST emit TransferSingle event on success
    * Caller must be approved to manage the _from account's tokens (see isApprovedForAll)
    * MUST throw if `_to` is the zero address
    * MUST throw if balance of sender for token `_id` is lower than the `_amount` sent
    * MUST throw on any other error
    * When transfer is complete, this function MUST check if `_to` is a smart contract (code size > 0). If so, it MUST call `onERC1155Received` on `_to` and revert if the return amount is not `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
    * @param _from    Source address
    * @param _to      Target address
    * @param _id      ID of the token type
    * @param _amount  Transfered amount
    * @param _data    Additional data with no specified format, sent in call to `_to`
    */
  function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount, bytes calldata _data) external;

  /**
    * @notice Send multiple types of Tokens from the _from address to the _to address (with safety call)
    * @dev MUST emit TransferBatch event on success
    * Caller must be approved to manage the _from account's tokens (see isApprovedForAll)
    * MUST throw if `_to` is the zero address
    * MUST throw if length of `_ids` is not the same as length of `_amounts`
    * MUST throw if any of the balance of sender for token `_ids` is lower than the respective `_amounts` sent
    * MUST throw on any other error
    * When transfer is complete, this function MUST check if `_to` is a smart contract (code size > 0). If so, it MUST call `onERC1155BatchReceived` on `_to` and revert if the return amount is not `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
    * Transfers and events MUST occur in the array order they were submitted (_ids[0] before _ids[1], etc)
    * @param _from     Source addresses
    * @param _to       Target addresses
    * @param _ids      IDs of each token type
    * @param _amounts  Transfer amounts per token type
    * @param _data     Additional data with no specified format, sent in call to `_to`
  */
  function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _amounts, bytes calldata _data) external;

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

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

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

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

File 13 of 18 : ERC1155MintBurn.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.7.4;
import "./ERC1155.sol";


/**
 * @dev Multi-Fungible Tokens with minting and burning methods. These methods assume
 *      a parent contract to be executed as they are `internal` functions
 */
contract ERC1155MintBurn is ERC1155 {
  using SafeMath for uint256;

  /****************************************|
  |            Minting Functions           |
  |_______________________________________*/

  /**
   * @notice Mint _amount of tokens of a given id
   * @param _to      The address to mint tokens to
   * @param _id      Token id to mint
   * @param _amount  The amount to be minted
   * @param _data    Data to pass if receiver is contract
   */
  function _mint(address _to, uint256 _id, uint256 _amount, bytes memory _data)
    internal
  {
    // Add _amount
    balances[_to][_id] = balances[_to][_id].add(_amount);

    // Emit event
    emit TransferSingle(msg.sender, address(0x0), _to, _id, _amount);

    // Calling onReceive method if recipient is contract
    _callonERC1155Received(address(0x0), _to, _id, _amount, gasleft(), _data);
  }

  /**
   * @notice Mint tokens for each ids in _ids
   * @param _to       The address to mint tokens to
   * @param _ids      Array of ids to mint
   * @param _amounts  Array of amount of tokens to mint per id
   * @param _data    Data to pass if receiver is contract
   */
  function _batchMint(address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data)
    internal
  {
    require(_ids.length == _amounts.length, "ERC1155MintBurn#batchMint: INVALID_ARRAYS_LENGTH");

    // Number of mints to execute
    uint256 nMint = _ids.length;

     // Executing all minting
    for (uint256 i = 0; i < nMint; i++) {
      // Update storage balance
      balances[_to][_ids[i]] = balances[_to][_ids[i]].add(_amounts[i]);
    }

    // Emit batch mint event
    emit TransferBatch(msg.sender, address(0x0), _to, _ids, _amounts);

    // Calling onReceive method if recipient is contract
    _callonERC1155BatchReceived(address(0x0), _to, _ids, _amounts, gasleft(), _data);
  }


  /****************************************|
  |            Burning Functions           |
  |_______________________________________*/

  /**
   * @notice Burn _amount of tokens of a given token id
   * @param _from    The address to burn tokens from
   * @param _id      Token id to burn
   * @param _amount  The amount to be burned
   */
  function _burn(address _from, uint256 _id, uint256 _amount)
    internal
  {
    //Substract _amount
    balances[_from][_id] = balances[_from][_id].sub(_amount);

    // Emit event
    emit TransferSingle(msg.sender, _from, address(0x0), _id, _amount);
  }

  /**
   * @notice Burn tokens of given token id for each (_ids[i], _amounts[i]) pair
   * @param _from     The address to burn tokens from
   * @param _ids      Array of token ids to burn
   * @param _amounts  Array of the amount to be burned
   */
  function _batchBurn(address _from, uint256[] memory _ids, uint256[] memory _amounts)
    internal
  {
    // Number of mints to execute
    uint256 nBurn = _ids.length;
    require(nBurn == _amounts.length, "ERC1155MintBurn#batchBurn: INVALID_ARRAYS_LENGTH");

    // Executing all minting
    for (uint256 i = 0; i < nBurn; i++) {
      // Update storage balance
      balances[_from][_ids[i]] = balances[_from][_ids[i]].sub(_amounts[i]);
    }

    // Emit batch mint event
    emit TransferBatch(msg.sender, _from, address(0x0), _ids, _amounts);
  }
}

File 14 of 18 : IOwnable.sol
pragma solidity 0.7.4;
pragma experimental ABIEncoderV2;

interface IOwnable {
  /**
   * @notice Transfers the ownership of the contract to new address
   * @param _newOwner Address of the new owner
   */
  function transferOwnership(address _newOwner) external;

  /**
   * @notice Returns the address of the owner.
   */
  function getOwner() external view returns (address);
}

File 15 of 18 : ERC1155.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.7.4;

import "../../utils/SafeMath.sol";
import "../../interfaces/IERC1155TokenReceiver.sol";
import "../../interfaces/IERC1155.sol";
import "../../utils/Address.sol";
import "../../utils/ERC165.sol";


/**
 * @dev Implementation of Multi-Token Standard contract
 */
contract ERC1155 is IERC1155, ERC165 {
  using SafeMath for uint256;
  using Address for address;

  /***********************************|
  |        Variables and Events       |
  |__________________________________*/

  // onReceive function signatures
  bytes4 constant internal ERC1155_RECEIVED_VALUE = 0xf23a6e61;
  bytes4 constant internal ERC1155_BATCH_RECEIVED_VALUE = 0xbc197c81;

  // Objects balances
  mapping (address => mapping(uint256 => uint256)) internal balances;

  // Operator Functions
  mapping (address => mapping(address => bool)) internal operators;


  /***********************************|
  |     Public Transfer Functions     |
  |__________________________________*/

  /**
   * @notice Transfers amount amount of an _id from the _from address to the _to address specified
   * @param _from    Source address
   * @param _to      Target address
   * @param _id      ID of the token type
   * @param _amount  Transfered amount
   * @param _data    Additional data with no specified format, sent in call to `_to`
   */
  function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount, bytes memory _data)
    public override
  {
    require((msg.sender == _from) || isApprovedForAll(_from, msg.sender), "ERC1155#safeTransferFrom: INVALID_OPERATOR");
    require(_to != address(0),"ERC1155#safeTransferFrom: INVALID_RECIPIENT");
    // require(_amount <= balances[_from][_id]) is not necessary since checked with safemath operations

    _safeTransferFrom(_from, _to, _id, _amount);
    _callonERC1155Received(_from, _to, _id, _amount, gasleft(), _data);
  }

  /**
   * @notice Send multiple types of Tokens from the _from address to the _to address (with safety call)
   * @param _from     Source addresses
   * @param _to       Target addresses
   * @param _ids      IDs of each token type
   * @param _amounts  Transfer amounts per token type
   * @param _data     Additional data with no specified format, sent in call to `_to`
   */
  function safeBatchTransferFrom(address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data)
    public override
  {
    // Requirements
    require((msg.sender == _from) || isApprovedForAll(_from, msg.sender), "ERC1155#safeBatchTransferFrom: INVALID_OPERATOR");
    require(_to != address(0), "ERC1155#safeBatchTransferFrom: INVALID_RECIPIENT");

    _safeBatchTransferFrom(_from, _to, _ids, _amounts);
    _callonERC1155BatchReceived(_from, _to, _ids, _amounts, gasleft(), _data);
  }


  /***********************************|
  |    Internal Transfer Functions    |
  |__________________________________*/

  /**
   * @notice Transfers amount amount of an _id from the _from address to the _to address specified
   * @param _from    Source address
   * @param _to      Target address
   * @param _id      ID of the token type
   * @param _amount  Transfered amount
   */
  function _safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount)
    internal
  {
    // Update balances
    balances[_from][_id] = balances[_from][_id].sub(_amount); // Subtract amount
    balances[_to][_id] = balances[_to][_id].add(_amount);     // Add amount

    // Emit event
    emit TransferSingle(msg.sender, _from, _to, _id, _amount);
  }

  /**
   * @notice Verifies if receiver is contract and if so, calls (_to).onERC1155Received(...)
   */
  function _callonERC1155Received(address _from, address _to, uint256 _id, uint256 _amount, uint256 _gasLimit, bytes memory _data)
    internal
  {
    // Check if recipient is contract
    if (_to.isContract()) {
      bytes4 retval = IERC1155TokenReceiver(_to).onERC1155Received{gas: _gasLimit}(msg.sender, _from, _id, _amount, _data);
      require(retval == ERC1155_RECEIVED_VALUE, "ERC1155#_callonERC1155Received: INVALID_ON_RECEIVE_MESSAGE");
    }
  }

  /**
   * @notice Send multiple types of Tokens from the _from address to the _to address (with safety call)
   * @param _from     Source addresses
   * @param _to       Target addresses
   * @param _ids      IDs of each token type
   * @param _amounts  Transfer amounts per token type
   */
  function _safeBatchTransferFrom(address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts)
    internal
  {
    require(_ids.length == _amounts.length, "ERC1155#_safeBatchTransferFrom: INVALID_ARRAYS_LENGTH");

    // Number of transfer to execute
    uint256 nTransfer = _ids.length;

    // Executing all transfers
    for (uint256 i = 0; i < nTransfer; i++) {
      // Update storage balance of previous bin
      balances[_from][_ids[i]] = balances[_from][_ids[i]].sub(_amounts[i]);
      balances[_to][_ids[i]] = balances[_to][_ids[i]].add(_amounts[i]);
    }

    // Emit event
    emit TransferBatch(msg.sender, _from, _to, _ids, _amounts);
  }

  /**
   * @notice Verifies if receiver is contract and if so, calls (_to).onERC1155BatchReceived(...)
   */
  function _callonERC1155BatchReceived(address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts, uint256 _gasLimit, bytes memory _data)
    internal
  {
    // Pass data if recipient is contract
    if (_to.isContract()) {
      bytes4 retval = IERC1155TokenReceiver(_to).onERC1155BatchReceived{gas: _gasLimit}(msg.sender, _from, _ids, _amounts, _data);
      require(retval == ERC1155_BATCH_RECEIVED_VALUE, "ERC1155#_callonERC1155BatchReceived: INVALID_ON_RECEIVE_MESSAGE");
    }
  }


  /***********************************|
  |         Operator Functions        |
  |__________________________________*/

  /**
   * @notice Enable or disable approval for a third party ("operator") to manage all of caller's tokens
   * @param _operator  Address to add to the set of authorized operators
   * @param _approved  True if the operator is approved, false to revoke approval
   */
  function setApprovalForAll(address _operator, bool _approved)
    external override
  {
    // Update operator status
    operators[msg.sender][_operator] = _approved;
    emit ApprovalForAll(msg.sender, _operator, _approved);
  }

  /**
   * @notice Queries the approval status of an operator for a given owner
   * @param _owner     The owner of the Tokens
   * @param _operator  Address of authorized operator
   * @return isOperator True if the operator is approved, false if not
   */
  function isApprovedForAll(address _owner, address _operator)
    public override view returns (bool isOperator)
  {
    return operators[_owner][_operator];
  }


  /***********************************|
  |         Balance Functions         |
  |__________________________________*/

  /**
   * @notice Get the balance of an account's Tokens
   * @param _owner  The address of the token holder
   * @param _id     ID of the Token
   * @return The _owner's balance of the Token type requested
   */
  function balanceOf(address _owner, uint256 _id)
    public override view returns (uint256)
  {
    return balances[_owner][_id];
  }

  /**
   * @notice Get the balance of multiple account/token pairs
   * @param _owners The addresses of the token holders
   * @param _ids    ID of the Tokens
   * @return        The _owner's balance of the Token types requested (i.e. balance for each (owner, id) pair)
   */
  function balanceOfBatch(address[] memory _owners, uint256[] memory _ids)
    public override view returns (uint256[] memory)
  {
    require(_owners.length == _ids.length, "ERC1155#balanceOfBatch: INVALID_ARRAY_LENGTH");

    // Variables
    uint256[] memory batchBalances = new uint256[](_owners.length);

    // Iterate over each owner and token ID
    for (uint256 i = 0; i < _owners.length; i++) {
      batchBalances[i] = balances[_owners[i]][_ids[i]];
    }

    return batchBalances;
  }


  /***********************************|
  |          ERC165 Functions         |
  |__________________________________*/

  /**
   * @notice Query if a contract implements an interface
   * @param _interfaceID  The interface identifier, as specified in ERC-165
   * @return `true` if the contract implements `_interfaceID` and
   */
  function supportsInterface(bytes4 _interfaceID) public override(ERC165, IERC165) virtual pure returns (bool) {
    if (_interfaceID == type(IERC1155).interfaceId) {
      return true;
    }
    return super.supportsInterface(_interfaceID);
  }
}

File 16 of 18 : Address.sol
pragma solidity 0.7.4;


/**
 * Utility library of inline functions on addresses
 */
library Address {

  // Default hash for EOA accounts returned by extcodehash
  bytes32 constant internal ACCOUNT_HASH = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;

  /**
   * Returns whether the target address is a contract
   * @dev This function will return false if invoked during the constructor of a contract.
   * @param _address address of the account to check
   * @return Whether the target address is a contract
   */
  function isContract(address _address) internal view returns (bool) {
    bytes32 codehash;

    // Currently there is no better way to check if there is a contract in an address
    // than to check the size of the code at that address or if it has a non-zero code hash or account hash
    assembly { codehash := extcodehash(_address) }
    return (codehash != 0x0 && codehash != ACCOUNT_HASH);
  }
}

File 17 of 18 : ERC165.sol
pragma solidity 0.7.4;
import "../interfaces/IERC165.sol";

abstract contract ERC165 is IERC165 {
  /**
   * @notice Query if a contract implements an interface
   * @param _interfaceID The interface identifier, as specified in ERC-165
   * @return `true` if the contract implements `_interfaceID`
   */
  function supportsInterface(bytes4 _interfaceID) virtual override public pure returns (bool) {
    return _interfaceID == this.supportsInterface.selector;
  }
}

File 18 of 18 : SafeMath.sol
pragma solidity 0.7.4;


/**
 * @title SafeMath
 * @dev Unsigned math operations with safety checks that revert on error
 */
library SafeMath {

  /**
   * @dev Multiplies two unsigned integers, reverts on overflow.
   */
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
    // benefit is lost if 'b' is also tested.
    // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
    if (a == 0) {
      return 0;
    }

    uint256 c = a * b;
    require(c / a == b, "SafeMath#mul: OVERFLOW");

    return c;
  }

  /**
   * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
   */
  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // Solidity only automatically asserts when dividing by 0
    require(b > 0, "SafeMath#div: DIVISION_BY_ZERO");
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold

    return c;
  }

  /**
   * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
   */
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b <= a, "SafeMath#sub: UNDERFLOW#!");
    uint256 c = a - b;

    return c;
  }

  /**
   * @dev Adds two unsigned integers, reverts on overflow.
   */
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    require(c >= a, "SafeMath#add: OVERFLOW");

    return c; 
  }

  /**
   * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
   * reverts when dividing by zero.
   */
  function mod(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b != 0, "SafeMath#mod: DIVISION_BY_ZERO");
    return a % b;
  }
}

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

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"_tokenAddr","type":"address"},{"internalType":"address","name":"_currencyAddr","type":"address"},{"internalType":"uint256","name":"_lpFee","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_owner","type":"address"},{"indexed":true,"internalType":"address","name":"_operator","type":"address"},{"indexed":false,"internalType":"bool","name":"_approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokensSoldIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"tokensSoldAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"currencyBoughtAmounts","type":"uint256[]"},{"indexed":false,"internalType":"address[]","name":"extraFeeRecipients","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"extraFeeAmounts","type":"uint256[]"}],"name":"CurrencyPurchase","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"provider","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"currencyAmounts","type":"uint256[]"}],"name":"LiquidityAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"provider","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenAmounts","type":"uint256[]"},{"components":[{"internalType":"uint256","name":"currencyAmount","type":"uint256"},{"internalType":"uint256","name":"soldTokenNumerator","type":"uint256"},{"internalType":"uint256","name":"boughtCurrencyNumerator","type":"uint256"},{"internalType":"uint256","name":"totalSupply","type":"uint256"}],"indexed":false,"internalType":"struct IFnbswapExchange20.LiquidityRemovedEventObj[]","name":"details","type":"tuple[]"}],"name":"LiquidityRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousParent","type":"address"},{"indexed":true,"internalType":"address","name":"newParent","type":"address"}],"name":"ParentOwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"royaltyRecipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"royaltyFee","type":"uint256"}],"name":"RoyaltyChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokensBoughtIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"tokensBoughtAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"currencySoldAmounts","type":"uint256[]"},{"indexed":false,"internalType":"address[]","name":"extraFeeRecipients","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"extraFeeAmounts","type":"uint256[]"}],"name":"TokensPurchase","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_operator","type":"address"},{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"_ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"_amounts","type":"uint256[]"}],"name":"TransferBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_operator","type":"address"},{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"uint256","name":"_id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"TransferSingle","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"uint256","name":"_id","type":"uint256"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_owners","type":"address[]"},{"internalType":"uint256[]","name":"_ids","type":"uint256[]"}],"name":"balanceOfBatch","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_tokensBoughtAmounts","type":"uint256[]"},{"internalType":"uint256","name":"_maxCurrency","type":"uint256"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"address[]","name":"_extraFeeRecipients","type":"address[]"},{"internalType":"uint256[]","name":"_extraFeeAmounts","type":"uint256[]"}],"name":"buyTokens","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newParent","type":"address"}],"name":"changeOwnableParent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_assetBoughtAmount","type":"uint256"},{"internalType":"uint256","name":"_assetSoldReserve","type":"uint256"},{"internalType":"uint256","name":"_assetBoughtReserve","type":"uint256"}],"name":"getBuyPrice","outputs":[{"internalType":"uint256","name":"price","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_assetBoughtAmount","type":"uint256"},{"internalType":"uint256","name":"_assetSoldReserve","type":"uint256"},{"internalType":"uint256","name":"_assetBoughtReserve","type":"uint256"}],"name":"getBuyPriceWithRoyalty","outputs":[{"internalType":"uint256","name":"price","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrencyInfo","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_ids","type":"uint256[]"}],"name":"getCurrencyReserves","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFactoryAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getGlobalRoyaltyFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getGlobalRoyaltyRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLPFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_ids","type":"uint256[]"},{"internalType":"uint256[]","name":"_tokensBought","type":"uint256[]"}],"name":"getPrice_currencyToToken","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_ids","type":"uint256[]"},{"internalType":"uint256[]","name":"_tokensSold","type":"uint256[]"}],"name":"getPrice_tokenToCurrency","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_royaltyRecipient","type":"address"}],"name":"getRoyalties","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_royaltyRecipient","type":"address"}],"name":"getRoyaltiesNumerator","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_cost","type":"uint256"}],"name":"getRoyaltyInfo","outputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"royalty","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_assetSoldAmount","type":"uint256"},{"internalType":"uint256","name":"_assetSoldReserve","type":"uint256"},{"internalType":"uint256","name":"_assetBoughtReserve","type":"uint256"}],"name":"getSellPrice","outputs":[{"internalType":"uint256","name":"price","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_assetSoldAmount","type":"uint256"},{"internalType":"uint256","name":"_assetSoldReserve","type":"uint256"},{"internalType":"uint256","name":"_assetBoughtReserve","type":"uint256"}],"name":"getSellPriceWithRoyalty","outputs":[{"internalType":"uint256","name":"price","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTokenAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_ids","type":"uint256[]"}],"name":"getTotalSupply","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"isOperator","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"_from","type":"address"},{"internalType":"uint256[]","name":"_ids","type":"uint256[]"},{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"},{"internalType":"address","name":"_from","type":"address"},{"internalType":"uint256","name":"_id","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256[]","name":"_ids","type":"uint256[]"},{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeBatchTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_id","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_royaltyRecipient","type":"address"}],"name":"sendRoyalties","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"},{"internalType":"bool","name":"_approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_fee","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"setRoyaltyInfo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"test","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_id","type":"uint256"}],"name":"uri","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}]

6101206040526009805460ff191660011790553480156200001f57600080fd5b50604051620054533803806200545383398101604081905262000042916200029b565b336001600081905550806001600160a01b031663893d20e86040518163ffffffff1660e01b815260040160206040518083038186803b1580156200008557600080fd5b505afa925050508015620000b8575060408051601f3d908101601f19168201909252620000b59181019062000277565b60015b620000e05760405162461bcd60e51b8152600401620000d79062000352565b60405180910390fd5b50600380546001600160a01b0319166001600160a01b0383169081179091556040516000907ffca4bc097843727c93a8ab8d241a38481cccf8ec96dda1a342574712cfe8eb40908290a3506001600160a01b038316158015906200014c57506001600160a01b03821615155b6200016b5760405162461bcd60e51b8152600401620000d79062000332565b6103e8811115620001905760405162461bcd60e51b8152600401620000d79062000312565b33606090811b60c0526001600160601b031984821b81166080529083901b1660a0526103e881900360e0526040516301ffc9a760e01b81526001600160a01b038416906301ffc9a790620001f09063152a902d60e11b90600401620002fd565b60206040518083038186803b1580156200020957600080fd5b505afa9250505080156200023c575060408051601f3d908101601f191682019092526200023991810190620002db565b60015b620002475762000251565b151560f81b610100525b50505062000370565b80516001600160a01b03811681146200027257600080fd5b919050565b60006020828403121562000289578081fd5b62000294826200025a565b9392505050565b600080600060608486031215620002b0578182fd5b620002bb846200025a565b9250620002cb602085016200025a565b9150604084015190509250925092565b600060208284031215620002ed578081fd5b8151801515811462000294578182fd5b6001600160e01b031991909116815260200190565b60208082526006908201526527229918119960d11b604082015260600190565b6020808252600690820152654e453230233160d01b604082015260600190565b602080825260049082015263444f233160e01b604082015260600190565b60805160601c60a05160601c60c05160601c60e0516101005160f81c61503362000420600039806114ba5250806108a35280610a555280611a4852508061055b5280610e655250806108e65280610e0452806112ce52806116505280612453528061282d5280612c31528061311152508061066b5280610b0e5280610f03528061106e52806111bc528061139152806114f55280612c6e5280613096528061344a528061361d52506150336000f3fe608060405234801561001057600080fd5b50600436106101f95760003560e01c8063893d20e81161011a578063be571468116100ad578063e985e9c51161007c578063e985e9c514610448578063f23a6e611461045b578063f242432a1461046e578063f8a8fd6d14610481578063fca16c3b14610489576101f9565b8063be571468146103f9578063c7ed115e1461040c578063d93e8aaa1461042d578063e523d3fc14610440576101f9565b8063a9c2e36c116100e9578063a9c2e36c146103ab578063aeaad208146103b3578063b5de3d14146103c6578063bc197c81146103d9576101f9565b8063893d20e81461036a578063a22cb46514610372578063a631387514610385578063a7380f6e14610398576101f9565b80632eb2c2d6116101925780634e1273f4116101615780634e1273f41461031e5780636ee8e13414610331578063863ed3001461034457806389382ca014610357576101f9565b80632eb2c2d6146102e65780633750a8bd146102fb57806338b49e2d1461030357806346adf5ca14610316576101f9565b806310fe9ae8116101ce57806310fe9ae81461028b57806314556a56146102a0578063209b96c5146102b35780632bef5e38146102d3576101f9565b80628e09d81461021a578062fdd58e1461023857806301ffc9a71461024b5780630e89341c1461026b575b60405162461bcd60e51b8152600401610211906149dd565b60405180910390fd5b61022261049c565b60405161022f9190614cad565b60405180910390f35b610222610246366004613fbc565b6104a2565b61025e61025936600461423c565b6104cd565b60405161022f9190614849565b61027e61027936600461450f565b610557565b60405161022f9190614869565b610293610669565b60405161022f9190614652565b6102226102ae366004613e10565b61068d565b6102c66102c1366004614074565b6106ad565b60405161022f91906146fc565b6102c66102e1366004614074565b61074d565b6102f96102f4366004613e80565b6107e4565b005b6102226108a1565b610222610311366004613e10565b6108c9565b6102936108e4565b6102c661032c366004614014565b610908565b61022261033f366004614584565b610a20565b6102c66103523660046140b3565b610ab8565b6102f9610365366004613e10565b610c3d565b610293610ceb565b6102f9610380366004613f8f565b610d6d565b6102f9610393366004613e10565b610ddb565b6102226103a63660046145af565b610e2e565b610293610e63565b6102226103c13660046145af565b610e87565b6102f96103d436600461453f565b610eb1565b6103ec6103e7366004613e80565b611031565b60405161022f9190614854565b6102c66104073660046140b3565b61133b565b61041f61041a366004614563565b6114b5565b60405161022f9291906146be565b6102c661043b366004614155565b611608565b610293611831565b61025e610456366004613e48565b611840565b6103ec610469366004613f29565b61186e565b6102f961047c366004613f29565b61193b565b61025e6119f1565b610222610497366004614584565b6119fa565b60045490565b6001600160a01b03821660009081526001602090815260408083208484529091529020545b92915050565b60006001600160e01b031982166336372b0760e01b14806104fe57506001600160e01b031982166301ffc9a760e01b145b8061051957506001600160e01b03198216636cdb3d1360e11b145b8061053457506001600160e01b03198216630271189760e51b145b8061054f57506001600160e01b031982166303a24d0760e21b145b90505b919050565b60607f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d3d72d2a6040518163ffffffff1660e01b815260040160206040518083038186803b1580156105b257600080fd5b505afa1580156105c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105ea9190613e2c565b6001600160a01b0316630e89341c836040518263ffffffff1660e01b81526004016106159190614cad565b60006040518083038186803b15801561062d57600080fd5b505afa158015610641573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261054f919081019061449d565b7f000000000000000000000000000000000000000000000000000000000000000090565b6001600160a01b0316600090815260086020526040902054612710900490565b60608181816001600160401b03811180156106c757600080fd5b506040519080825280602002602001820160405280156106f1578160200160208202803683370190505b50905060005b82811015610744576007600087878481811061070f57fe5b9050602002013581526020019081526020016000205482828151811061073157fe5b60209081029190910101526001016106f7565b50949350505050565b60608181816001600160401b038111801561076757600080fd5b50604051908082528060200260200182016040528015610791578160200160208202803683370190505b50905060005b8281101561074457600660008787848181106107af57fe5b905060200201358152602001908152602001600020548282815181106107d157fe5b6020908102919091010152600101610797565b336001600160a01b038616148061080057506108008533611840565b61083b5760405162461bcd60e51b815260040180806020018281038252602f815260200180614ef9602f913960400191505060405180910390fd5b6001600160a01b0384166108805760405162461bcd60e51b8152600401808060200182810382526030815260200180614e6d6030913960400191505060405180910390fd5b61088c85858585611a7d565b61089a858585855a86611d18565b5050505050565b7f00000000000000000000000000000000000000000000000000000000000000006103e80390565b6001600160a01b031660009081526008602052604090205490565b7f000000000000000000000000000000000000000000000000000000000000000090565b6060815183511461094a5760405162461bcd60e51b815260040180806020018281038252602c815260200180614ecd602c913960400191505060405180910390fd5b606083516001600160401b038111801561096357600080fd5b5060405190808252806020026020018201604052801561098d578160200160208202803683370190505b50905060005b8451811015610a1857600160008683815181106109ac57fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060008583815181106109e257fe5b6020026020010151815260200190815260200160002054828281518110610a0557fe5b6020908102919091010152600101610993565b509392505050565b60008083118015610a315750600082115b610a4d5760405162461bcd60e51b8152600401610211906149fe565b6000610a79857f0000000000000000000000000000000000000000000000000000000000000000611f10565b90506000610a878285611f10565b90506000610aa183610a9b886103e8611f10565b90611f7f565b9050808281610aac57fe5b04979650505050505050565b60608381816001600160401b0381118015610ad257600080fd5b50604051908082528060200260200182016040528015610afc578160200160208202803683370190505b50905060005b82811015610c325760007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031662fdd58e308b8b86818110610b4757fe5b905060200201356040518363ffffffff1660e01b8152600401610b6b9291906146be565b60206040518083038186803b158015610b8357600080fd5b505afa158015610b97573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bbb9190614527565b9050610c12898984818110610bcc57fe5b90506020020135888885818110610bdf57fe5b9050602002013583600760008e8e89818110610bf757fe5b90506020020135815260200190815260200160002054610e2e565b838381518110610c1e57fe5b602090810291909101015250600101610b02565b509695505050505050565b610c45610ceb565b6001600160a01b0316336001600160a01b031614610c755760405162461bcd60e51b8152600401610211906148bd565b6001600160a01b038116610c9b5760405162461bcd60e51b8152600401610211906148fc565b600380546001600160a01b0319166001600160a01b0383811691821792839055604051919216907ffca4bc097843727c93a8ab8d241a38481cccf8ec96dda1a342574712cfe8eb4090600090a350565b60035460408051631127a41d60e31b815290516000926001600160a01b03169163893d20e8916004808301926020929190829003018186803b158015610d3057600080fd5b505afa158015610d44573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d689190613e2c565b905090565b3360008181526002602090815260408083206001600160a01b03871680855290835292819020805460ff1916861515908117909155815190815290519293927f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31929181900390910190a35050565b6001600160a01b0381166000908152600860205260409020805461271080820690925504610e2a7f00000000000000000000000000000000000000000000000000000000000000008383611fd2565b5050565b600080610e3c858585610a20565b90506000610e4a87836114b5565b9150610e589050828261211f565b979650505050505050565b7f000000000000000000000000000000000000000000000000000000000000000090565b600080610e958585856119fa565b90506000610ea387836114b5565b9150610e5890508282611f7f565b610eb9610ceb565b6001600160a01b0316336001600160a01b031614610ee95760405162461bcd60e51b8152600401610211906148bd565b6040516301ffc9a760e01b81526000906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906301ffc9a790610f3f9063152a902d60e11b90600401614854565b60206040518083038186803b158015610f5757600080fd5b505afa158015610f6b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f8f9190614220565b90508015610faf5760405162461bcd60e51b815260040161021190614ae2565b6109c4831115610fd15760405162461bcd60e51b815260040161021190614c8c565b6004839055600580546001600160a01b0319166001600160a01b0384169081179091556040517f02365318429bf1d603e8383b62068288a077545c5c9e709201d563b3f56ce2b390611024908690614cad565b60405180910390a2505050565b600080828060200190518101906110489190614258565b90506001600160e01b031981166356f3ce3d60e11b141561119857336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146110ab5760405162461bcd60e51b815260040161021190614b24565b6110b3613b36565b838060200190518101906110c791906143c7565b8051909250600091506001600160a01b0316156110e55781516110e7565b875b9050816060015151826040015151146111125760405162461bcd60e51b815260040161021190614ac1565b6060611133888885602001518660800151868860400151896060015161217c565b9050816001600160a01b0316896001600160a01b03167fb57378559821141c0e7ae964206b7523234d19e5783ade99b3d665eee495c9978a8a85886040015189606001516040516111889594939291906147dc565b60405180910390a3505050611328565b6001600160e01b031981166382da2b7360e01b141561123657336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146111f95760405162461bcd60e51b815260040161021190614aa0565b611201613b6e565b838060200190518101906112159190614274565b9050809150506112308787878460000151856020015161248c565b50611328565b6001600160e01b03198116635c0bf25960e01b14156112aa5733301461126e5760405162461bcd60e51b815260040161021190614c6b565b611276613b88565b8380602001905181019061128a919061430c565b9050809150506112308787878460000151856020015186604001516128c5565b6001600160e01b0319811663c8c323f960e01b141561131057336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461130b5760405162461bcd60e51b8152600401610211906149bc565b611328565b60405162461bcd60e51b815260040161021190614b03565b5063bc197c8160e01b9695505050505050565b60608381816001600160401b038111801561135557600080fd5b5060405190808252806020026020018201604052801561137f578160200160208202803683370190505b50905060005b82811015610c325760007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031662fdd58e308b8b868181106113ca57fe5b905060200201356040518363ffffffff1660e01b81526004016113ee9291906146be565b60206040518083038186803b15801561140657600080fd5b505afa15801561141a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061143e9190614527565b905061149589898481811061144f57fe5b9050602002013588888581811061146257fe5b90506020020135600760008d8d8881811061147957fe5b9050602002013581526020019081526020016000205484610e87565b8383815181106114a157fe5b602090810291909101015250600101611385565b6000807f0000000000000000000000000000000000000000000000000000000000000000156115e05760405163152a902d60e11b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690632a55205a9061152c9087908790600401614cb6565b604080518083038186803b15801561154357600080fd5b505afa925050508015611573575060408051601f3d908101601f1916820190925261157091810190613fe7565b60015b6115a9576005546004546001600160a01b039091169061271090611598908690611f10565b8161159f57fe5b0491509150611601565b60006127106115ba876109c4611f10565b816115c157fe5b049050828183116115d257826115d4565b815b94509450505050611601565b6005546004546001600160a01b039091169061271090611598908690611f10565b9250929050565b60604285101561162a5760405162461bcd60e51b8152600401610211906148db565b600088511161164b5760405162461bcd60e51b815260040161021190614a5e565b6116777f0000000000000000000000000000000000000000000000000000000000000000333089612d32565b60006001600160a01b0385161561168e5784611690565b335b84518451919250889181146116b75760405162461bcd60e51b815260040161021190614ba8565b60005b818110156117bd5760008682815181106116d057fe5b602002602001015111156117b5576117048682815181106116ed57fe5b60200260200101518461211f90919063ffffffff16565b925061177861173161271088848151811061171b57fe5b6020026020010151611f1090919063ffffffff16565b600860008a858151811061174157fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002054611f7f90919063ffffffff16565b6008600089848151811061178857fe5b60200260200101516001600160a01b03166001600160a01b03168152602001908152602001600020819055505b6001016116ba565b5060606117cd8c8c858c88612e87565b9050836001600160a01b0316336001600160a01b03167f28fb4c13c000d11e44ba9e45029f385ed02ffeae62dc5704e2c0706a4dab5ecb8e8e858c8c60405161181a9594939291906147dc565b60405180910390a39b9a5050505050505050505050565b6005546001600160a01b031690565b6001600160a01b03918216600090815260026020908152604080832093909416825291909152205460ff1690565b6040805160018082528183019092526000916060919060208083019080368337505060408051600180825281830190925292935060609291506020808301908036833701905050905085826000815181106118c557fe5b60200260200101818152505084816000815181106118df57fe5b6020026020010181815250506118f88888848488611031565b6001600160e01b03191663bc197c8160e01b146119275760405162461bcd60e51b815260040161021190614b45565b5063f23a6e6160e01b979650505050505050565b336001600160a01b038616148061195757506119578533611840565b6119925760405162461bcd60e51b815260040180806020018281038252602a815260200180614ddd602a913960400191505060405180910390fd5b6001600160a01b0384166119d75760405162461bcd60e51b815260040180806020018281038252602b815260200180614d92602b913960400191505060405180910390fd5b6119e385858585613148565b61089a858585855a86613229565b60095460ff1681565b60008083118015611a0b5750600082115b611a275760405162461bcd60e51b815260040161021190614be9565b6000611a3f6103e8611a398688611f10565b90611f10565b90506000611a717f0000000000000000000000000000000000000000000000000000000000000000611a39868961211f565b9050610c32828261339b565b8051825114611abd5760405162461bcd60e51b8152600401808060200182810382526035815260200180614e386035913960400191505060405180910390fd5b815160005b81811015611c3757611b39838281518110611ad957fe5b602002602001015160016000896001600160a01b03166001600160a01b031681526020019081526020016000206000878581518110611b1457fe5b602002602001015181526020019081526020016000205461211f90919063ffffffff16565b6001600160a01b03871660009081526001602052604081208651909190879085908110611b6257fe5b6020026020010151815260200190815260200160002081905550611beb838281518110611b8b57fe5b602002602001015160016000886001600160a01b03166001600160a01b031681526020019081526020016000206000878581518110611bc657fe5b6020026020010151815260200190815260200160002054611f7f90919063ffffffff16565b6001600160a01b03861660009081526001602052604081208651909190879085908110611c1457fe5b602090810291909101810151825281019190915260400160002055600101611ac2565b50836001600160a01b0316856001600160a01b0316336001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8686604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b83811015611cbd578181015183820152602001611ca5565b50505050905001838103825284818151815260200191508051906020019060200280838360005b83811015611cfc578181015183820152602001611ce4565b5050505090500194505050505060405180910390a45050505050565b611d2a856001600160a01b03166133e1565b15611f08576000856001600160a01b031663bc197c8184338a8989886040518763ffffffff1660e01b815260040180866001600160a01b03168152602001856001600160a01b03168152602001806020018060200180602001848103845287818151815260200191508051906020019060200280838360005b83811015611dbb578181015183820152602001611da3565b50505050905001848103835286818151815260200191508051906020019060200280838360005b83811015611dfa578181015183820152602001611de2565b50505050905001848103825285818151815260200191508051906020019080838360005b83811015611e36578181015183820152602001611e1e565b50505050905090810190601f168015611e635780820380516001836020036101000a031916815260200191505b5098505050505050505050602060405180830381600088803b158015611e8857600080fd5b5087f1158015611e9c573d6000803e3d6000fd5b50505050506040513d6020811015611eb357600080fd5b505190506001600160e01b0319811663bc197c8160e01b14611f065760405162461bcd60e51b815260040180806020018281038252603f815260200180614f58603f913960400191505060405180910390fd5b505b505050505050565b600082611f1f575060006104c7565b82820282848281611f2c57fe5b0414611f78576040805162461bcd60e51b8152602060048201526016602482015275536166654d617468236d756c3a204f564552464c4f5760501b604482015290519081900360640190fd5b9392505050565b600082820183811015611f78576040805162461bcd60e51b8152602060048201526016602482015275536166654d617468236164643a204f564552464c4f5760501b604482015290519081900360640190fd5b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663a9059cbb60e01b178152925182516000946060949389169392918291908083835b6020831061204f5780518252601f199092019160209182019101612030565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146120b1576040519150601f19603f3d011682016040523d82523d6000602084013e6120b6565b606091505b50915091508180156120e45750805115806120e457508080602001905160208110156120e157600080fd5b50515b61089a5760405162461bcd60e51b815260040180806020018281038252602d815260200180614fd1602d913960400191505060405180910390fd5b600082821115612176576040805162461bcd60e51b815260206004820152601960248201527f536166654d617468237375623a20554e444552464c4f57232100000000000000604482015290519081900360640190fd5b50900390565b6060600260005414156121c4576040805162461bcd60e51b815260206004820152601f6024820152600080516020614dbd833981519152604482015290519081900360640190fd5b60026000558751428610156121eb5760405162461bcd60e51b815260040161021190614a3e565b6000816001600160401b038111801561220357600080fd5b5060405190808252806020026020018201604052801561222d578160200160208202803683370190505b509250606061223b8b613418565b905060005b838110156123995760008c828151811061225657fe5b6020026020010151905060008c838151811061226e57fe5b60200260200101519050600084848151811061228657fe5b60200260200101519050600082116122b05760405162461bcd60e51b815260040161021190614bc9565b600083815260076020526040812054906122d4846122ce858261211f565b84610a20565b90506000806122e387846114b5565b909250905080156123365761231c6122fd82612710611f10565b6001600160a01b03841660009081526008602052604090205490611f7f565b6001600160a01b0383166000908152600860205260409020555b61234a612343848361211f565b8b90611f7f565b9950612356848461211f565b60008881526007602052604090205561236f838261211f565b8c898151811061237b57fe5b60209081029190910101525050600190950194506122409350505050565b5060005b855181101561242d5760008682815181106123b457fe5b60200260200101511115612425576123d18682815181106116ed57fe5b92506123e861173161271088848151811061171b57fe5b600860008984815181106123f857fe5b60200260200101516001600160a01b03166001600160a01b03168152602001908152602001600020819055505b60010161239d565b508882101561244e5760405162461bcd60e51b815260040161021190614a1e565b6124797f00000000000000000000000000000000000000000000000000000000000000008884611fd2565b5050506001600055979650505050505050565b600260005414156124d2576040805162461bcd60e51b815260206004820152601f6024820152600080516020614dbd833981519152604482015290519081900360640190fd5b6002600055428110156124f75760405162461bcd60e51b815260040161021190614c4a565b835160006060826001600160401b038111801561251357600080fd5b5060405190808252806020026020018201604052801561253d578160200160208202803683370190505b5090506060836001600160401b038111801561255857600080fd5b50604051908082528060200260200182016040528015612582578160200160208202803683370190505b509050606061259089613418565b905060005b858110156128275760008a82815181106125ab57fe5b6020026020010151905060008a83815181106125c357fe5b6020026020010151905060008a84815181106125db57fe5b6020026020010151116126005760405162461bcd60e51b815260040161021190614b66565b600081116126205760405162461bcd60e51b815260040161021190614c29565b600082815260066020526040902054801561277c57600083815260076020526040812054865190919087908790811061265557fe5b602002602001015190506000806126876126788588611f1090919063ffffffff16565b612682858961211f565b61339b565b91509150818f898151811061269857fe5b602002602001015110156126be5760405162461bcd60e51b815260040161021190614918565b6126c88483611f7f565b6000888152600760205260409020556126e18c83611f7f565b9b508361270486611a39846126f75760006126fa565b60015b869060ff1661211f565b8161270b57fe5b048b898151811061271857fe5b602002602001018181525050818a898151811061273157fe5b6020026020010181815250506127638b898151811061274c57fe5b602002602001015186611f7f90919063ffffffff16565b6000888152600660205260409020555061281c92505050565b60008b858151811061278a57fe5b602002602001015190506103e88110156127b65760405162461bcd60e51b81526004016102119061499b565b60008481526007602052604090208190556127d18982611f7f565b6000858152600660205260409020829055885190995081908990879081106127f557fe5b6020026020010181815250508087868151811061280e57fe5b602002602001018181525050505b505050600101612595565b506128547f00000000000000000000000000000000000000000000000000000000000000008b3087612d32565b61286f8a8a85604051806020016040528060008152506136b1565b896001600160a01b03167f403f9dc4582dae52d3eeb4a22d37540ffb13c32d964c92ec5ac0d3d5628da3168a8a856040516128ac93929190614799565b60405180910390a2505060016000555050505050505050565b6002600054141561290b576040805162461bcd60e51b815260206004820152601f6024820152600080516020614dbd833981519152604482015290519081900360640190fd5b600260005542811161292f5760405162461bcd60e51b815260040161021190614959565b845160006060826001600160401b038111801561294b57600080fd5b50604051908082528060200260200182016040528015612975578160200160208202803683370190505b5090506060836001600160401b038111801561299057600080fd5b506040519080825280602002602001820160405280156129ca57816020015b6129b7613ba9565b8152602001906001900390816129af5790505b50905060606129d88a613418565b905060005b85811015612c205760008b82815181106129f357fe5b6020026020010151905060008b8381518110612a0b57fe5b6020026020010151905060006006600084815260200190815260200160002054905060008111612a4d5760405162461bcd60e51b81526004016102119061497a565b600083815260076020526040812054865190919081908190899089908110612a7157fe5b60200260200101519050600080600080612a8e8b8b878b8d61387e565b6001600160a01b038216600090815260086020526040902054959c50939a50919750955093509150612ac09082611f7f565b6001600160a01b0383166000908152600860205260409020558d5184908f908e908110612ae957fe5b60200260200101516020018181525050828e8d81518110612b0657fe5b60200260200101516040018181525050888e8d81518110612b2357fe5b6020026020010151606001818152505050505050508e8781518110612b4457fe5b6020026020010151821015612b6b5760405162461bcd60e51b815260040161021190614b87565b8d8781518110612b7757fe5b6020026020010151811015612b9e5760405162461bcd60e51b81526004016102119061489c565b612ba8848661211f565b600087815260066020526040902055612bc1838361211f565b600087815260076020526040902055612bda8b83611f7f565b9a50808a8881518110612be957fe5b60200260200101818152505081898881518110612c0257fe5b6020908102919091010151525050600190940193506129dd92505050565b50612c2c308b8b613973565b612c577f00000000000000000000000000000000000000000000000000000000000000008c86611fd2565b604051631759616b60e11b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690632eb2c2d690612ca99030908f908f908990600401614666565b600060405180830381600087803b158015612cc357600080fd5b505af1158015612cd7573d6000803e3d6000fd5b505050508a6001600160a01b03167f3024a3223ce9e6a0b0324a52224694f329e7f092c1a7b74067b8f3cbfa1885718b8585604051612d189392919061470f565b60405180910390a250506001600055505050505050505050565b604080516001600160a01b0385811660248301528481166044830152606480830185905283518084039091018152608490920183526020820180516001600160e01b03166323b872dd60e01b17815292518251600094606094938a169392918291908083835b60208310612db75780518252601f199092019160209182019101612d98565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114612e19576040519150601f19603f3d011682016040523d82523d6000602084013e612e1e565b606091505b5091509150818015612e4c575080511580612e4c5750808060200190516020811015612e4957600080fd5b50515b611f085760405162461bcd60e51b8152600401808060200182810382526031815260200180614e076031913960400191505060405180910390fd5b606060026000541415612ecf576040805162461bcd60e51b815260206004820152601f6024820152600080516020614dbd833981519152604482015290519081900360640190fd5b600260005542831015612ef45760405162461bcd60e51b815260040161021190614c09565b855184816001600160401b0381118015612f0d57600080fd5b50604051908082528060200260200182016040528015612f37578160200160208202803683370190505b5092506060612f4589613418565b905060005b8381101561307e5760008a8281518110612f6057fe5b6020026020010151905060008a8381518110612f7857fe5b602002602001015190506000848481518110612f9057fe5b6020026020010151905060008211612fba5760405162461bcd60e51b815260040161021190614939565b60008381526007602052604081205490612fd58483856119fa565b9050600080612fe487846114b5565b9092509050801561301857612ffe6122fd82612710611f10565b6001600160a01b0383166000908152600860205260409020555b61302c816130268c8661211f565b9061211f565b99506130388382611f7f565b8c898151811061304457fe5b60209081029190910101526130598484611f7f565b60009788526007602052604090972096909655505060019094019350612f4a92505050565b50604051631759616b60e11b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690632eb2c2d6906130d190309089908e908e90600401614666565b600060405180830381600087803b1580156130eb57600080fd5b505af11580156130ff573d6000803e3d6000fd5b505050506000821115613137576131377f00000000000000000000000000000000000000000000000000000000000000008684611fd2565b505050600160005595945050505050565b6001600160a01b0384166000908152600160209081526040808320858452909152902054613176908261211f565b6001600160a01b0380861660009081526001602081815260408084208885528252808420959095559287168252825282812085825290915220546131ba9082611f7f565b6001600160a01b038085166000818152600160209081526040808320888452825291829020949094558051868152938401859052805191939288169233927fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62929181900390910190a450505050565b61323b856001600160a01b03166133e1565b15611f08576000856001600160a01b031663f23a6e6184338a8989886040518763ffffffff1660e01b815260040180866001600160a01b03168152602001856001600160a01b0316815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156132cd5781810151838201526020016132b5565b50505050905090810190601f1680156132fa5780820380516001836020036101000a031916815260200191505b509650505050505050602060405180830381600088803b15801561331d57600080fd5b5087f1158015613331573d6000803e3d6000fd5b50505050506040513d602081101561334857600080fd5b505190506001600160e01b0319811663f23a6e6160e01b14611f065760405162461bcd60e51b815260040180806020018281038252603a815260200180614f97603a913960400191505060405180910390fd5b6000808284816133a757fe5b06156133c9576133c260018486816133bb57fe5b0490611f7f565b60016133d6565b8284816133d257fe5b0460005b915091509250929050565b6000813f8015801590611f7857507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470141592915050565b8051606090600181141561351a57604080516001808252818301909252606091602080830190803683370190505090507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031662fdd58e308660008151811061348457fe5b60200260200101516040518363ffffffff1660e01b81526004016134a99291906146be565b60206040518083038186803b1580156134c157600080fd5b505afa1580156134d5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134f99190614527565b8160008151811061350657fe5b602090810291909101015291506105529050565b6060816001600160401b038111801561353257600080fd5b5060405190808252806020026020018201604052801561355c578160200160208202803683370190505b509050308160008151811061356d57fe5b6001600160a01b039092166020928302919091019091015260015b828110156136055784818151811061359c57fe5b60200260200101518560018303815181106135b357fe5b6020026020010151106135d85760405162461bcd60e51b815260040161021190614a7f565b308282815181106135e557fe5b6001600160a01b0390921660209283029190910190910152600101613588565b506040516313849cfd60e21b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690634e1273f49061365490849088906004016146d7565b60006040518083038186803b15801561366c57600080fd5b505afa158015613680573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526136a8919081019061411b565b92505050610552565b81518351146136f15760405162461bcd60e51b8152600401808060200182810382526030815260200180614f286030913960400191505060405180910390fd5b825160005b818110156137945761374884828151811061370d57fe5b602002602001015160016000896001600160a01b03166001600160a01b031681526020019081526020016000206000888581518110611bc657fe5b6001600160a01b0387166000908152600160205260408120875190919088908590811061377157fe5b6020908102919091018101518252810191909152604001600020556001016136f6565b50846001600160a01b031660006001600160a01b0316336001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561381b578181015183820152602001613803565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561385a578181015183820152602001613842565b5050505090500194505050505060405180910390a461089a60008686865a87611d18565b60008080808080806138908b8a611f10565b9050600061389e8c8c611f10565b90508881816138a957fe5b069550851561394c5760006138d48a611a398c85816138c457fe5b048f61211f90919063ffffffff16565b905060006138e88b611a398d87816138c457fe5b905080158015906138f857508115155b1561394957613908888383610a20565b96506139148f886114b5565b9096509450613923878661211f565b965061392f8488611f7f565b93508a61393e86612710611f10565b8161394557fe5b0494505b50505b88828161395557fe5b04975088818161396157fe5b04965050509550955095509550955095565b8151815181146139b45760405162461bcd60e51b8152600401808060200182810382526030815260200180614e9d6030913960400191505060405180910390fd5b60005b81811015613a5557613a098382815181106139ce57fe5b602002602001015160016000886001600160a01b03166001600160a01b031681526020019081526020016000206000878581518110611b1457fe5b6001600160a01b03861660009081526001602052604081208651909190879085908110613a3257fe5b6020908102919091018101518252810191909152604001600020556001016139b7565b5060006001600160a01b0316846001600160a01b0316336001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8686604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b83811015613adc578181015183820152602001613ac4565b50505050905001838103825284818151815260200191508051906020019060200280838360005b83811015613b1b578181015183820152602001613b03565b5050505090500194505050505060405180910390a450505050565b6040518060a0016040528060006001600160a01b03168152602001600081526020016060815260200160608152602001600081525090565b604051806040016040528060608152602001600081525090565b60405180606001604052806060815260200160608152602001600081525090565b6040518060800160405280600081526020016000815260200160008152602001600081525090565b803561055281614d55565b805161055281614d55565b600082601f830112613bf7578081fd5b8135613c0a613c0582614ce7565b614cc4565b818152915060208083019084810181840286018201871015613c2b57600080fd5b60005b84811015613c53578135613c4181614d55565b84529282019290820190600101613c2e565b505050505092915050565b600082601f830112613c6e578081fd5b8151613c7c613c0582614ce7565b818152915060208083019084810181840286018201871015613c9d57600080fd5b60005b84811015613c53578151613cb381614d55565b84529282019290820190600101613ca0565b60008083601f840112613cd6578182fd5b5081356001600160401b03811115613cec578182fd5b602083019150836020808302850101111561160157600080fd5b600082601f830112613d16578081fd5b8135613d24613c0582614ce7565b818152915060208083019084810181840286018201871015613d4557600080fd5b60005b84811015613c5357813584529282019290820190600101613d48565b600082601f830112613d74578081fd5b8151613d82613c0582614ce7565b818152915060208083019084810181840286018201871015613da357600080fd5b60005b84811015613c5357815184529282019290820190600101613da6565b600082601f830112613dd2578081fd5b8135613de0613c0582614d04565b9150808252836020828501011115613df757600080fd5b8060208401602084013760009082016020015292915050565b600060208284031215613e21578081fd5b8135611f7881614d55565b600060208284031215613e3d578081fd5b8151611f7881614d55565b60008060408385031215613e5a578081fd5b8235613e6581614d55565b91506020830135613e7581614d55565b809150509250929050565b600080600080600060a08688031215613e97578081fd5b8535613ea281614d55565b94506020860135613eb281614d55565b935060408601356001600160401b0380821115613ecd578283fd5b613ed989838a01613d06565b94506060880135915080821115613eee578283fd5b613efa89838a01613d06565b93506080880135915080821115613f0f578283fd5b50613f1c88828901613dc2565b9150509295509295909350565b600080600080600060a08688031215613f40578283fd5b8535613f4b81614d55565b94506020860135613f5b81614d55565b9350604086013592506060860135915060808601356001600160401b03811115613f83578182fd5b613f1c88828901613dc2565b60008060408385031215613fa1578182fd5b8235613fac81614d55565b91506020830135613e7581614d6d565b60008060408385031215613fce578182fd5b8235613fd981614d55565b946020939093013593505050565b60008060408385031215613ff9578182fd5b825161400481614d55565b6020939093015192949293505050565b60008060408385031215614026578182fd5b82356001600160401b038082111561403c578384fd5b61404886838701613be7565b9350602085013591508082111561405d578283fd5b5061406a85828601613d06565b9150509250929050565b60008060208385031215614086578182fd5b82356001600160401b0381111561409b578283fd5b6140a785828601613cc5565b90969095509350505050565b600080600080604085870312156140c8578182fd5b84356001600160401b03808211156140de578384fd5b6140ea88838901613cc5565b90965094506020870135915080821115614102578384fd5b5061410f87828801613cc5565b95989497509550505050565b60006020828403121561412c578081fd5b81516001600160401b03811115614141578182fd5b61414d84828501613d64565b949350505050565b600080600080600080600060e0888a03121561416f578485fd5b87356001600160401b0380821115614185578687fd5b6141918b838c01613d06565b985060208a01359150808211156141a6578687fd5b6141b28b838c01613d06565b975060408a0135965060608a013595506141ce60808b01613bd1565b945060a08a01359150808211156141e3578384fd5b6141ef8b838c01613be7565b935060c08a0135915080821115614204578283fd5b506142118a828b01613d06565b91505092959891949750929550565b600060208284031215614231578081fd5b8151611f7881614d6d565b60006020828403121561424d578081fd5b8135611f7881614d7b565b600060208284031215614269578081fd5b8151611f7881614d7b565b60008060408385031215614286578182fd5b825161429181614d7b565b60208401519092506001600160401b03808211156142ad578283fd5b90840190604082870312156142c0578283fd5b6040516040810181811083821117156142d557fe5b6040528251828111156142e6578485fd5b6142f288828601613d64565b825250602083015160208201528093505050509250929050565b6000806040838503121561431e578182fd5b825161432981614d7b565b60208401519092506001600160401b0380821115614345578283fd5b9084019060608287031215614358578283fd5b60405160608101818110838211171561436d57fe5b60405282518281111561437e578485fd5b61438a88828601613d64565b82525060208301518281111561439e578485fd5b6143aa88828601613d64565b602083015250604083015160408201528093505050509250929050565b600080604083850312156143d9578182fd5b82516143e481614d7b565b60208401519092506001600160401b0380821115614400578283fd5b9084019060a08287031215614413578283fd5b60405160a08101818110838211171561442857fe5b60405261443483613bdc565b815260208301516020820152604083015182811115614451578485fd5b61445d88828601613c5e565b604083015250606083015182811115614474578485fd5b61448088828601613d64565b606083015250608083015160808201528093505050509250929050565b6000602082840312156144ae578081fd5b81516001600160401b038111156144c3578182fd5b8201601f810184136144d3578182fd5b80516144e1613c0582614d04565b8181528560208385010111156144f5578384fd5b614506826020830160208601614d25565b95945050505050565b600060208284031215614520578081fd5b5035919050565b600060208284031215614538578081fd5b5051919050565b60008060408385031215614551578182fd5b823591506020830135613e7581614d55565b60008060408385031215614575578182fd5b50508035926020909101359150565b600080600060608486031215614598578081fd5b505081359360208301359350604090920135919050565b600080600080608085870312156145c4578182fd5b5050823594602084013594506040840135936060013592509050565b6000815180845260208085019450808401835b838110156146185781516001600160a01b0316875295820195908201906001016145f3565b509495945050505050565b6000815180845260208085019450808401835b8381101561461857815187529582019590820190600101614636565b6001600160a01b0391909116815260200190565b6001600160a01b0385811682528416602082015260a06040820181905260009061469290830185614623565b82810360608401526146a48185614623565b838103608090940193909352508152602001949350505050565b6001600160a01b03929092168252602082015260400190565b6000604082526146ea60408301856145e0565b82810360208401526145068185614623565b600060208252611f786020830184614623565b6000606080835261472281840187614623565b6020848203818601526147358288614623565b915060408583038187015282875180855283850191508389019450865b8181101561478957855180518452858101518685015284810151858501528701518784015294840194608090920191600101614752565b50909a9950505050505050505050565b6000606082526147ac6060830186614623565b82810360208401526147be8186614623565b905082810360408401526147d28185614623565b9695505050505050565b600060a082526147ef60a0830188614623565b82810360208401526148018188614623565b905082810360408401526148158187614623565b9050828103606084015261482981866145e0565b9050828103608084015261483d8185614623565b98975050505050505050565b901515815260200190565b6001600160e01b031991909116815260200190565b6000602082528251806020840152614888816040850160208701614d25565b601f01601f19169190910160400192915050565b60208082526007908201526609c8a64604662760cb1b604082015260600190565b6020808252600490820152632227919960e11b604082015260600190565b6020808252600790820152664e45323023313960c81b604082015260600190565b602080825260029082015261443360f01b604082015260600190565b6020808252600790820152664e45323023313360c81b604082015260600190565b60208082526006908201526513914c8c08cd60d21b604082015260600190565b6020808252600790820152664e45323023313560c81b604082015260600190565b6020808252600790820152662722991811989b60c91b604082015260600190565b60208082526007908201526613914c8c08cc4d60ca1b604082015260600190565b6020808252600790820152662722991811991b60c91b604082015260600190565b6020808252600790820152664e45323023323960c81b604082015260600190565b6020808252600690820152654e453230233960d01b604082015260600190565b60208082526006908201526509c8a646046760d31b604082015260600190565b60208082526006908201526527229918119b60d11b604082015260600190565b60208082526007908201526604e4532302332360cc1b604082015260600190565b6020808252600790820152662722991811999960c91b604082015260600190565b60208082526007908201526613914c8c08cc8d60ca1b604082015260600190565b6020808252600790820152664e45323023323360c81b604082015260600190565b60208082526007908201526604e4532302333360cc1b604082015260600190565b6020808252600790820152664e45323023323760c81b604082015260600190565b6020808252600790820152662722991811991960c91b604082015260600190565b60208082526007908201526609c8a64604664760cb1b604082015260600190565b6020808252600790820152664e45323023313160c81b604082015260600190565b6020808252600790820152664e45323023313760c81b604082015260600190565b6020808252600790820152664e45323023323160c81b604082015260600190565b6020808252600690820152654e453230233760d01b604082015260600190565b6020808252600690820152654e453230233560d01b604082015260600190565b6020808252600690820152654e453230233360d01b604082015260600190565b6020808252600790820152662722991811989960c91b604082015260600190565b60208082526007908201526604e4532302331360cc1b604082015260600190565b6020808252600790820152664e45323023323560c81b604082015260600190565b6020808252600790820152664e45323023333160c81b604082015260600190565b90815260200190565b918252602082015260400190565b6040518181016001600160401b0381118282101715614cdf57fe5b604052919050565b60006001600160401b03821115614cfa57fe5b5060209081020190565b60006001600160401b03821115614d1757fe5b50601f01601f191660200190565b60005b83811015614d40578181015183820152602001614d28565b83811115614d4f576000848401525b50505050565b6001600160a01b0381168114614d6a57600080fd5b50565b8015158114614d6a57600080fd5b6001600160e01b031981168114614d6a57600080fdfe4552433131353523736166655472616e7366657246726f6d3a20494e56414c49445f524543495049454e545265656e7472616e637947756172643a207265656e7472616e742063616c6c004552433131353523736166655472616e7366657246726f6d3a20494e56414c49445f4f50455241544f525472616e7366657248656c7065723a3a7472616e7366657246726f6d3a207472616e7366657246726f6d206661696c656445524331313535235f7361666542617463685472616e7366657246726f6d3a20494e56414c49445f4152524159535f4c454e47544845524331313535237361666542617463685472616e7366657246726f6d3a20494e56414c49445f524543495049454e54455243313135354d696e744275726e2362617463684275726e3a20494e56414c49445f4152524159535f4c454e475448455243313135352362616c616e63654f6642617463683a20494e56414c49445f41525241595f4c454e47544845524331313535237361666542617463685472616e7366657246726f6d3a20494e56414c49445f4f50455241544f52455243313135354d696e744275726e2362617463684d696e743a20494e56414c49445f4152524159535f4c454e47544845524331313535235f63616c6c6f6e45524331313535426174636852656365697665643a20494e56414c49445f4f4e5f524543454956455f4d45535341474545524331313535235f63616c6c6f6e4552433131353552656365697665643a20494e56414c49445f4f4e5f524543454956455f4d4553534147455472616e7366657248656c7065723a3a736166655472616e736665723a207472616e73666572206661696c6564a264697066735822122021a7a684b02c590bc84b35f4e93f50c715f65c1756c4f19edc30fac5bbc4a0f864736f6c63430007040033000000000000000000000000fbf1124c6de0a09f4be4d62c90d95fa7ab9eea2e000000000000000000000000b05c4cf6cf5989ca95273a2667515bd68ac8b029000000000000000000000000000000000000000000000000000000000000000a

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

000000000000000000000000fbf1124c6de0a09f4be4d62c90d95fa7ab9eea2e000000000000000000000000b05c4cf6cf5989ca95273a2667515bd68ac8b029000000000000000000000000000000000000000000000000000000000000000a

-----Decoded View---------------
Arg [0] : _tokenAddr (address): 0xfbf1124c6de0a09f4be4d62c90d95fa7ab9eea2e
Arg [1] : _currencyAddr (address): 0xb05c4cf6cf5989ca95273a2667515bd68ac8b029
Arg [2] : _lpFee (uint256): 10

-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 000000000000000000000000fbf1124c6de0a09f4be4d62c90d95fa7ab9eea2e
Arg [1] : 000000000000000000000000b05c4cf6cf5989ca95273a2667515bd68ac8b029
Arg [2] : 000000000000000000000000000000000000000000000000000000000000000a


Block Transaction Difficulty Gas Used Reward
Block Uncle Number Difficulty Gas Used Reward
Loading
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.