Contract

0x4F77c5D13004bd1af865e453c2f38C2DC05120da

Overview

S Balance

Sonic LogoSonic LogoSonic Logo0 S

S Value

-

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Set Active Givea...8726322024-12-20 12:41:109 days ago1734698470IN
0x4F77c5D1...DC05120da
0 S0.000057061.11
Edit Silver Fees8720752024-12-20 12:38:149 days ago1734698294IN
0x4F77c5D1...DC05120da
0 S0.000054911.11

Parent Transaction Hash Block From To
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
SilverFeesGiveaway

Compiler Version
v0.8.20+commit.a1b79de6

Optimization Enabled:
Yes with 10 runs

Other Settings:
paris EvmVersion
File 1 of 26 : SilverFeesGiveaway.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";

import "@chainlink/contracts/src/v0.8/vrf/VRFV2WrapperConsumerBase.sol";
import "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";

import 'contracts/SilverFees/SilverFees.sol';

// User's tickets
struct UserTickets {
	uint256 tickets;
	uint256 timestamp;
}

// User's tickets data
struct GiveawayTicketsData {
	mapping(address => UserTickets) userTickets;
	uint256 lastExecution;
}

// Weekly giveaway
struct GiveawayData {
	address[] giveawayParticipants;
	uint256 numberOfParticipants;
	bool participationEnded;
	uint256 giveawayEndTime;
}

// Giveaway settings
struct GiveawaySettings {
	bool isGiveawayActive;
	uint256 ticketPrice;
	uint256 giveawayTime;
	bool editedActive;
	uint256 editedTicketPrice;
	uint256 editedGiveawayTime;
}

// Chainlink VRF datas
struct RequestStatus {
	uint256 paid;
	bool fulfilled;
	uint256[] randomWords;
}

// Chainlink VRF inputs
struct RequestInput {
	uint32 callbackGasLimit;
	uint16 requestConfirmations;
	uint32 numWords;
}

/// @title SilverFeesGiveaway
/// @author github.com/SifexPro
/// @notice This contract take care of the weekly giveaway
contract SilverFeesGiveaway is Ownable2Step, VRFV2WrapperConsumerBase {
	SilverFees public silverFees;

	GiveawayData public giveawayData;
	GiveawayTicketsData public giveawayTicketsData;

	GiveawaySettings public giveawaySettings;

	RequestStatus public s_request;
	RequestInput public s_input;
    uint256 private requestId;

	event GiveawaySyncStarted(uint256 ticketPrice, uint256 giveawayEndTime);
	event GiveawaySynced();

	event GiveawayExecuted(bool isGiveawayActive, uint256 weeklyGiveawayAmount);
	event BuyTickets(address indexed account, uint256 amount, uint256 tickets);
	event DrawWinner(address indexed winner, uint256 amount, uint256 randomIndex);

	event RequestSent(uint256 requestId, uint32 numWords);
    event RequestFulfilled(uint256 requestId, uint256[] randomWords, uint256 payment);

	event EditedSilverFees(address silverFees);
	event EditedGiveawayTicketPrice(uint256 ticketPrice);
	event EditedGiveawayTime(uint256 giveawayTime);
	event EditedGiveawayActive(bool isGiveawayActive);
	event StartedGiveaway(uint256 ticketPrice, uint256 giveawayEndTime);
	event StoppedGiveaway();

	event WithdrawnToken(address indexed token, address to, uint256 amount);


	constructor(address _link, address _wrapper) Ownable(msg.sender) VRFV2WrapperConsumerBase(_link, _wrapper) {
		s_input.callbackGasLimit = 500000;
    	s_input.requestConfirmations = 7;
    	s_input.numWords = 1;

		giveawaySettings = GiveawaySettings(true, 1 ether, 1 weeks, false, 0, 0);
	}


	// Fees management

	/**
	 * @dev Check if the giveaway can be executed
	 * @notice Will return true : if the giveaway is over and s_request is fulfilled, or if the giveaway is not active
	 */
	function checkExecuteGiveaway() external onlySilverFees view returns (bool) {
		return ((giveawayData.participationEnded && s_request.fulfilled) || !giveawaySettings.isGiveawayActive);
	}

	/**
	 * @dev Execute the giveaway
	 * @param isSwapToWrappedToken If the giveaway is in wrapped native token
	 * @param weeklyGiveawayAmount Amount to giveaway
	 * @notice Will draw a winner if the giveaway is active, otherwise will transfer the amount to the contract
	 */
	function executeGiveaway(bool isSwapToWrappedToken, uint256 weeklyGiveawayAmount) external onlySilverFees {
		if (giveawaySettings.isGiveawayActive && s_request.fulfilled)
			drawWinner(isSwapToWrappedToken, weeklyGiveawayAmount);
		else if (!giveawaySettings.isGiveawayActive) {
			bool success;
			if (isSwapToWrappedToken)
				success = silverFees.wrappedToken().transferFrom(address(silverFees), address(this), weeklyGiveawayAmount);
			else
				success = silverFees.silverToken().transferFrom(address(silverFees), address(this), weeklyGiveawayAmount);
			require(success, "Transfer failed");
		}

		if (!giveawaySettings.isGiveawayActive || (giveawaySettings.isGiveawayActive && s_request.fulfilled))
			applyEditedSettings();

		emit GiveawayExecuted(giveawaySettings.isGiveawayActive, weeklyGiveawayAmount);
	}


	// Sync Fees Management 

	/**
	 * @dev Start the giveaway sync
	 */
	function startSyncGiveaway() public onlySilverFees {
		applyEditedSettings();

		giveawayData = GiveawayData(new address[](0), 0, false, silverFees.syncFeesLastSync() + giveawaySettings.giveawayTime);
		
		emit GiveawaySyncStarted(giveawaySettings.ticketPrice, giveawayData.giveawayEndTime);
	}

	/**
	 * @dev Sync the giveaway
	 */
	function syncGiveaway() external onlySilverFees {
		if (!giveawaySettings.isGiveawayActive) return;
		uint256 syncTime = silverFees.syncFeesTime();
		uint256 giveawayTime = giveawaySettings.giveawayTime;

		bool giveawayTimeCheck = block.timestamp + (syncTime / 2) >= giveawayData.giveawayEndTime;
		if (giveawayTimeCheck && giveawayData.numberOfParticipants > 0 && requestId == 0) { // Check if the giveaway is over and no request is sent
			requestRandomWords();
			giveawayData.participationEnded = true;
		} else if (giveawayTimeCheck && giveawayData.numberOfParticipants == 0) { // Check if the giveaway is over and no participants
			giveawaySettings.editedGiveawayTime = giveawayTime;
			applyEditedSettings();
		} else if (giveawayTimeCheck && giveawayData.participationEnded && block.timestamp > giveawayData.giveawayEndTime + (2 * syncTime)) { // Check if the giveaway is over and still not executed after 2 sync 
			clearRequest();
			giveawayData.participationEnded = false;

			giveawaySettings.editedGiveawayTime = giveawayTime;
			applyEditedSettings();
		}

		emit GiveawaySynced();
	}


	// Weekly giveaway

	/**
	 * @dev Buy tickets for the giveaway
	 * @param _amount Amount of tokens to buy tickets
	 * @param user Address of the user
	 * @notice Will buy tickets for the user if the giveaway is active and not ended
	 */
	function buyTickets(uint256 _amount, address user) external onlySilverFees {
		require(giveawaySettings.isGiveawayActive, "Giveaway not active");
		require(!giveawayData.participationEnded, "Ended");
		require(_amount >= giveawaySettings.ticketPrice, "Price too low");
		
		uint256 amount = _amount;
		uint256 tokenAmountIn = _amount / giveawaySettings.ticketPrice;
		if (_amount > giveawaySettings.ticketPrice) {
			amount = tokenAmountIn * giveawaySettings.ticketPrice;
		}

		uint256 balance = silverFees.silverToken().balanceOf(user);
		require(balance >= amount, "Not enough balance");

		uint256 allowance = silverFees.silverToken().allowance(user, address(silverFees));
		require(allowance >= amount, 'Not enough allowance');

		silverFees.buyTicketsBurn(user, amount);

		for (uint256 i = 0; i < tokenAmountIn; i++)
			giveawayData.giveawayParticipants.push(user);
		giveawayData.numberOfParticipants = giveawayData.giveawayParticipants.length;

		giveawayTicketsData.userTickets[user] = UserTickets(userTickets(user) + tokenAmountIn, block.timestamp);

		emit BuyTickets(user, amount, tokenAmountIn);
	}

	/**
	 * @dev Draw a winner for the giveaway
	 * @param isSwapToWrappedToken If the giveaway is in wrapped native token
	 * @param weeklyGiveawayAmount Amount to giveaway
	 * @notice Will draw a winner if the giveaway is active and ended
	 */
	function drawWinner(bool isSwapToWrappedToken, uint256 weeklyGiveawayAmount) private {
		require(giveawayData.participationEnded, "Too early");
		require(giveawayData.numberOfParticipants > 0, "0 participants");

		uint256 winnerIndex = getRandomIndex(giveawayData.numberOfParticipants);
		address winner = giveawayData.giveawayParticipants[winnerIndex];
		uint256 giveawayAmount = weeklyGiveawayAmount;
		
		bool success;
		if (isSwapToWrappedToken)
			success = silverFees.wrappedToken().transferFrom(address(silverFees), winner, giveawayAmount);
		else
			success = silverFees.silverToken().transferFrom(address(silverFees), winner, giveawayAmount);
		require(success, "Transfer failed");

		// Reset giveaway
		giveawayData.participationEnded = false;
		giveawayData.giveawayEndTime = silverFees.syncFeesLastSync() + giveawaySettings.giveawayTime;
		giveawayData.giveawayParticipants = new address[](0);
		giveawayData.numberOfParticipants = 0;
		clearRequest();

		giveawayTicketsData.lastExecution = block.timestamp;

		emit DrawWinner(winner, giveawayAmount, winnerIndex);
	}

	function getRandomIndex(uint256 lenght) private view returns (uint256 _randomWords) {
        uint256 randomWords = s_request.randomWords[0] % lenght;
		return (randomWords);
    }


	// User tickets

	/**
	 * @dev Get the user's tickets
	 * @param user Address of the user
	 * @return Number of tickets
	 */
	function userTickets(address user) public view returns (uint256) {
		if (giveawayTicketsData.userTickets[user].timestamp > giveawayTicketsData.lastExecution && giveawaySettings.isGiveawayActive)
			return giveawayTicketsData.userTickets[user].tickets;
		return 0;
	}


	// Internal functions

	function withdrawToken(address _token, address _to) public onlyOwner {
		IERC20 token = IERC20(_token);
		uint256 balance = token.balanceOf(address(this));

		SafeERC20.safeTransfer(token, _to, balance);

		emit WithdrawnToken(_token, _to, balance);
	}

	function applyEditedSettings() private {
		if (giveawaySettings.editedTicketPrice != 0)
		{
			giveawaySettings.ticketPrice = giveawaySettings.editedTicketPrice;
			giveawaySettings.editedTicketPrice = 0;
		}

		if (giveawaySettings.editedGiveawayTime != 0)
		{
			giveawayData.giveawayEndTime = silverFees.syncFeesLastSync() + giveawaySettings.editedGiveawayTime;
			giveawaySettings.giveawayTime = giveawaySettings.editedGiveawayTime;
			giveawaySettings.editedGiveawayTime = 0;
		}

		if (giveawaySettings.editedActive)
		{
			giveawaySettings.isGiveawayActive = !giveawaySettings.isGiveawayActive;
			giveawaySettings.editedActive = false;

			if (giveawaySettings.isGiveawayActive)
			{
				giveawayData.giveawayEndTime = silverFees.syncFeesLastSync() + giveawaySettings.giveawayTime;
				emit StartedGiveaway(giveawaySettings.ticketPrice, giveawayData.giveawayEndTime);
			}
			else 
				emit StoppedGiveaway();
		}
	}

	function editSilverFees(address _silverFees) public onlyOwner {
		silverFees = SilverFees(payable(_silverFees));
		
		emit EditedSilverFees(_silverFees);
	}

	function editGiveawayTicketPrice(uint256 _ticketPrice) public onlyOwner {
		giveawaySettings.editedTicketPrice = _ticketPrice;
		
		emit EditedGiveawayTicketPrice(_ticketPrice);
	}

	function editGiveawayTime(uint256 _giveawayTime) public onlyOwner {
		giveawaySettings.editedGiveawayTime = _giveawayTime;
		
		emit EditedGiveawayTime(_giveawayTime);
	}

	function setActiveGiveaway(bool _giveawayActive) public onlyOwner {
		if (giveawaySettings.isGiveawayActive == _giveawayActive)
			giveawaySettings.editedActive = false;
		else
			giveawaySettings.editedActive = true;

		emit EditedGiveawayActive(_giveawayActive);
	}


	// Chainlink VRF

	function requestRandomWords() private returns (uint256 _requestId) {
		require(requestId == 0, "already sent");
        requestId = requestRandomness(s_input.callbackGasLimit, s_input.requestConfirmations, s_input.numWords);
        s_request = RequestStatus({
            paid: VRF_V2_WRAPPER.calculateRequestPrice(s_input.callbackGasLimit),
            randomWords: new uint256[](0),
            fulfilled: false
        });
        emit RequestSent(requestId, s_input.numWords);
        return requestId;
    }

    function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal override {
        require(s_request.paid > 0, "request not found");
		require(_requestId == requestId, "request id mismatch");
        s_request.fulfilled = true;
        s_request.randomWords = _randomWords;
        emit RequestFulfilled(_requestId, _randomWords, s_request.paid);
    }

	function clearRequest() private {
		requestId = 0;
		s_request = RequestStatus({
			paid: 0,
			randomWords: new uint256[](0),
			fulfilled: false
		});
	}


	// Modifiers 

	modifier onlySilverFees() {
		require(msg.sender == address(silverFees), 'Only SilverFees');
		_;
	}
}

File 2 of 26 : ConfirmedOwner.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

/// @title The ConfirmedOwner contract
/// @notice A contract with helpers for basic contract ownership.
contract ConfirmedOwner is ConfirmedOwnerWithProposal {
  constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
}

File 3 of 26 : ConfirmedOwnerWithProposal.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IOwnable} from "../interfaces/IOwnable.sol";

/// @title The ConfirmedOwner contract
/// @notice A contract with helpers for basic contract ownership.
contract ConfirmedOwnerWithProposal is IOwnable {
  address private s_owner;
  address private s_pendingOwner;

  event OwnershipTransferRequested(address indexed from, address indexed to);
  event OwnershipTransferred(address indexed from, address indexed to);

  constructor(address newOwner, address pendingOwner) {
    // solhint-disable-next-line gas-custom-errors
    require(newOwner != address(0), "Cannot set owner to zero");

    s_owner = newOwner;
    if (pendingOwner != address(0)) {
      _transferOwnership(pendingOwner);
    }
  }

  /// @notice Allows an owner to begin transferring ownership to a new address.
  function transferOwnership(address to) public override onlyOwner {
    _transferOwnership(to);
  }

  /// @notice Allows an ownership transfer to be completed by the recipient.
  function acceptOwnership() external override {
    // solhint-disable-next-line gas-custom-errors
    require(msg.sender == s_pendingOwner, "Must be proposed owner");

    address oldOwner = s_owner;
    s_owner = msg.sender;
    s_pendingOwner = address(0);

    emit OwnershipTransferred(oldOwner, msg.sender);
  }

  /// @notice Get the current owner
  function owner() public view override returns (address) {
    return s_owner;
  }

  /// @notice validate, transfer ownership, and emit relevant events
  function _transferOwnership(address to) private {
    // solhint-disable-next-line gas-custom-errors
    require(to != msg.sender, "Cannot transfer to self");

    s_pendingOwner = to;

    emit OwnershipTransferRequested(s_owner, to);
  }

  /// @notice validate access
  function _validateOwnership() internal view {
    // solhint-disable-next-line gas-custom-errors
    require(msg.sender == s_owner, "Only callable by owner");
  }

  /// @notice Reverts if called by anyone other than the contract owner.
  modifier onlyOwner() {
    _validateOwnership();
    _;
  }
}

File 4 of 26 : IOwnable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IOwnable {
  function owner() external returns (address);

  function transferOwnership(address recipient) external;

  function acceptOwnership() external;
}

File 5 of 26 : LinkTokenInterface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// solhint-disable-next-line interface-starts-with-i
interface LinkTokenInterface {
  function allowance(address owner, address spender) external view returns (uint256 remaining);

  function approve(address spender, uint256 value) external returns (bool success);

  function balanceOf(address owner) external view returns (uint256 balance);

  function decimals() external view returns (uint8 decimalPlaces);

  function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);

  function increaseApproval(address spender, uint256 subtractedValue) external;

  function name() external view returns (string memory tokenName);

  function symbol() external view returns (string memory tokenSymbol);

  function totalSupply() external view returns (uint256 totalTokensIssued);

  function transfer(address to, uint256 value) external returns (bool success);

  function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool success);

  function transferFrom(address from, address to, uint256 value) external returns (bool success);
}

File 6 of 26 : VRFV2WrapperInterface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// solhint-disable-next-line interface-starts-with-i
interface VRFV2WrapperInterface {
  /**
   * @return the request ID of the most recent VRF V2 request made by this wrapper. This should only
   * be relied option within the same transaction that the request was made.
   */
  function lastRequestId() external view returns (uint256);

  /**
   * @notice Calculates the price of a VRF request with the given callbackGasLimit at the current
   * @notice block.
   *
   * @dev This function relies on the transaction gas price which is not automatically set during
   * @dev simulation. To estimate the price at a specific gas price, use the estimatePrice function.
   *
   * @param _callbackGasLimit is the gas limit used to estimate the price.
   */
  function calculateRequestPrice(uint32 _callbackGasLimit) external view returns (uint256);

  /**
   * @notice Estimates the price of a VRF request with a specific gas limit and gas price.
   *
   * @dev This is a convenience function that can be called in simulation to better understand
   * @dev pricing.
   *
   * @param _callbackGasLimit is the gas limit used to estimate the price.
   * @param _requestGasPriceWei is the gas price in wei used for the estimation.
   */
  function estimateRequestPrice(uint32 _callbackGasLimit, uint256 _requestGasPriceWei) external view returns (uint256);
}

File 7 of 26 : VRFV2WrapperConsumerBase.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {LinkTokenInterface} from "../shared/interfaces/LinkTokenInterface.sol";
import {VRFV2WrapperInterface} from "./interfaces/VRFV2WrapperInterface.sol";

/** *******************************************************************************
 * @notice Interface for contracts using VRF randomness through the VRF V2 wrapper
 * ********************************************************************************
 * @dev PURPOSE
 *
 * @dev Create VRF V2 requests without the need for subscription management. Rather than creating
 * @dev and funding a VRF V2 subscription, a user can use this wrapper to create one off requests,
 * @dev paying up front rather than at fulfillment.
 *
 * @dev Since the price is determined using the gas price of the request transaction rather than
 * @dev the fulfillment transaction, the wrapper charges an additional premium on callback gas
 * @dev usage, in addition to some extra overhead costs associated with the VRFV2Wrapper contract.
 * *****************************************************************************
 * @dev USAGE
 *
 * @dev Calling contracts must inherit from VRFV2WrapperConsumerBase. The consumer must be funded
 * @dev with enough LINK to make the request, otherwise requests will revert. To request randomness,
 * @dev call the 'requestRandomness' function with the desired VRF parameters. This function handles
 * @dev paying for the request based on the current pricing.
 *
 * @dev Consumers must implement the fullfillRandomWords function, which will be called during
 * @dev fulfillment with the randomness result.
 */
abstract contract VRFV2WrapperConsumerBase {
  // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i
  LinkTokenInterface internal immutable LINK;
  // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i
  VRFV2WrapperInterface internal immutable VRF_V2_WRAPPER;

  /**
   * @param _link is the address of LinkToken
   * @param _vrfV2Wrapper is the address of the VRFV2Wrapper contract
   */
  constructor(address _link, address _vrfV2Wrapper) {
    LINK = LinkTokenInterface(_link);
    VRF_V2_WRAPPER = VRFV2WrapperInterface(_vrfV2Wrapper);
  }

  /**
   * @dev Requests randomness from the VRF V2 wrapper.
   *
   * @param _callbackGasLimit is the gas limit that should be used when calling the consumer's
   *        fulfillRandomWords function.
   * @param _requestConfirmations is the number of confirmations to wait before fulfilling the
   *        request. A higher number of confirmations increases security by reducing the likelihood
   *        that a chain re-org changes a published randomness outcome.
   * @param _numWords is the number of random words to request.
   *
   * @return requestId is the VRF V2 request ID of the newly created randomness request.
   */
  // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
  function requestRandomness(
    uint32 _callbackGasLimit,
    uint16 _requestConfirmations,
    uint32 _numWords
  ) internal returns (uint256 requestId) {
    LINK.transferAndCall(
      address(VRF_V2_WRAPPER),
      VRF_V2_WRAPPER.calculateRequestPrice(_callbackGasLimit),
      abi.encode(_callbackGasLimit, _requestConfirmations, _numWords)
    );
    return VRF_V2_WRAPPER.lastRequestId();
  }

  /**
   * @notice fulfillRandomWords handles the VRF V2 wrapper response. The consuming contract must
   * @notice implement it.
   *
   * @param _requestId is the VRF V2 request ID.
   * @param _randomWords is the randomness result.
   */
  // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
  function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal virtual;

  function rawFulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) external {
    // solhint-disable-next-line gas-custom-errors
    require(msg.sender == address(VRF_V2_WRAPPER), "only VRF V2 wrapper can fulfill");
    fulfillRandomWords(_requestId, _randomWords);
  }
}

File 8 of 26 : Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

File 9 of 26 : Ownable2Step.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Returns the address of the pending owner.
     */
    function pendingOwner() public view virtual returns (address) {
        return _pendingOwner;
    }

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}

File 10 of 26 : draft-IERC6093.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;

/**
 * @dev Standard ERC20 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
 */
interface IERC20Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC20InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC20InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     * @param allowance Amount of tokens a `spender` is allowed to operate with.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC20InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `spender` to be approved. Used in approvals.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC20InvalidSpender(address spender);
}

/**
 * @dev Standard ERC721 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
 */
interface IERC721Errors {
    /**
     * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
     * Used in balance queries.
     * @param owner Address of the current owner of a token.
     */
    error ERC721InvalidOwner(address owner);

    /**
     * @dev Indicates a `tokenId` whose `owner` is the zero address.
     * @param tokenId Identifier number of a token.
     */
    error ERC721NonexistentToken(uint256 tokenId);

    /**
     * @dev Indicates an error related to the ownership over a particular token. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param tokenId Identifier number of a token.
     * @param owner Address of the current owner of a token.
     */
    error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC721InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC721InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param tokenId Identifier number of a token.
     */
    error ERC721InsufficientApproval(address operator, uint256 tokenId);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC721InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC721InvalidOperator(address operator);
}

/**
 * @dev Standard ERC1155 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
 */
interface IERC1155Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     * @param tokenId Identifier number of a token.
     */
    error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC1155InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC1155InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param owner Address of the current owner of a token.
     */
    error ERC1155MissingApprovalForAll(address operator, address owner);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC1155InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC1155InvalidOperator(address operator);

    /**
     * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
     * Used in batch transfers.
     * @param idsLength Length of the array of token identifiers
     * @param valuesLength Length of the array of token amounts
     */
    error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}

File 11 of 26 : ERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     * ```
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}

File 12 of 26 : IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

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

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

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

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

File 13 of 26 : IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

File 14 of 26 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

File 15 of 26 : SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}

File 16 of 26 : Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

File 17 of 26 : Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

File 18 of 26 : Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // 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-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            return a / b;
        }

        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
            // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.

            uint256 twos = denominator & (0 - denominator);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
            // works in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}

File 19 of 26 : SignedMath.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

File 20 of 26 : Strings.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)

pragma solidity ^0.8.20;

import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant HEX_DIGITS = "0123456789abcdef";
    uint8 private constant ADDRESS_LENGTH = 20;

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toStringSigned(int256 value) internal pure returns (string memory) {
        return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        uint256 localValue = value;
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = HEX_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            revert StringsInsufficientHexLength(value, length);
        }
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
     * representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

File 21 of 26 : AutomateModuleHelper.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.14;

import "./Types.sol";

abstract contract AutomateModuleHelper {
    function _resolverModuleArg(
        address _resolverAddress,
        bytes memory _resolverData
    ) internal pure returns (bytes memory) {
        return abi.encode(_resolverAddress, _resolverData);
    }

    function _proxyModuleArg() internal pure returns (bytes memory) {
        return bytes("");
    }

    function _singleExecModuleArg() internal pure returns (bytes memory) {
        return bytes("");
    }

    function _web3FunctionModuleArg(
        string memory _web3FunctionHash,
        bytes memory _web3FunctionArgsHex
    ) internal pure returns (bytes memory) {
        return abi.encode(_web3FunctionHash, _web3FunctionArgsHex);
    }

    function _timeTriggerModuleArg(uint128 _start, uint128 _interval)
        internal
        pure
        returns (bytes memory)
    {
        bytes memory triggerConfig = abi.encode(_start, _interval);

        return abi.encode(TriggerType.TIME, triggerConfig);
    }

    function _cronTriggerModuleArg(string memory _expression)
        internal
        pure
        returns (bytes memory)
    {
        bytes memory triggerConfig = abi.encode(_expression);

        return abi.encode(TriggerType.CRON, triggerConfig);
    }

    function _eventTriggerModuleArg(
        address _address,
        bytes32[][] memory _topics,
        uint256 _blockConfirmations
    ) internal pure returns (bytes memory) {
        bytes memory triggerConfig = abi.encode(
            _address,
            _topics,
            _blockConfirmations
        );

        return abi.encode(TriggerType.EVENT, triggerConfig);
    }

    function _blockTriggerModuleArg() internal pure returns (bytes memory) {
        bytes memory triggerConfig = abi.encode(bytes(""));

        return abi.encode(TriggerType.BLOCK, triggerConfig);
    }
}

File 22 of 26 : AutomateReady.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.14;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./Types.sol";

/**
 * @dev Inherit this contract to allow your smart contract to
 * - Make synchronous fee payments.
 * - Have call restrictions for functions to be automated.
 */
// solhint-disable private-vars-leading-underscore
abstract contract AutomateReady {
    IAutomate public immutable automate;
    address public immutable dedicatedMsgSender;
    address private immutable feeCollector;
    address internal constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    /**
     * @dev
     * Only tasks created by _taskCreator defined in constructor can call
     * the functions with this modifier.
     */
    modifier onlyDedicatedMsgSender() {
        require(msg.sender == dedicatedMsgSender, "Only dedicated msg.sender");
        _;
    }

    /**
     * @dev
     * _taskCreator is the address which will create tasks for this contract.
     */
    constructor(address _automate, address _taskCreator) {
        automate = IAutomate(_automate);
        IGelato gelato = IGelato(IAutomate(_automate).gelato());

        feeCollector = gelato.feeCollector();

        address proxyModuleAddress = IAutomate(_automate).taskModuleAddresses(
            Module.PROXY
        );

        address opsProxyFactoryAddress = IProxyModule(proxyModuleAddress)
            .opsProxyFactory();

        (dedicatedMsgSender, ) = IOpsProxyFactory(opsProxyFactoryAddress)
            .getProxyOf(_taskCreator);
    }

    /**
     * @dev
     * Transfers fee to gelato for synchronous fee payments.
     *
     * _fee & _feeToken should be queried from IAutomate.getFeeDetails()
     */
    function _transfer(uint256 _fee, address _feeToken) internal {
        if (_feeToken == ETH) {
            (bool success, ) = feeCollector.call{value: _fee}("");
            require(success, "_transfer: ETH transfer failed");
        } else {
            SafeERC20.safeTransfer(IERC20(_feeToken), feeCollector, _fee);
        }
    }

    function _getFeeDetails()
        internal
        view
        returns (uint256 fee, address feeToken)
    {
        (fee, feeToken) = automate.getFeeDetails();
    }
}

File 23 of 26 : AutomateTaskCreator.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.14;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./AutomateReady.sol";
import {AutomateModuleHelper} from "./AutomateModuleHelper.sol";

/**
 * @dev Inherit this contract to allow your smart contract
 * to be a task creator and create tasks.
 */
//solhint-disable const-name-snakecase
//solhint-disable no-empty-blocks
abstract contract AutomateTaskCreator is AutomateModuleHelper, AutomateReady {
    using SafeERC20 for IERC20;

    IGelato1Balance public constant gelato1Balance =
        IGelato1Balance(0x7506C12a824d73D9b08564d5Afc22c949434755e);

    constructor(address _automate) AutomateReady(_automate, address(this)) {}

    function _depositFunds1Balance(
        uint256 _amount,
        address _token,
        address _sponsor
    ) internal {
        if (_token == ETH) {
            ///@dev Only deposit ETH on goerli for now.
            require(block.chainid == 5, "Only deposit ETH on goerli");
            gelato1Balance.depositNative{value: _amount}(_sponsor);
        } else {
            ///@dev Only deposit USDC on polygon for now.
            require(
                block.chainid == 137 &&
                    _token ==
                    address(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174),
                "Only deposit USDC on polygon"
            );
            IERC20(_token).approve(address(gelato1Balance), _amount);
            gelato1Balance.depositToken(_sponsor, _token, _amount);
        }
    }

    function _createTask(
        address _execAddress,
        bytes memory _execDataOrSelector,
        ModuleData memory _moduleData,
        address _feeToken
    ) internal returns (bytes32) {
        return
            automate.createTask(
                _execAddress,
                _execDataOrSelector,
                _moduleData,
                _feeToken
            );
    }

    function _cancelTask(bytes32 _taskId) internal {
        automate.cancelTask(_taskId);
    }
}

File 24 of 26 : Types.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

enum Module {
    RESOLVER,
    DEPRECATED_TIME,
    PROXY,
    SINGLE_EXEC,
    WEB3_FUNCTION,
    TRIGGER
}

enum TriggerType {
    TIME,
    CRON,
    EVENT,
    BLOCK
}

struct ModuleData {
    Module[] modules;
    bytes[] args;
}

interface IAutomate {
    function createTask(
        address execAddress,
        bytes calldata execDataOrSelector,
        ModuleData calldata moduleData,
        address feeToken
    ) external returns (bytes32 taskId);

    function cancelTask(bytes32 taskId) external;

    function getFeeDetails() external view returns (uint256, address);

    function gelato() external view returns (address payable);

    function taskModuleAddresses(Module) external view returns (address);
}

interface IProxyModule {
    function opsProxyFactory() external view returns (address);
}

interface IOpsProxyFactory {
    function getProxyOf(address account) external view returns (address, bool);
}

interface IGelato1Balance {
    function depositNative(address _sponsor) external payable;

    function depositToken(
        address _sponsor,
        address _token,
        uint256 _amount
    ) external;
}

interface IGelato {
    function feeCollector() external view returns (address);
}

File 25 of 26 : TransferHelper.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.6.0;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-periphery
library TransferHelper {
    /// @notice Transfers tokens from the targeted address to the given destination
    /// @notice Errors with 'STF' if transfer fails
    /// @param token The contract address of the token to be transferred
    /// @param from The originating address from which the tokens will be transferred
    /// @param to The destination address of the transfer
    /// @param value The amount to be transferred
    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value)
        );
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'STF');
    }

    /// @notice Transfers tokens from msg.sender to a recipient
    /// @dev Errors with ST if transfer fails
    /// @param token The contract address of the token which will be transferred
    /// @param to The recipient of the transfer
    /// @param value The value of the transfer
    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'ST');
    }

    /// @notice Approves the stipulated contract to spend the given allowance in the given token
    /// @dev Errors with 'SA' if transfer fails
    /// @param token The contract address of the token to be approved
    /// @param to The target of the approval
    /// @param value The amount of the given token the target will be allowed to spend
    function safeApprove(
        address token,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'SA');
    }

    /// @notice Transfers NativeToken to the recipient address
    /// @dev Fails with `STE`
    /// @param to The destination of the transfer
    /// @param value The value to be transferred
    function safeTransferNative(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, 'STE');
    }
}

File 26 of 26 : SilverFees.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "contracts/Libraries/TransferHelper.sol";

import "contracts/Integrations/Gelato/AutomateTaskCreator.sol";

import "contracts/SilverFees/SilverFeesGiveaway.sol";

struct ExactInputParams {
	bytes path;
	address recipient;
	uint256 deadline;
	uint256 amountIn;
	uint256 amountOutMinimum;
}

interface IAlgebraSwapRouter {
	function exactInput(ExactInputParams memory data) external payable returns (uint256);
}

interface IAlgebraCommunityVault {
	function withdraw(address token, uint256 amount) external;
	function algebraFee() external view returns (uint16);
}

interface IAlgebraPool {
	function communityVault() external view returns (address);
}

interface IAlgebraNFTPositionManager {
	function balanceOf(address owner) external view returns (uint256);
}

// Fees redistribution data
struct FeesRedistributionData {
	uint256 agWeeklyGiveawayAmount;
	uint256 wrappedTokenWeeklyGiveawayAmount;
	uint256 agFlareAmount;
	uint256 wrappedTokenFlareAmount;
	uint256 agSnatchAmount;
	uint256 wrappedTokenSnatchAmount;
}

// Fees management
struct FeesManagementData {
	uint256 teamFees;
	uint256 weeklyGiveawayFees;
	uint256 buybackFees;
	FeesRedistributionData redistributionData;
	mapping (address => bool) flareWhitelistedTokens;
	address[] flareTokenToUnwhitelist;
	uint256 flareProgramPercentage;
	address bannedFlareUser;
	bool flareEnded;
	bool snatchEnded;
	string flareCID;
	string snatchCID;
	bool swapChanged;
	bool swapToWrappedToken;
	uint256 firstExecution;
	uint256 lastExecution;
	bytes32 taskId;
}

// Sync fees management
struct SyncFeesManagementData {
	uint256 time;
	uint256 lastSync;
	uint256 nextSync;
	bytes32 taskId;
}

// Fees converter
struct FeesTokenData {
	mapping(address => bytes32) taskId;
	string scriptCID;
}

// Bid data
struct BidData {
	uint256 amount;
	uint256 timestamp;
}

// Flare datas
struct FlareData {
	address user;
	uint256 bidAmount;
	address buybackToken;
	mapping (address=>BidData) lastBid;
	bytes32 taskId;
}

// Snatch datas
struct SnatchPerPoolData {
	address user;
	uint256 bidAmount;
}

struct SnatchData {
	SnatchPerPoolData perPoolData;
	mapping (address=>BidData) lastBid;
	uint256 lastExecution;
	address bannedUser;
	bytes32 taskId;
}

/// @title SilverFees
/// @author github.com/SifexPro
/// @notice Contract for the fees management of Silver
contract SilverFees is AutomateTaskCreator, Ownable2Step {
	SilverFeesGiveaway public silverFeesGiveaway;

	// Utils variables
	IERC20 public silverToken;
	IERC20 public wrappedToken;
	address public burnAddress;
	address public flareProgramAddress;
	address public teamMultisig;
	IAlgebraSwapRouter public swapRouter;
	IAlgebraCommunityVault public communityVault;
	IAlgebraNFTPositionManager public nftPositionManager;

	// All datas structures
	FeesManagementData public feesManagementData;
	SyncFeesManagementData public syncFeesManagementData;
	FeesTokenData public feesTokenData;
	FlareData public flareData;
	mapping (address => SnatchData) public snatchData;
	mapping(address => bool) public snatchIsPoolBided;
	address[] public snatchPoolsBids;

	// Events
	event FeesManagementExecuted(uint256 forTeam, uint256 forWeeklyGiveaway, uint256 forBuyback);
	event SyncFeesStarted();
	event SyncFeesManagement(uint256 indexed timestamp);
	event TokensBurned(uint256 amount);

	event FeesTokenAdded(address indexed token, bytes32 taskId);
	event FeesTokenRemoved(address indexed token);
	event FeesTokenSwapped(address indexed token, uint256 amountIn, uint256 amountOut);

	event FlareAuction(address indexed user, uint256 auctionAmount);
	event FlareExecution(address indexed user, uint256 buybackAmount, uint256 programAmount);
	event FlareBuyback(address indexed token, uint256 amount);

	event SnatchAuction(address indexed user, address indexed poolToSteal, uint256 auctionAmount);
	event SnatchExecution(address indexed user, address indexed poolToSteal);
	event SnatchSteal(address indexed user, address indexed rewardsPool, address rewardsToken, uint256 rewardsAmount);

	// Events for misc
	event SwapToWrappedToken(bool swapToWrappedToken);
	event SwapTypeChanged();
	event WithdrawnNative(address indexed to, uint256 amount);
	event WithdrawnToken(address indexed token, address to, uint256 amount);
	event EditedTeamMultisig(address indexed teamMultisig);
	event EditedFees(uint256 teamFees, uint256 weeklyGiveawayFees, uint256 buybackFees);

	// Gelato events
	event GelatoTaskCreated(bytes32 id);
	event GelatoTaskCanceled(bytes32 id);
	event GelatoTaskCancelFailed(bytes32 id);
	event GelatoFeesCheck(uint256 fees, address token);
	
	// Constructor
	constructor(address _silver, address _silverFeesGiveaway, address _burnAddress, address _flareProgramAddress, address _swapRouter, address _nftPositionManager, address _communityVault, address _teamMultisig, address _automate, address _wrappedToken, string memory _flareCID, string memory _snatchCID, string memory _feesTokenCID) AutomateTaskCreator(_automate) Ownable(msg.sender) {
		silverToken = IERC20(payable(_silver));
		wrappedToken = IERC20(payable(_wrappedToken));
		burnAddress = _burnAddress;
		flareProgramAddress = _flareProgramAddress;
		teamMultisig = _teamMultisig;
		swapRouter = IAlgebraSwapRouter(payable(_swapRouter));
		communityVault = IAlgebraCommunityVault(payable(_communityVault));
		nftPositionManager = IAlgebraNFTPositionManager(payable(_nftPositionManager));

		silverFeesGiveaway = SilverFeesGiveaway(payable(_silverFeesGiveaway));
		
		feesManagementData.teamFees = 12; // 12% for team
		feesManagementData.weeklyGiveawayFees = 3; // 3% for weekly giveaway
		feesManagementData.buybackFees = 85; // 85% for buyback

		feesManagementData.flareProgramPercentage = 0;

		feesManagementData.flareCID = _flareCID;
		feesManagementData.snatchCID = _snatchCID;
		
		feesManagementData.swapToWrappedToken = true;

		feesTokenData.scriptCID = _feesTokenCID;
    }


	// Fees management

	/**
	 * @dev Main function (to manage the fees) scheduled with Gelato by the sync system (10 min after the last sync) 
	 */
	function executeFeesManagement() public onlyDedicatedMsgSender {
		require(block.timestamp >= feesManagementData.lastExecution + syncFeesManagementData.time - 5 minutes, "Too early");

		// Balance
		uint256 balance;
		if (feesManagementData.swapToWrappedToken)
			balance = tokenAmount(address(wrappedToken)) - feesManagementData.redistributionData.wrappedTokenWeeklyGiveawayAmount - feesManagementData.redistributionData.wrappedTokenFlareAmount - feesManagementData.redistributionData.wrappedTokenSnatchAmount;
		else
			balance = tokenAmount(address(silverToken)) - feesManagementData.redistributionData.agWeeklyGiveawayAmount - feesManagementData.redistributionData.agFlareAmount - feesManagementData.redistributionData.agSnatchAmount;

		// Fees
		uint256 teamFees = (balance * feesManagementData.teamFees) / 100;
		uint256 weeklyGiveawayFees = (balance * feesManagementData.weeklyGiveawayFees) / 100;
		uint256 buybackFees = balance - teamFees - weeklyGiveawayFees;

		uint256 flareFees = buybackFees / 2;
		uint256 snatchFees = buybackFees - flareFees;

		// Redistribution
		if (feesManagementData.swapToWrappedToken) {
			SafeERC20.safeTransfer(wrappedToken, teamMultisig, teamFees);
			feesManagementData.redistributionData.wrappedTokenWeeklyGiveawayAmount += weeklyGiveawayFees;
			feesManagementData.redistributionData.wrappedTokenFlareAmount += flareFees;
			feesManagementData.redistributionData.wrappedTokenSnatchAmount += snatchFees;
		} else {
			SafeERC20.safeTransfer(silverToken, teamMultisig, teamFees);
			feesManagementData.redistributionData.agWeeklyGiveawayAmount += weeklyGiveawayFees;
			feesManagementData.redistributionData.agFlareAmount += flareFees;
			feesManagementData.redistributionData.agSnatchAmount += snatchFees;
		}

		// Giveaway
		if (silverFeesGiveaway.checkExecuteGiveaway())
			drawWinner();

		// Flare
		if (feesManagementData.flareEnded && flareData.user != address(0))
			executeFlare();
		feesManagementData.flareEnded = false;

		// Snatch
		if (feesManagementData.snatchEnded && snatchPoolsBids.length > 0)
		{
			for (uint256 i = 0; i < snatchPoolsBids.length; i++)
			{
				executeSnatch(snatchPoolsBids[i]);
				snatchIsPoolBided[snatchPoolsBids[i]] = false;
			}
			delete snatchPoolsBids;
		}
		feesManagementData.snatchEnded = false;

		// Fees management data update
		if (feesManagementData.swapChanged) {
			feesManagementData.swapChanged = false;
			feesManagementData.swapToWrappedToken = !feesManagementData.swapToWrappedToken;
			emit SwapToWrappedToken(feesManagementData.swapToWrappedToken);
		}
		feesManagementData.lastExecution = block.timestamp;

		// Gelato fees
		(uint256 fee, address feeToken) = _getFeeDetails();

		_transfer(fee, feeToken);
		emit GelatoFeesCheck(fee, feeToken);

		feesManagementData.taskId = bytes32("");
		emit FeesManagementExecuted(teamFees, weeklyGiveawayFees, buybackFees);
	}


	// Sync fees management

	/**
	 * @dev Sync the fees management 
	 */
	function syncFeesManagement() public onlyDedicatedMsgSender {
		silverFeesGiveaway.syncGiveaway(); // Sync the giveaway 

		if (flareData.user != address(0))
			feesManagementData.flareEnded = true; // End the flare auction
		if (snatchPoolsBids.length > 0)
			feesManagementData.snatchEnded = true; // End the snatch auction

		syncFeesManagementData.lastSync = block.timestamp;
		syncFeesManagementData.nextSync = block.timestamp + syncFeesManagementData.time;

		createTaskFeesManagement(); // Create the task for executeFeesManagement() (10 min)

		// Gelato fees
		(uint256 fee, address feeToken) = _getFeeDetails();

		_transfer(fee, feeToken);
		emit GelatoFeesCheck(fee, feeToken);

		emit SyncFeesManagement(block.timestamp);
	}

	/**
	 * @dev Start the sync system
	 * @param time Time between each sync
	 * @notice The sync system will execute the syncFeesManagement() function every time seconds (12 hours by default)
	 */
	function startSyncSystem(uint256 time) public onlyOwner {
		require(time == 0 || time >= 1200, "Time too low");

		syncFeesManagementData.time = time;
		syncFeesManagementData.lastSync = block.timestamp;
		syncFeesManagementData.nextSync = block.timestamp + time;

		feesManagementData.firstExecution = block.timestamp;
		feesManagementData.lastExecution = block.timestamp;
		
		cancelTask(flareData.taskId);

		cancelTask(feesManagementData.taskId);
		cancelTask(syncFeesManagementData.taskId);

		delete flareData;
		for (uint256 i = 0; i < snatchPoolsBids.length; i++)
		{
			cancelTaskSnatch(snatchPoolsBids[i]);
			delete snatchData[snatchPoolsBids[i]].perPoolData;
			snatchData[snatchPoolsBids[i]].lastExecution = 0;
			snatchIsPoolBided[snatchPoolsBids[i]] = false;
		}
		delete snatchPoolsBids;

		feesManagementData.flareEnded = false;
		feesManagementData.snatchEnded = false;

		if (time != 0) {
			silverFeesGiveaway.startSyncGiveaway();
			createTaskSyncSystem();
		}

		emit SyncFeesStarted();
	}

	function syncFeesTime() public view returns (uint256) {
		return syncFeesManagementData.time;
	}

	function syncFeesLastSync() public view returns (uint256) {
		return syncFeesManagementData.lastSync;
	}


	// Tokens fees converter

	/**
	 * @dev Swap the fees token to WrappedToken or $AG
	 * @param tokenAddress Token to swap
	 * @param swapArgs Swap arguments
	 * @notice Executed by a gelato task when SyncFeesManagement event is emitted
	 */
	function swapFeesToken(address tokenAddress, ExactInputParams memory swapArgs) public onlyDedicatedMsgSender {
		uint256 amountIn;
		uint256 amountOut;
		uint256 balanceBefore;
		uint256 balanceAfter;

		if (feesManagementData.swapToWrappedToken)
			balanceBefore = tokenAmount(address(wrappedToken));
		else
			balanceBefore = tokenAmount(address(silverToken));

		communityVault.withdraw(tokenAddress, swapArgs.amountIn);

		if (!(feesManagementData.swapToWrappedToken && tokenAddress == address(wrappedToken) || !feesManagementData.swapToWrappedToken && tokenAddress == address(silverToken))) 
		{
			swapArgs.recipient = payable(address(this));
			swapArgs.amountIn = tokenAmount(tokenAddress);

			TransferHelper.safeApprove(address(tokenAddress), address(swapRouter), swapArgs.amountIn);

			swapRouter.exactInput(swapArgs);
		}

		if (feesManagementData.swapToWrappedToken)
			balanceAfter = tokenAmount(address(wrappedToken));
		else
			balanceAfter = tokenAmount(address(silverToken));

		amountIn = swapArgs.amountIn;
		amountOut = balanceAfter - balanceBefore;

		// Gelato fees
		(uint256 fee, address feeToken) = _getFeeDetails();

		_transfer(fee, feeToken);
		emit GelatoFeesCheck(fee, feeToken);

		emit FeesTokenSwapped(tokenAddress, amountIn, amountOut);
	}

	/**
	 * @dev Add a token to the fees converter system 
	 * @param tokenAddress Token to add
	 * @notice The token must be in the community vault
	 */
	function addFeesToken(address tokenAddress) public onlyOwner {
		require(feesTokenData.taskId[tokenAddress] == bytes32(""), "Already added");

		createTaskFeesToken(tokenAddress);

		bytes32 taskId = feesTokenData.taskId[tokenAddress];
		emit FeesTokenAdded(tokenAddress, taskId);
	}

	function removeFeesToken(address tokenAddress) public onlyOwner {
		require(feesTokenData.taskId[tokenAddress] != bytes32(""), "Not added");

		_cancelTask(feesTokenData.taskId[tokenAddress]);
		feesTokenData.taskId[tokenAddress] = bytes32("");

		emit FeesTokenRemoved(tokenAddress);
	}

	function tokenAmountVault(address tokenAddress) public view returns (uint256) {
		IERC20 token = IERC20(tokenAddress);
        uint256 balance = token.balanceOf(address(communityVault));
        
		return (balance);
	}

	function tokenAmount(address tokenAddress) public view returns (uint256) {
		IERC20 token = IERC20(tokenAddress);
        uint256 balance = token.balanceOf(address(this));
        
		return (balance);
	}

	function isSwapToWrappedToken() public view returns (bool) {
		return (feesManagementData.swapToWrappedToken);
	}

	function feesTokenTaskId(address tokenAddress) public view returns (bytes32) {
		return feesTokenData.taskId[tokenAddress];
	}


	// Weekly giveaway

	/**
	 * @dev Buy tickets for the weekly giveaway
	 * @param _amount Amount in $AG of tickets to buy, by default 1 ticket = 1 $AG
	 */
	function buyTickets(uint256 _amount) public onlyLpUser {
		silverFeesGiveaway.buyTickets(_amount, msg.sender);
	}

	/**
	 * @dev Draw the winner of the weekly giveaway
	 * @notice The winner will receive the weekly giveaway amount
	 */
	function drawWinner() public onlyDedicatedMsgSender {
		bool _isSwapToWrappedToken = feesManagementData.swapToWrappedToken;
		uint256 weeklyGiveawayAmount;

		if (_isSwapToWrappedToken) {
			weeklyGiveawayAmount = feesManagementData.redistributionData.wrappedTokenWeeklyGiveawayAmount;
			feesManagementData.redistributionData.wrappedTokenWeeklyGiveawayAmount = 0;
			TransferHelper.safeApprove(address(wrappedToken), address(silverFeesGiveaway), weeklyGiveawayAmount);
		}
		else {
			weeklyGiveawayAmount = feesManagementData.redistributionData.agWeeklyGiveawayAmount;
			feesManagementData.redistributionData.agWeeklyGiveawayAmount = 0;
			TransferHelper.safeApprove(address(silverToken), address(silverFeesGiveaway), weeklyGiveawayAmount);
		}
		silverFeesGiveaway.executeGiveaway(_isSwapToWrappedToken, weeklyGiveawayAmount);
	}
	
	function buyTicketsBurn(address _user, uint256 _amount) external {
		require(msg.sender == address(silverFeesGiveaway), "Only FeesGiveaway");

		bool success = silverToken.transferFrom(_user, address(this), _amount);
		require(success, "Transfer failed");

		burnTokens(_amount);
	}


	// Flare

	/**
	 * @dev Flare auction
	 * @param _amountToBurn Amount of $AG to burn
	 * @param buybackToken Token to buyback
	 */
	function flare(uint256 _amountToBurn, address buybackToken) public onlyLpUser {
		require(!feesManagementData.flareEnded, "Ended");
		require(feesManagementData.bannedFlareUser != msg.sender, "Banned");
		require(flareIsWhitelistedToken(buybackToken), "Not whitelisted token");

		uint256 rounding = 1 ether / 10;
		uint256 roundAmount = _amountToBurn / rounding;
		uint256 amountToBurn = roundAmount * rounding;

		require(amountToBurn > flareData.bidAmount, "Bid too low");
		require(amountToBurn >= rounding, "< 0.1 $AG");

		uint256 balance = silverToken.balanceOf(msg.sender);
		require(balance >= amountToBurn, "Not enough balance");

		uint256 allowance = silverToken.allowance(msg.sender, address(this));
		require(allowance >= amountToBurn, "Not enough allowance");

		flareData.user = msg.sender;
		flareData.bidAmount = amountToBurn;
		flareData.buybackToken = buybackToken;
		flareData.lastBid[msg.sender] = BidData(amountToBurn, block.timestamp);
		
		emit FlareAuction(msg.sender, amountToBurn);
	}

	/**
	 * @dev Flare's execute function
	 */
	function executeFlare() public onlyDedicatedMsgSender {
		require(feesManagementData.flareEnded, "Too early");

		address user = flareData.user;
		uint256 amountToBurn = flareData.bidAmount;

		uint256 balance = silverToken.balanceOf(user);
		uint256 allowance = silverToken.allowance(user, address(this));
		if (balance < amountToBurn || allowance < amountToBurn) {
			feesManagementData.bannedFlareUser = user;
			delete flareData;
			return;
		}

		bool success = silverToken.transferFrom(user, address(this), amountToBurn);
		require(success, "Transfer failed");
		
		burnTokens(amountToBurn);

		uint256 flareAmountBuyback;
		uint256 flareAmountProgram;
		if (feesManagementData.swapToWrappedToken) {
			flareAmountProgram = (feesManagementData.redistributionData.wrappedTokenFlareAmount * feesManagementData.flareProgramPercentage) / 100;
			flareAmountBuyback = feesManagementData.redistributionData.wrappedTokenFlareAmount - flareAmountProgram;
			
			feesManagementData.redistributionData.wrappedTokenFlareAmount = 0;

			if (flareAmountProgram > 0)
				SafeERC20.safeTransfer(wrappedToken, flareProgramAddress, flareAmountProgram);
		} 
		else {
			flareAmountProgram = (feesManagementData.redistributionData.agFlareAmount * feesManagementData.flareProgramPercentage) / 100;
			flareAmountBuyback = feesManagementData.redistributionData.agFlareAmount - flareAmountProgram;
			
			feesManagementData.redistributionData.agFlareAmount = 0;

			if (flareAmountProgram > 0)
				SafeERC20.safeTransfer(silverToken, flareProgramAddress, flareAmountProgram);
		}
		feesManagementData.bannedFlareUser = address(0);

		address buybackToken = flareData.buybackToken;
		delete flareData;

		createTaskFlareBuyback(buybackToken, flareAmountBuyback);

		emit FlareExecution(user, flareAmountBuyback, flareAmountProgram);
	}

	/**
	 * @dev Flare's buyback function (after the auction when executeFeeManagement is called)
	 * @param tokenToSwap Token to swap
	 * @param swapArgs Swap arguments
	 */
	function flareBuyback(address tokenToSwap, address tokenAddress, ExactInputParams memory swapArgs) public onlyDedicatedMsgSender {
		if (swapArgs.amountIn != 0 && tokenToSwap != tokenAddress && flareIsWhitelistedToken(tokenAddress)) {
			swapArgs.recipient = payable(teamMultisig);

			TransferHelper.safeApprove(address(tokenToSwap), address(swapRouter), swapArgs.amountIn);

			swapRouter.exactInput(swapArgs);
		}
		else if (swapArgs.amountIn != 0 && tokenToSwap == tokenAddress && flareIsWhitelistedToken(tokenAddress)) {
			bool success = IERC20(tokenToSwap).transfer(teamMultisig, swapArgs.amountIn);
			require(success, "Transfer failed");
		}

		for (uint256 i = 0; i < feesManagementData.flareTokenToUnwhitelist.length && i < 10; i++)
			feesManagementData.flareWhitelistedTokens[feesManagementData.flareTokenToUnwhitelist[i]] = false;
		delete feesManagementData.flareTokenToUnwhitelist;

		// Gelato fees
		(uint256 fee, address feeToken) = _getFeeDetails();

		_transfer(fee, feeToken);
		emit GelatoFeesCheck(fee, feeToken);

		flareData.taskId = bytes32("");
		emit FlareBuyback(tokenToSwap, swapArgs.amountIn);
	}

	function flareAddWhitelistedToken(address token) public onlyOwner {
		require(!flareIsWhitelistedToken(token), "Already whitelisted");
		feesManagementData.flareWhitelistedTokens[token] = true;
	}

	function flareRemoveWhitelistedToken(address token) public onlyOwner {
		require(flareIsWhitelistedToken(token), "Not whitelisted");
		if (flareData.buybackToken == token)
			feesManagementData.flareTokenToUnwhitelist.push(token);
		else
			feesManagementData.flareWhitelistedTokens[token] = false;
	}

	function flareIsWhitelistedToken(address token) public view returns (bool) {
		return feesManagementData.flareWhitelistedTokens[token];
	}

	function flareLastBid(address user) public view returns (uint256) {
		if (flareData.lastBid[user].timestamp > feesManagementData.lastExecution)
			return flareData.lastBid[user].amount;
		return 0;
	}


	// Snatch 

	/**
	 * @dev Snatch auction
	 * @param _amountToBurn Amount of $AG to burn
	 * @param poolToSteal Pool to steal 50% of swap fees
	 */
	function snatch(uint256 _amountToBurn, address poolToSteal) public onlyLpUser {
		require(!feesManagementData.snatchEnded, "Ended");
		require(snatchData[poolToSteal].bannedUser != msg.sender, "Banned");
		require(IAlgebraPool(poolToSteal).communityVault() == address(communityVault), "Invalid pool");

		uint256 rounding = 1 ether / 10;
		uint256 roundAmount = _amountToBurn / rounding;
		uint256 amountToBurn = roundAmount * rounding;

		require(amountToBurn > snatchData[poolToSteal].perPoolData.bidAmount, "Bid too low");
		require(amountToBurn >= rounding, "< 0.1 $AG");

		uint256 balance = silverToken.balanceOf(msg.sender);
		require(balance >= amountToBurn, "Not enough balance");

		uint256 allowance = silverToken.allowance(msg.sender, address(this));
		require(allowance >= amountToBurn, "Not enough allowance");

		if (!snatchIsPoolBided[poolToSteal])
		{
			snatchPoolsBids.push(poolToSteal);
			snatchIsPoolBided[poolToSteal] = true;
		}

		if (snatchData[poolToSteal].lastExecution == 0)
			snatchData[poolToSteal].lastExecution = feesManagementData.firstExecution;
		snatchData[poolToSteal].perPoolData.user = msg.sender;
		snatchData[poolToSteal].perPoolData.bidAmount = amountToBurn;
		snatchData[poolToSteal].lastBid[msg.sender] = BidData(amountToBurn, block.timestamp);
		
		emit SnatchAuction(msg.sender, poolToSteal, amountToBurn);
	}

	/**
	 * @dev Snatch's execute function
	 */
	function executeSnatch(address poolToSteal) public onlyDedicatedMsgSender {
		require(feesManagementData.snatchEnded, "Too early");

		address user = snatchData[poolToSteal].perPoolData.user;
		uint256 amountToBurn = snatchData[poolToSteal].perPoolData.bidAmount;

		uint256 balance = silverToken.balanceOf(user);
		uint256 allowance = silverToken.allowance(user, address(this));
		if (balance < amountToBurn || allowance < amountToBurn) {
			snatchData[poolToSteal].bannedUser = user;
			delete snatchData[poolToSteal].perPoolData;
			return;
		}

		bool success = silverToken.transferFrom(user, address(this), amountToBurn);
		require(success, "Transfer failed");
		
		burnTokens(amountToBurn);

		snatchData[poolToSteal].bannedUser = address(0);
		delete snatchData[poolToSteal].perPoolData;

		createTaskSnatchSteal(user, poolToSteal);

		emit SnatchExecution(user, poolToSteal);
	}
	
	/**
	 * @dev Snatch's steal function (after the auction when executeFeeManagement is called)
	 * @param user User to send the rewards
	 * @param rewardsPool Pool to steal
	 * @param rewardsToken Token to send
	 * @param rewardsAmount Amount to send
	 */
	function snatchSteal(address user, address rewardsPool, address rewardsToken, uint256 rewardsAmount) public onlyDedicatedMsgSender {
		if (rewardsToken == address(wrappedToken))
		{
			require(wrappedToken.transfer(user, rewardsAmount), "Transfer failed");
			if (rewardsAmount > feesManagementData.redistributionData.wrappedTokenSnatchAmount)
				feesManagementData.redistributionData.wrappedTokenSnatchAmount = 0;
			else
				feesManagementData.redistributionData.wrappedTokenSnatchAmount -= rewardsAmount;
		}
		else if (rewardsToken == address(silverToken))
		{
			require(silverToken.transfer(user, rewardsAmount), "Transfer failed");
			if (rewardsAmount > feesManagementData.redistributionData.agSnatchAmount)
				feesManagementData.redistributionData.agSnatchAmount = 0;
			else
				feesManagementData.redistributionData.agSnatchAmount -= rewardsAmount;
		}

		snatchData[rewardsPool].lastExecution = block.timestamp;
		snatchData[rewardsPool].taskId = bytes32("");

		// Gelato fees
		(uint256 fee, address feeToken) = _getFeeDetails();

		_transfer(fee, feeToken);
		emit GelatoFeesCheck(fee, feeToken);

		emit SnatchSteal(user, rewardsPool, rewardsToken, rewardsAmount);
	}

	function snatchLastBid(address user, address pool) public view returns (uint256) {
		if (snatchData[pool].lastBid[user].timestamp > feesManagementData.lastExecution)
			return snatchData[pool].lastBid[user].amount;
		return 0;
	}


	// Get allowance

	/**
	 * @dev Get all bids of a user
	 * @param user User to check
	 * @return totalBids Total bids of the user
	 * @notice Include Flare and Snatch bids
	 */
	function getAllBids(address user) public view returns (uint256) {
		uint256 totalBids;

		totalBids += flareLastBid(user);
		for (uint256 i = 0; i < snatchPoolsBids.length; i++)
			totalBids += snatchLastBid(user, snatchPoolsBids[i]);

		return totalBids;
	} 


	// Burn function

	/**
	 * @dev Burn the Silver tokens (send to burn contract)
	 * @param _amount Amount of $AG to burn
	 */
	function burnTokens(uint256 _amount) private {
		require(silverToken.transfer(burnAddress, _amount), "Burn failed");
		
		emit TokensBurned(_amount);
	}


	// Gelato functions

	/**
	 * @dev Create a task for the sync system 
	 * @notice The task will be executed every syncFeesManagementData.time seconds
	 */
	function createTaskSyncSystem() private {
		bytes memory execData = abi.encodeCall(this.syncFeesManagement, ());

		ModuleData memory moduleData = ModuleData({
			modules: new Module[](2),
			args: new bytes[](2)
		});

		moduleData.modules[0] = Module.PROXY;
		moduleData.modules[1] = Module.TRIGGER;
	
		moduleData.args[0] = _proxyModuleArg();
		
		moduleData.args[1] = _timeTriggerModuleArg(
			uint128(syncFeesManagementData.nextSync) * 1000,
			uint128(syncFeesManagementData.time) * 1000
		);

		bytes32 taskId = _createTask(address(this), execData, moduleData, ETH);
	
		syncFeesManagementData.taskId = taskId;
		
		emit GelatoTaskCreated(taskId);
	}
	
	/**
	 * @dev Create task for executeFeesManagement function (SINGLE_EXEC)
	 * @notice Created by the sync system
	 * @notice The task will be executed 10 min after the last sync
	 */
	function createTaskFeesManagement() private {
		uint256 execTime = 10 minutes;
		
		bytes memory execData = abi.encodeCall(this.executeFeesManagement, ());

		ModuleData memory moduleData = ModuleData({
			modules: new Module[](3),
			args: new bytes[](3)
		});

		moduleData.modules[0] = Module.PROXY;
		moduleData.modules[1] = Module.SINGLE_EXEC;
		moduleData.modules[2] = Module.TRIGGER;
	
		moduleData.args[0] = _proxyModuleArg();
		moduleData.args[1] = _singleExecModuleArg();
		moduleData.args[2] = _timeTriggerModuleArg(
			uint128(syncFeesManagementData.lastSync + execTime) * 1000, 
			uint128(execTime) * 1000
		);

		bytes32 taskId = _createTask(address(this), execData, moduleData, ETH);
	
		feesManagementData.taskId = taskId;
		
		emit GelatoTaskCreated(taskId);
	}

	/**
	 * @dev Create task for convert fees token to WrappedToken or $AG
	 * @param tokenAddress Token to convert
	 * @notice Executed by a gelato task when SyncFeesManagement event is emitted
	 */
	function createTaskFeesToken(address tokenAddress) private {
		bytes memory execData = abi.encode( 
			Strings.toHexString(uint256(uint160(address(this))), 20),			// contract address
			Strings.toHexString(uint256(uint160(tokenAddress)), 20),			// tokenAddress
			Strings.toString(ERC20(tokenAddress).decimals()),					// tokenDecimals
			Strings.toHexString(uint256(uint160(address(silverToken))), 20),	// agAddress
			Strings.toHexString(uint256(uint160(address(wrappedToken))), 20),	// wrappedTokenAddress
			Strings.toString(block.chainid)										// network
		);

		ModuleData memory moduleData = ModuleData({
			modules: new Module[](3),
			args: new bytes[](3)
		});

		moduleData.modules[0] = Module.PROXY;
		moduleData.modules[1] = Module.WEB3_FUNCTION;
		moduleData.modules[2] = Module.TRIGGER;
	
		moduleData.args[0] = _proxyModuleArg();
		moduleData.args[1] = _web3FunctionModuleArg(
			feesTokenData.scriptCID,
			execData
		);
		bytes32[][] memory topics = new bytes32[][](1);
		topics[0] = new bytes32[](1);
		topics[0][0] = keccak256("SyncFeesManagement(uint256)");

		moduleData.args[2] = _eventTriggerModuleArg(
			address(this),
           	topics,
			7
		);

		bytes32 taskId = _createTask(address(this), execData, moduleData, ETH);
	
		feesTokenData.taskId[tokenAddress] = taskId;
		
		emit GelatoTaskCreated(taskId);
	}

	/**
	 * @dev Create task for buyback function (after the auction)
	 * @param tokenAddress Token to swap
	 * @param buybackAmount Amount to buyback
	 * @notice Task created when executeFlare is called and executed right after
	 */
	function createTaskFlareBuyback(address tokenAddress, uint256 buybackAmount) private {
		address addressToSwap;
		if (feesManagementData.swapToWrappedToken)
			addressToSwap = address(wrappedToken);
		else
			addressToSwap = address(silverToken);
		
		bytes memory execData = abi.encode( 
			Strings.toHexString(uint256(uint160(address(this))), 20),			// contract address
			Strings.toHexString((uint256(uint160(teamMultisig))), 20), 		 	// teamMultisig
			Strings.toHexString((uint256(uint160(tokenAddress))), 20),			// tokenAddress
			Strings.toString(ERC20(tokenAddress).decimals()),					// tokenDecimals
			Strings.toString(buybackAmount),									// buybackAmount
			Strings.toHexString(uint256(uint160(address(addressToSwap))), 20), 	// addressToSwap
			Strings.toString(block.chainid)										// network
		);

		ModuleData memory moduleData = ModuleData({
			modules: new Module[](3),
			args: new bytes[](3)
		});

		moduleData.modules[0] = Module.PROXY;
		moduleData.modules[1] = Module.SINGLE_EXEC;
		moduleData.modules[2] = Module.WEB3_FUNCTION;
	
		moduleData.args[0] = _proxyModuleArg();
		moduleData.args[1] = _singleExecModuleArg();
		moduleData.args[2] = _web3FunctionModuleArg(
			feesManagementData.flareCID,
			execData
		);

		bytes32 taskId = _createTask(address(this), execData, moduleData, ETH);
	
		flareData.taskId = taskId;
		
		emit GelatoTaskCreated(taskId);
	}

	/**
     * @dev Create task for snatchSteal function (after the auction)
     * @param user User that steal the fees
     * @param poolToSteal Pool from which to steal 50% of swap fees
     * @notice Task is executed right after creation
     */
	function createTaskSnatchSteal(address user, address poolToSteal) private {
		address rewardsToken;
		uint256 timeToSteal = block.timestamp - snatchData[poolToSteal].lastExecution;
		
		if (feesManagementData.swapToWrappedToken)
			rewardsToken = address(wrappedToken);
		else
			rewardsToken = address(silverToken);

		bytes memory execData = abi.encode( 
			Strings.toHexString(uint256(uint160(address(this))), 20),			// contract address
			Strings.toHexString((uint256(uint160(user))), 20), 		 			// userAddress
			Strings.toString(timeToSteal),										// timeToSteal
			Strings.toHexString((uint256(uint160(poolToSteal))), 20),			// poolToSteal
			Strings.toHexString(uint256(uint160(address(rewardsToken))), 20), 	// rewardsToken
			Strings.toString(block.chainid)										// network
		);

		ModuleData memory moduleData = ModuleData({
			modules: new Module[](3),
			args: new bytes[](3)
		});

		moduleData.modules[0] = Module.PROXY;
		moduleData.modules[1] = Module.SINGLE_EXEC;
		moduleData.modules[2] = Module.WEB3_FUNCTION;
	
		moduleData.args[0] = _proxyModuleArg();
		moduleData.args[1] = _singleExecModuleArg();
		moduleData.args[2] = _web3FunctionModuleArg(
			feesManagementData.snatchCID,
			execData
		);

		bytes32 taskId = _createTask(address(this), execData, moduleData, ETH);
	
		snatchData[poolToSteal].taskId = taskId;
		
		emit GelatoTaskCreated(taskId);
	}

	function cancelTaskCall(bytes32 taskId) public {
		require(msg.sender == address(this));
		_cancelTask(taskId);
	}

	/**
	 * @dev Cancel a gelato task
	 * @param taskId Task id to cancel
	 */
	function cancelTask(bytes32 taskId) public onlyOwner {
		if (taskId == bytes32(""))
			return;

		(bool success, ) = address(this).call(
            abi.encodeWithSignature("cancelTaskCall(bytes32)", taskId)
        );

		if (success)
			emit GelatoTaskCanceled(taskId);
		else
			emit GelatoTaskCancelFailed(taskId);

		if (taskId == syncFeesManagementData.taskId)
			syncFeesManagementData.taskId = bytes32("");
		else if (taskId == feesManagementData.taskId)
			feesManagementData.taskId = bytes32("");
		else if (taskId == flareData.taskId)
			flareData.taskId = bytes32("");
	}

	/**
	 * @dev Cancel a gelato task
	 * @param pool Pool to cancel
	 */
	function cancelTaskSnatch(address pool) public onlyOwner {
		bytes32 taskId = snatchData[pool].taskId;
		if (taskId == bytes32(""))
			return;

		(bool success, ) = address(this).call(
            abi.encodeWithSignature("cancelTaskCall(bytes32)", taskId)
        );

		if (success)
			emit GelatoTaskCanceled(taskId);
		else
			emit GelatoTaskCancelFailed(taskId);
			
		snatchData[pool].taskId = bytes32("");
	}

	// Internal functions

	/**
	 * @dev Change the swap fees to WrappedToken or $AG
	 * @param swapToWrappedToken True if swap to WrappedToken, false if swap to $AG
	 * @notice The change is not immediate, it will be applied at the next fees management
	 */
	function setSwapToWrappedToken(bool swapToWrappedToken) public onlyOwner {
		if (feesManagementData.swapToWrappedToken == swapToWrappedToken)
			feesManagementData.swapChanged = false;
		else
			feesManagementData.swapChanged = true;
		
		emit SwapTypeChanged();
	}

	function withdrawNative(address _to) public onlyOwner {
		uint256 balance = address(this).balance;
        require(balance > 0, "No Native to withdraw");

		address payable _tresory = payable(_to);
		(bool success, ) = _tresory.call{value:balance}("");
		require(success, "Transaction failed");

		emit WithdrawnNative(_tresory, balance);
	}

	function withdrawToken(address _token, address _to) public onlyOwner {
		IERC20 token = IERC20(_token);
		uint256 balance = token.balanceOf(address(this));

		SafeERC20.safeTransfer(token, _to, balance);

		emit WithdrawnToken(_token, _to, balance);
	}
	
	function editMultisig(address _teamMultisig) public onlyMultisig {
		teamMultisig = _teamMultisig;

		emit EditedTeamMultisig(_teamMultisig);
	}

	function editSilver(address _silver) public onlyOwner {
		silverToken = IERC20(payable(_silver));
	}

	function editSilverFeesGiveaway(address _silverFeesGiveaway) public onlyOwner {
		silverFeesGiveaway = SilverFeesGiveaway(payable(_silverFeesGiveaway));
	}

	function editwrappedToken(address _wrappedToken) public onlyOwner {
		wrappedToken = IERC20(payable(_wrappedToken));
	}

	function editFlareProgramAddress(address _flareProgramAddress) public onlyOwner {
		flareProgramAddress = _flareProgramAddress;
	}

	function editAlgebraSwapRouter(address _swapRouter) public onlyOwner {
		swapRouter = IAlgebraSwapRouter(payable(_swapRouter));
	}

	function editCommunityVault(address _communityVault) public onlyOwner {
		communityVault = IAlgebraCommunityVault(payable(_communityVault));
	}

	function editNftPositionManager(address _nftPositionManager) public onlyOwner {
		nftPositionManager = IAlgebraNFTPositionManager(payable(_nftPositionManager));
	}

	function editFees(uint256 teamFees, uint256 weeklyGiveawayFees, uint256 buybackFees) public onlyOwner {
		require(teamFees + weeklyGiveawayFees + buybackFees == 100, "Invalid fees");

		feesManagementData.teamFees = teamFees;
		feesManagementData.weeklyGiveawayFees = weeklyGiveawayFees;
		feesManagementData.buybackFees = buybackFees;

		emit EditedFees(teamFees, weeklyGiveawayFees, buybackFees);
	}

	function editFlareProgramPercentage(uint256 flareProgramPercentage) public onlyOwner {
		require(flareProgramPercentage <= 100);
		
		feesManagementData.flareProgramPercentage = flareProgramPercentage;
	}

	function editFlareCID(string memory flareCID) public onlyOwner {
		feesManagementData.flareCID = flareCID;
	}

	function editSnatchCID(string memory snatchCID) public onlyOwner {
		feesManagementData.snatchCID = snatchCID;
	}

	function editFeesTokenCID(string memory scriptCID) public onlyOwner {
		feesTokenData.scriptCID = scriptCID;
	}


	// Modifiers 

	modifier onlyLpUser() {
		require(nftPositionManager.balanceOf(msg.sender) > 0, "Not LP user");
		_;
	}

	modifier onlyMultisig() {
		require(msg.sender == teamMultisig, "Not authorized");
		_;
	}


	// Receive function (to receive Native)

	receive() external payable {}
}

Settings
{
  "evmVersion": "paris",
  "optimizer": {
    "enabled": true,
    "runs": 10
  },
  "metadata": {
    "bytecodeHash": "none"
  },
  "viaIR": true,
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"_link","type":"address"},{"internalType":"address","name":"_wrapper","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tickets","type":"uint256"}],"name":"BuyTickets","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"winner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"randomIndex","type":"uint256"}],"name":"DrawWinner","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"isGiveawayActive","type":"bool"}],"name":"EditedGiveawayActive","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"ticketPrice","type":"uint256"}],"name":"EditedGiveawayTicketPrice","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"giveawayTime","type":"uint256"}],"name":"EditedGiveawayTime","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"silverFees","type":"address"}],"name":"EditedSilverFees","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"isGiveawayActive","type":"bool"},{"indexed":false,"internalType":"uint256","name":"weeklyGiveawayAmount","type":"uint256"}],"name":"GiveawayExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"ticketPrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"giveawayEndTime","type":"uint256"}],"name":"GiveawaySyncStarted","type":"event"},{"anonymous":false,"inputs":[],"name":"GiveawaySynced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"randomWords","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"payment","type":"uint256"}],"name":"RequestFulfilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint32","name":"numWords","type":"uint32"}],"name":"RequestSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"ticketPrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"giveawayEndTime","type":"uint256"}],"name":"StartedGiveaway","type":"event"},{"anonymous":false,"inputs":[],"name":"StoppedGiveaway","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawnToken","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"user","type":"address"}],"name":"buyTickets","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"checkExecuteGiveaway","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ticketPrice","type":"uint256"}],"name":"editGiveawayTicketPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_giveawayTime","type":"uint256"}],"name":"editGiveawayTime","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_silverFees","type":"address"}],"name":"editSilverFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"isSwapToWrappedToken","type":"bool"},{"internalType":"uint256","name":"weeklyGiveawayAmount","type":"uint256"}],"name":"executeGiveaway","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"giveawayData","outputs":[{"internalType":"uint256","name":"numberOfParticipants","type":"uint256"},{"internalType":"bool","name":"participationEnded","type":"bool"},{"internalType":"uint256","name":"giveawayEndTime","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"giveawaySettings","outputs":[{"internalType":"bool","name":"isGiveawayActive","type":"bool"},{"internalType":"uint256","name":"ticketPrice","type":"uint256"},{"internalType":"uint256","name":"giveawayTime","type":"uint256"},{"internalType":"bool","name":"editedActive","type":"bool"},{"internalType":"uint256","name":"editedTicketPrice","type":"uint256"},{"internalType":"uint256","name":"editedGiveawayTime","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"giveawayTicketsData","outputs":[{"internalType":"uint256","name":"lastExecution","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"},{"internalType":"uint256[]","name":"_randomWords","type":"uint256[]"}],"name":"rawFulfillRandomWords","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"s_input","outputs":[{"internalType":"uint32","name":"callbackGasLimit","type":"uint32"},{"internalType":"uint16","name":"requestConfirmations","type":"uint16"},{"internalType":"uint32","name":"numWords","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_request","outputs":[{"internalType":"uint256","name":"paid","type":"uint256"},{"internalType":"bool","name":"fulfilled","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"_giveawayActive","type":"bool"}],"name":"setActiveGiveaway","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"silverFees","outputs":[{"internalType":"contract SilverFees","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"startSyncGiveaway","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"syncGiveaway","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"userTickets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"_to","type":"address"}],"name":"withdrawToken","outputs":[],"stateMutability":"nonpayable","type":"function"}]

60c034620001955762002253906001600160401b03601f38849003908101601f1916830190828211848310176200019a578084916040968794855283398101031262000195576200005e60206200005684620001b0565b9301620001b0565b9133156200017d57600180546001600160a01b031990811690915560008054339281168317825590946001600160a01b03939092849283167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08880a3166080521660a052601280546001600160501b031916660100070007a12017905582519060c08201908111828210176200016957835260018152670de0b6b3a7640000908160208201528260a062093a80928387820152826060820152826080820152015260ff19916001836009541617600955600a55600b55600c5416600c5580600d55600e555161208d9081620001c6823960805181611938015260a0518181816111e301526118650152f35b634e487b7160e01b83526041600452602483fd5b8351631e4fbdf760e01b815260006004820152602490fd5b600080fd5b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b0382168203620001955756fe60406080815260048036101561001457600080fd5b6000803560e01c8063149d8b8e146113d15780631fe543e31461116557806332b7083b146111305780633aeac4e114610f7b5780633e16d3dd14610efc57806340b2268314610e8d578063711332dc146109c0578063715018a61461096d57806379ba5097146108fd57806380017f4e146108d05780638942fc311461089f5780638da5cb5b14610877578063903d0d7e1461083857806393b886c1146107e9578063a189b421146107bf578063a48debd11461075e578063a527913e14610735578063b6ec155b146106e1578063b9c5a9781461068e578063e30c397814610665578063e8177b8614610642578063f2fde38b146105d55763fdad3a5f1461011c57600080fd5b346105d257826003193601126105d25781359261013761160f565b6002546001600160a01b0395908616610151338214611683565b60ff60095416156105995760ff6005541661056e57600a549283831061053b5782938015610528578080850494116104fb575b50845163c34fe1dd60e01b815297602092838a8a81845afa998a156104f157848a9b84928b9c9b916104d4575b5060248a51809481936370a0823160e01b8352818a169e8f90840152165afa908115610434579087918b916104a3575b501061046b57865163c34fe1dd60e01b815284818c81855afa908115610434578a9161043e575b50848960448d868c519586948593636eb1769f60e11b8552840152876024840152165afa908115610434579087918b916103ff575b50106103c557803b156103c157888661026d9285838e8c5196879586948593639e28bdcf60e01b85528401611cec565b03925af180156103b75761038a575b508390885b82811061031c57505061029b6102a0926003548b55611d38565b611758565b8451908186016001600160401b03811183821017610307577feb94184f338c573d1df2cb195ce1a83fc8fc5ffcc64d03de272232fb7aa183cf97989950865281526001828201428152888a5260078452868a2092518355519101558351928352820152a280f35b60418a634e487b7160e01b6000525260246000fd5b90915060038054600160401b8110156103775780600161033e92018355611d07565b81929154911b90848b831b921b1916179055600019811461036457600101908491610281565b634e487b7160e01b895260118a52602489fd5b634e487b7160e01b8b5260418c5260248bfd5b9097906001600160401b0381116103a4578652963861027c565b634e487b7160e01b825260418a52602482fd5b87513d8b823e3d90fd5b8880fd5b865162461bcd60e51b8152808b0185905260146024820152734e6f7420656e6f75676820616c6c6f77616e636560601b6044820152606490fd5b809250868092503d831161042d575b61041881836115d1565b81010312610429578690513861023d565b8980fd5b503d61040e565b88513d8c823e3d90fd5b61045e9150853d8711610464575b61045681836115d1565b8101906116c1565b38610208565b503d61044c565b865162461bcd60e51b8152808b0185905260126024820152714e6f7420656e6f7567682062616c616e636560701b6044820152606490fd5b809250868092503d83116104cd575b6104bc81836115d1565b8101031261042957869051386101e1565b503d6104b2565b6104eb9150823d84116104645761045681836115d1565b386101b1565b87513d8a823e3d90fd5b809194508302908382041483151715610515579238610184565b634e487b7160e01b865260118752602486fd5b634e487b7160e01b875260128852602487fd5b845162461bcd60e51b8152602081890152600d60248201526c507269636520746f6f206c6f7760981b6044820152606490fd5b835162461bcd60e51b81526020818801526005602482015264115b99195960da1b6044820152606490fd5b835162461bcd60e51b815260208188015260136024820152724769766561776179206e6f742061637469766560681b6044820152606490fd5b80fd5b50346105d25760203660031901126105d2576105ef6115f4565b6105f7611657565b600180546001600160a01b0319166001600160a01b0392831690811790915582549091167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e227008380a380f35b5082346106615781600319360112610661576020906008549051908152f35b5080fd5b50823461066157816003193601126106615760015490516001600160a01b039091168152602090f35b509190346106dd5760203660031901126106dd577f68bff35e46d3206e18452a58ab63d15ddf725d0b38dfb1df3d4dab8e77f5d5659160209135906106d1611657565b81600e5551908152a180f35b8280fd5b50823461066157816003193601126106615760c09060ff6009541690600a5490600b5460ff600c541690600d5492600e549481519615158752602087015285015215156060840152608083015260a0820152f35b50823461066157816003193601126106615760025490516001600160a01b039091168152602090f35b50823461066157816003193601126106615760209061078860018060a01b03600254163314611683565b60ff6005541690816107b2575b81156107a4575b519015158152f35b60095460ff1615915061079c565b60105460ff169150610795565b8382346105d257806003193601126105d25750600f5460ff60105416825191825215156020820152f35b509190346106dd5760203660031901126106dd577ff56ae34a736154a570ed8935e3feb13fc99a990bfd5c7ad6486f4cb126859f9691602091359061082c611657565b81600d5551908152a180f35b50823461066157816003193601126106615761087390601254905191829163ffffffff90818160301c169161ffff8260201c16911684611634565b0390f35b508234610661578160031936011261066157905490516001600160a01b039091168152602090f35b50346105d257806003193601126105d2576108c560018060a01b03600254163314611683565b6108cd6117d3565b80f35b508234610661576020366003190112610661576020906108f66108f16115f4565b611d38565b9051908152f35b5091346106dd57826003193601126106dd57600154916001600160a01b039133838516036109565750506001600160a01b031991821660015582543392811683178455166000805160206120618339815191528380a380f35b60249250519063118cdaa760e01b82523390820152fd5b50346105d257806003193601126105d257610986611657565b600180546001600160a01b03199081169091558154908116825581906001600160a01b03166000805160206120618339815191528280a380f35b5091346106dd57806003193601126106dd576109da611625565b600254602435916001600160a01b03918216916109f8338414611683565b60ff928360095416808091610e82575b15610d3a575050826005541615610d0b578554958615610cd75760115415610cc45787906011825260209788832054069280610a4385611d07565b90549060031b1c1694600014610c7757600254885163996c6cc360e01b81529082168a828581845afa918215610c6d57610aa1928887868f958f8f968496610c4e575b50516323b872dd60e01b8152978896879586938d85016116f8565b0393165af1908115610c445791610ac28b9285948791610c17575b5061171a565b60ff196005541660055560025416895192838092631e14e34360e31b82525afa908115610c0d578391610bd6575b509287926000805160206120418339815191529992610b337f826bbd0fbc2baeb2244f7b5226338393b58c345e7dac64b39391b4e3b9b3813996600b5490611758565b600655818551610b42816115b6565b526003548260035580610bbd575b506003825255610b5e611f93565b42600855825191878352820152a25b80600954168015908115610ba4575b50610b97575b6009541690825191151582526020820152a180f35b610b9f611ddb565b610b82565b905080610bb2575b38610b7c565b508060105416610bac565b60038352848320610bd09181019061177b565b38610b50565b9298809294915083813d8311610c06575b610bf181836115d1565b810103126103c1579151909791929087610af0565b503d610be7565b88513d85823e3d90fd5b610c379150843d8611610c3d575b610c2f81836115d1565b8101906116e0565b38610abc565b503d610c25565b89513d86823e3d90fd5b610c66919650883d8a116104645761045681836115d1565b9438610a86565b8a513d87823e3d90fd5b600254885163c34fe1dd60e01b81529082168a828581845afa918215610c6d57610aa1928887868f958f8f968496610c4e5750516323b872dd60e01b8152978896879586938d85016116f8565b634e487b7160e01b885260329052602487fd5b606490602087519162461bcd60e51b8352820152600e60248201526d30207061727469636970616e747360901b6044820152fd5b845162461bcd60e51b81526020818801526009602482015268546f6f206561726c7960b81b6044820152606490fd5b9290919215610d5d575b5050506000805160206120418339815191529350610b6d565b15610e3457508060025416948685519263996c6cc360e01b845260209788858581845afa948515610c0d5792610db795928a959289958395610e15575b508a516323b872dd60e01b815297889687958693309185016116f8565b0393165af1948515610e0b5794610de691600080516020612041833981519152968892610dee575b505061171a565b388080610d44565b610e049250803d10610c3d57610c2f81836115d1565b3880610ddf565b84513d88823e3d90fd5b610e2d919550873d89116104645761045681836115d1565b9338610d9a565b948685519263c34fe1dd60e01b845260209788858581845afa948515610c0d5792610db795928a959289958395610e1557508a516323b872dd60e01b815297889687958693309185016116f8565b508460105416610a08565b5082346106615760203660031901126106615760207f1647043f27b6ab91a262baccf5e4b2dd4085e0ecea7e9885b6f6d329a0a6851a91610ecc6115f4565b610ed4611657565b600280546001600160a01b0319166001600160a01b039290921691821790559051908152a180f35b5082346106615760203660031901126106615760207fd1eac0a1c82fb301a1760f4bbacb6db64acaf2e4eebb26b7b1d11d267498a88c91610f3b611625565b90610f44611657565b6009549115159160ff1615158203610f695760ff19600c5416600c555b51908152a180f35b600160ff19600c541617600c55610f61565b509034610661578260031936011261066157610f956115f4565b90610f9e61160f565b91610fa7611657565b84516370a0823160e01b815230838201526001600160a01b039190911692909160208084602481885afa9384156111265786946110f3575b508651868083830163a9059cbb60e01b815283611000898960248401611cec565b0393611014601f19958681018352826115d1565b5190828a5af13d156110e6573d6001600160401b0381116110d357906110579291611048858c5194601f84011601846115d1565b82523d898584013e5b87611d78565b80519182151592836110ba575b5050506110a3575061109d7f11dd463c4f2edd676b85f050130c7ab2f5832f52becb5444b09a92404aa1498a9394955192839283611cec565b0390a280f35b8551635274afe760e01b8152908101849052602490fd5b6110ca93508201810191016116e0565b15388080611064565b634e487b7160e01b895260418552602489fd5b6110579150606090611051565b9080945081813d831161111f575b61110b81836115d1565b8101031261111b57519238610fdf565b8580fd5b503d611101565b87513d88823e3d90fd5b509190346106dd57826003193601126106dd5760609250549060ff600554169060065491815193845215156020840152820152f35b5090346106615782600319360112610661576024928335916001600160401b039190803583851161111b573660238601121561111b5784820135938085116113bf578460051b978451966020966111be888c018a6115d1565b885286880182819b830101913683116113bb5783899101915b8383106113ab575050507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163303905061136a57600f54156113355760135483036112fe576001938460ff19601054161760105587519283116112ed57600160401b83116112ed57505061125d816011969493965481601155611792565b8760118852838820885b8381106112dc5750505050600f54938351956060870191875260608488015251809152608086019792875b8281106112c957887f147eb1ff0c82f87f2b03e2c43f5a36488ff63ec6b730195fde4605f612f8db5189808d8b8b8301520390a180f35b84518a5298810198938101938301611292565b825182820155918501918401611267565b634e487b7160e01b89526041905287fd5b8360136064928888519362461bcd60e51b8552840152820152720e4cae2eacae6e840d2c840dad2e6dac2e8c6d606b1b6044820152fd5b8360116064928888519362461bcd60e51b8552840152820152701c995c5d595cdd081b9bdd08199bdd5b99607a1b6044820152fd5b83601f6064928888519362461bcd60e51b85528401528201527f6f6e6c792056524620563220777261707065722063616e2066756c66696c6c006044820152fd5b82358152918101918991016111d7565b8a80fd5b634e487b7160e01b8752604183528787fd5b5082346106615781600319360112610661576002546001600160a01b0393906113fd9085163314611683565b611405611ddb565b815190611411826115b6565b83825284600254169183518093631e14e34360e31b8252818460209687935afa801561157b57869061154c575b61144c9150600b5490611758565b8451909290916001600160401b036080840181811185821017611539578752818452858401918883526060888601958a875201958652805191821161153957600160401b82116115395786906003548360035580841061151e575b500160038952868920895b83811061150a57505050507fc848b8758eef2575b37a79281d279c80506ed2e7fdfc9d6a1d61da8b60e9c68d96975051905551151560ff8019600554169116176005555180600655600a54918351928352820152a180f35b82518c1682820155918801916001016114b2565b60038b52828b2061153391810190850161177b565b8b6114a7565b634e487b7160e01b895260418452602489fd5b508381813d8311611574575b61156281836115d1565b8101031261111b5761144c905161143e565b503d611558565b85513d88823e3d90fd5b606081019081106001600160401b038211176115a057604052565b634e487b7160e01b600052604160045260246000fd5b602081019081106001600160401b038211176115a057604052565b601f909101601f19168101906001600160401b038211908210176115a057604052565b600435906001600160a01b038216820361160a57565b600080fd5b602435906001600160a01b038216820361160a57565b60043590811515820361160a57565b63ffffffff918216815261ffff9092166020830152909116604082015260600190565b6000546001600160a01b0316330361166b57565b60405163118cdaa760e01b8152336004820152602490fd5b1561168a57565b60405162461bcd60e51b815260206004820152600f60248201526e4f6e6c792053696c7665724665657360881b6044820152606490fd5b9081602091031261160a57516001600160a01b038116810361160a5790565b9081602091031261160a5751801515810361160a5790565b6001600160a01b03918216815291166020820152604081019190915260600190565b1561172157565b60405162461bcd60e51b815260206004820152600f60248201526e151c985b9cd9995c8819985a5b1959608a1b6044820152606490fd5b9190820180921161176557565b634e487b7160e01b600052601160045260246000fd5b818110611786575050565b6000815560010161177b565b80821061179d575050565b60116000526117d1917f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68918201910161177b565b565b60ff60095416156117d1576002546040805163179bbedf60e21b8152916001600160a01b039190600490602090819086908490829088165afa948515611ce157600095611cb2575b50600b5460019561182e81881c42611758565b6006548091101591828093611ca8575b80611c9e575b15611bd95750505050601354611baa576012549163ffffffff9283811690867f000000000000000000000000000000000000000000000000000000000000000016968651916310c1b4d560e21b93848452808685015286846024818d5afa938415611b9f57600094611b70575b509186926118d68a9593955195869261ffff8c8360301c1692881c1690878501611634565b036118e9601f19918281018752866115d1565b8951948593630200057560e51b85528c8986015260248501526060604485015280519081606486015260005b828110611b585750506084848093601f84600085819786010152011681010301927f0000000000000000000000000000000000000000000000000000000000000000165af18015611b3057611b3b575b50845163fc2a88c360e01b8152838184818a5afa908115611b305790849291600091611b00575b506013556024856012541691875198899384928352868301525afa948515611af557600095611ac6575b508351946119c3866115b6565b60008652858580516119d481611585565b8381526000868201520152600f5560ff1994856010541660105580519160018060401b038311611ab157600160401b8311611ab157508290611a1c8360115481601155611792565b016011600052826000208760005b848110611a9f575050505050907fcc58b13ad3eab50626c6a6300b1d139cd6ebb1688a7cced9461c2f7e762665ee92916013549160125460301c16908351928352820152a160055416176005555b7fa82b306739f8d9062e2a6cb231e2b2b6010520832cf162c722a1c45c01ee3ce0600080a1565b85845194019381840155018890611a2a565b604190634e487b7160e01b6000525260246000fd5b90948282813d8311611aee575b611add81836115d1565b810103126105d257505193386119b6565b503d611ad3565b84513d6000823e3d90fd5b919282813d8311611b29575b611b1681836115d1565b810103126105d25750908391513861198c565b503d611b0c565b86513d6000823e3d90fd5b611b5190843d8611610c3d57610c2f81836115d1565b5038611965565b818101870151888201608401528a9688965001611915565b90938782813d8311611b98575b611b8781836115d1565b810103126105d257505192386118b1565b503d611b7d565b89513d6000823e3d90fd5b606492519162461bcd60e51b8352820152600c60248201526b185b1c9958591e481cd95b9d60a21b6044820152fd5b93509394508080965096919690611c95575b15611c05575050505050600e55611c00611ddb565b611a78565b84611c88575b84611c43575b50505050611c20575b50611a78565b611c28611f93565b60ff1960055416600555600e55611c3d611ddb565b38611c1a565b929594935090916001600160ff1b0382168203611c7357611c68939495501b90611758565b421138808080611c11565b601186634e487b7160e01b6000525260246000fd5b60055460ff169450611c0b565b50835415611beb565b5060135415611844565b508554151561183e565b90948582813d8311611cda575b611cc981836115d1565b810103126105d2575051933861181b565b503d611cbf565b83513d6000823e3d90fd5b6001600160a01b039091168152602081019190915260400190565b600354811015611d2257600360005260206000200190600090565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b0316600090815260076020526040902060018101546008541080611d6c575b611d685750600090565b5490565b5060ff60095416611d5e565b90611d9f5750805115611d8d57805190602001fd5b604051630a12f52160e11b8152600490fd5b81511580611dd2575b611db0575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b15611da8565b600d5480611f85575b50600e5480611efe575b50600c5460ff8116611dfd5750565b60095460ff8082161516918260ff198093161760095516600c55600014611ed757600254604051631e14e34360e31b815290602090829060049082906001600160a01b03165afa908115611ecb57600091611e99575b506040611e847ff630826129f37a41796127d038cfae85fe120359ddd6f9b0da24ee9e8ea8d8ac92600b5490611758565b80600655600a549082519182526020820152a1565b906020823d8211611ec3575b81611eb2602093836115d1565b810103126105d25750516040611e53565b3d9150611ea5565b6040513d6000823e3d90fd5b7f6f233cde5f47c07fa0d9895c54d2d2a3ab36f565c605df426bdd460a5bf3ea59600080a1565b600254604051631e14e34360e31b815290602090829060049082906001600160a01b03165afa8015611ecb578290600090611f4f575b611f3e9250611758565b600655600b556000600e5538611dee565b90506020823d8211611f7d575b81611f69602093836115d1565b810103126105d2575081611f3e9151611f34565b3d9150611f5c565b600a556000600d5538611de4565b60008060135560405190611fa6826115b6565b80825260405190611fb682611585565b8082528260406020938385820152015280600f5560ff196010541660105582519260018060401b03841161202c57600160401b841161202c5782906120018560115481601155611792565b019160118252808220915b84811061201a575050505050565b8351838201559281019260010161200c565b634e487b7160e01b82526041600452602482fdfef311bc4329e14d73a88accc37c5045c49a8f9a4e7d84c49c2f657b6fc13e5e348be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0a164736f6c6343000814000a0000000000000000000000006f43ff82cca38001b6699a8ac47a2d0e66939407000000000000000000000000eda5b00fb33b13c730d004cf5d1ada1ac191ddc2

Deployed Bytecode

0x60406080815260048036101561001457600080fd5b6000803560e01c8063149d8b8e146113d15780631fe543e31461116557806332b7083b146111305780633aeac4e114610f7b5780633e16d3dd14610efc57806340b2268314610e8d578063711332dc146109c0578063715018a61461096d57806379ba5097146108fd57806380017f4e146108d05780638942fc311461089f5780638da5cb5b14610877578063903d0d7e1461083857806393b886c1146107e9578063a189b421146107bf578063a48debd11461075e578063a527913e14610735578063b6ec155b146106e1578063b9c5a9781461068e578063e30c397814610665578063e8177b8614610642578063f2fde38b146105d55763fdad3a5f1461011c57600080fd5b346105d257826003193601126105d25781359261013761160f565b6002546001600160a01b0395908616610151338214611683565b60ff60095416156105995760ff6005541661056e57600a549283831061053b5782938015610528578080850494116104fb575b50845163c34fe1dd60e01b815297602092838a8a81845afa998a156104f157848a9b84928b9c9b916104d4575b5060248a51809481936370a0823160e01b8352818a169e8f90840152165afa908115610434579087918b916104a3575b501061046b57865163c34fe1dd60e01b815284818c81855afa908115610434578a9161043e575b50848960448d868c519586948593636eb1769f60e11b8552840152876024840152165afa908115610434579087918b916103ff575b50106103c557803b156103c157888661026d9285838e8c5196879586948593639e28bdcf60e01b85528401611cec565b03925af180156103b75761038a575b508390885b82811061031c57505061029b6102a0926003548b55611d38565b611758565b8451908186016001600160401b03811183821017610307577feb94184f338c573d1df2cb195ce1a83fc8fc5ffcc64d03de272232fb7aa183cf97989950865281526001828201428152888a5260078452868a2092518355519101558351928352820152a280f35b60418a634e487b7160e01b6000525260246000fd5b90915060038054600160401b8110156103775780600161033e92018355611d07565b81929154911b90848b831b921b1916179055600019811461036457600101908491610281565b634e487b7160e01b895260118a52602489fd5b634e487b7160e01b8b5260418c5260248bfd5b9097906001600160401b0381116103a4578652963861027c565b634e487b7160e01b825260418a52602482fd5b87513d8b823e3d90fd5b8880fd5b865162461bcd60e51b8152808b0185905260146024820152734e6f7420656e6f75676820616c6c6f77616e636560601b6044820152606490fd5b809250868092503d831161042d575b61041881836115d1565b81010312610429578690513861023d565b8980fd5b503d61040e565b88513d8c823e3d90fd5b61045e9150853d8711610464575b61045681836115d1565b8101906116c1565b38610208565b503d61044c565b865162461bcd60e51b8152808b0185905260126024820152714e6f7420656e6f7567682062616c616e636560701b6044820152606490fd5b809250868092503d83116104cd575b6104bc81836115d1565b8101031261042957869051386101e1565b503d6104b2565b6104eb9150823d84116104645761045681836115d1565b386101b1565b87513d8a823e3d90fd5b809194508302908382041483151715610515579238610184565b634e487b7160e01b865260118752602486fd5b634e487b7160e01b875260128852602487fd5b845162461bcd60e51b8152602081890152600d60248201526c507269636520746f6f206c6f7760981b6044820152606490fd5b835162461bcd60e51b81526020818801526005602482015264115b99195960da1b6044820152606490fd5b835162461bcd60e51b815260208188015260136024820152724769766561776179206e6f742061637469766560681b6044820152606490fd5b80fd5b50346105d25760203660031901126105d2576105ef6115f4565b6105f7611657565b600180546001600160a01b0319166001600160a01b0392831690811790915582549091167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e227008380a380f35b5082346106615781600319360112610661576020906008549051908152f35b5080fd5b50823461066157816003193601126106615760015490516001600160a01b039091168152602090f35b509190346106dd5760203660031901126106dd577f68bff35e46d3206e18452a58ab63d15ddf725d0b38dfb1df3d4dab8e77f5d5659160209135906106d1611657565b81600e5551908152a180f35b8280fd5b50823461066157816003193601126106615760c09060ff6009541690600a5490600b5460ff600c541690600d5492600e549481519615158752602087015285015215156060840152608083015260a0820152f35b50823461066157816003193601126106615760025490516001600160a01b039091168152602090f35b50823461066157816003193601126106615760209061078860018060a01b03600254163314611683565b60ff6005541690816107b2575b81156107a4575b519015158152f35b60095460ff1615915061079c565b60105460ff169150610795565b8382346105d257806003193601126105d25750600f5460ff60105416825191825215156020820152f35b509190346106dd5760203660031901126106dd577ff56ae34a736154a570ed8935e3feb13fc99a990bfd5c7ad6486f4cb126859f9691602091359061082c611657565b81600d5551908152a180f35b50823461066157816003193601126106615761087390601254905191829163ffffffff90818160301c169161ffff8260201c16911684611634565b0390f35b508234610661578160031936011261066157905490516001600160a01b039091168152602090f35b50346105d257806003193601126105d2576108c560018060a01b03600254163314611683565b6108cd6117d3565b80f35b508234610661576020366003190112610661576020906108f66108f16115f4565b611d38565b9051908152f35b5091346106dd57826003193601126106dd57600154916001600160a01b039133838516036109565750506001600160a01b031991821660015582543392811683178455166000805160206120618339815191528380a380f35b60249250519063118cdaa760e01b82523390820152fd5b50346105d257806003193601126105d257610986611657565b600180546001600160a01b03199081169091558154908116825581906001600160a01b03166000805160206120618339815191528280a380f35b5091346106dd57806003193601126106dd576109da611625565b600254602435916001600160a01b03918216916109f8338414611683565b60ff928360095416808091610e82575b15610d3a575050826005541615610d0b578554958615610cd75760115415610cc45787906011825260209788832054069280610a4385611d07565b90549060031b1c1694600014610c7757600254885163996c6cc360e01b81529082168a828581845afa918215610c6d57610aa1928887868f958f8f968496610c4e575b50516323b872dd60e01b8152978896879586938d85016116f8565b0393165af1908115610c445791610ac28b9285948791610c17575b5061171a565b60ff196005541660055560025416895192838092631e14e34360e31b82525afa908115610c0d578391610bd6575b509287926000805160206120418339815191529992610b337f826bbd0fbc2baeb2244f7b5226338393b58c345e7dac64b39391b4e3b9b3813996600b5490611758565b600655818551610b42816115b6565b526003548260035580610bbd575b506003825255610b5e611f93565b42600855825191878352820152a25b80600954168015908115610ba4575b50610b97575b6009541690825191151582526020820152a180f35b610b9f611ddb565b610b82565b905080610bb2575b38610b7c565b508060105416610bac565b60038352848320610bd09181019061177b565b38610b50565b9298809294915083813d8311610c06575b610bf181836115d1565b810103126103c1579151909791929087610af0565b503d610be7565b88513d85823e3d90fd5b610c379150843d8611610c3d575b610c2f81836115d1565b8101906116e0565b38610abc565b503d610c25565b89513d86823e3d90fd5b610c66919650883d8a116104645761045681836115d1565b9438610a86565b8a513d87823e3d90fd5b600254885163c34fe1dd60e01b81529082168a828581845afa918215610c6d57610aa1928887868f958f8f968496610c4e5750516323b872dd60e01b8152978896879586938d85016116f8565b634e487b7160e01b885260329052602487fd5b606490602087519162461bcd60e51b8352820152600e60248201526d30207061727469636970616e747360901b6044820152fd5b845162461bcd60e51b81526020818801526009602482015268546f6f206561726c7960b81b6044820152606490fd5b9290919215610d5d575b5050506000805160206120418339815191529350610b6d565b15610e3457508060025416948685519263996c6cc360e01b845260209788858581845afa948515610c0d5792610db795928a959289958395610e15575b508a516323b872dd60e01b815297889687958693309185016116f8565b0393165af1948515610e0b5794610de691600080516020612041833981519152968892610dee575b505061171a565b388080610d44565b610e049250803d10610c3d57610c2f81836115d1565b3880610ddf565b84513d88823e3d90fd5b610e2d919550873d89116104645761045681836115d1565b9338610d9a565b948685519263c34fe1dd60e01b845260209788858581845afa948515610c0d5792610db795928a959289958395610e1557508a516323b872dd60e01b815297889687958693309185016116f8565b508460105416610a08565b5082346106615760203660031901126106615760207f1647043f27b6ab91a262baccf5e4b2dd4085e0ecea7e9885b6f6d329a0a6851a91610ecc6115f4565b610ed4611657565b600280546001600160a01b0319166001600160a01b039290921691821790559051908152a180f35b5082346106615760203660031901126106615760207fd1eac0a1c82fb301a1760f4bbacb6db64acaf2e4eebb26b7b1d11d267498a88c91610f3b611625565b90610f44611657565b6009549115159160ff1615158203610f695760ff19600c5416600c555b51908152a180f35b600160ff19600c541617600c55610f61565b509034610661578260031936011261066157610f956115f4565b90610f9e61160f565b91610fa7611657565b84516370a0823160e01b815230838201526001600160a01b039190911692909160208084602481885afa9384156111265786946110f3575b508651868083830163a9059cbb60e01b815283611000898960248401611cec565b0393611014601f19958681018352826115d1565b5190828a5af13d156110e6573d6001600160401b0381116110d357906110579291611048858c5194601f84011601846115d1565b82523d898584013e5b87611d78565b80519182151592836110ba575b5050506110a3575061109d7f11dd463c4f2edd676b85f050130c7ab2f5832f52becb5444b09a92404aa1498a9394955192839283611cec565b0390a280f35b8551635274afe760e01b8152908101849052602490fd5b6110ca93508201810191016116e0565b15388080611064565b634e487b7160e01b895260418552602489fd5b6110579150606090611051565b9080945081813d831161111f575b61110b81836115d1565b8101031261111b57519238610fdf565b8580fd5b503d611101565b87513d88823e3d90fd5b509190346106dd57826003193601126106dd5760609250549060ff600554169060065491815193845215156020840152820152f35b5090346106615782600319360112610661576024928335916001600160401b039190803583851161111b573660238601121561111b5784820135938085116113bf578460051b978451966020966111be888c018a6115d1565b885286880182819b830101913683116113bb5783899101915b8383106113ab575050507f000000000000000000000000eda5b00fb33b13c730d004cf5d1ada1ac191ddc26001600160a01b03163303905061136a57600f54156113355760135483036112fe576001938460ff19601054161760105587519283116112ed57600160401b83116112ed57505061125d816011969493965481601155611792565b8760118852838820885b8381106112dc5750505050600f54938351956060870191875260608488015251809152608086019792875b8281106112c957887f147eb1ff0c82f87f2b03e2c43f5a36488ff63ec6b730195fde4605f612f8db5189808d8b8b8301520390a180f35b84518a5298810198938101938301611292565b825182820155918501918401611267565b634e487b7160e01b89526041905287fd5b8360136064928888519362461bcd60e51b8552840152820152720e4cae2eacae6e840d2c840dad2e6dac2e8c6d606b1b6044820152fd5b8360116064928888519362461bcd60e51b8552840152820152701c995c5d595cdd081b9bdd08199bdd5b99607a1b6044820152fd5b83601f6064928888519362461bcd60e51b85528401528201527f6f6e6c792056524620563220777261707065722063616e2066756c66696c6c006044820152fd5b82358152918101918991016111d7565b8a80fd5b634e487b7160e01b8752604183528787fd5b5082346106615781600319360112610661576002546001600160a01b0393906113fd9085163314611683565b611405611ddb565b815190611411826115b6565b83825284600254169183518093631e14e34360e31b8252818460209687935afa801561157b57869061154c575b61144c9150600b5490611758565b8451909290916001600160401b036080840181811185821017611539578752818452858401918883526060888601958a875201958652805191821161153957600160401b82116115395786906003548360035580841061151e575b500160038952868920895b83811061150a57505050507fc848b8758eef2575b37a79281d279c80506ed2e7fdfc9d6a1d61da8b60e9c68d96975051905551151560ff8019600554169116176005555180600655600a54918351928352820152a180f35b82518c1682820155918801916001016114b2565b60038b52828b2061153391810190850161177b565b8b6114a7565b634e487b7160e01b895260418452602489fd5b508381813d8311611574575b61156281836115d1565b8101031261111b5761144c905161143e565b503d611558565b85513d88823e3d90fd5b606081019081106001600160401b038211176115a057604052565b634e487b7160e01b600052604160045260246000fd5b602081019081106001600160401b038211176115a057604052565b601f909101601f19168101906001600160401b038211908210176115a057604052565b600435906001600160a01b038216820361160a57565b600080fd5b602435906001600160a01b038216820361160a57565b60043590811515820361160a57565b63ffffffff918216815261ffff9092166020830152909116604082015260600190565b6000546001600160a01b0316330361166b57565b60405163118cdaa760e01b8152336004820152602490fd5b1561168a57565b60405162461bcd60e51b815260206004820152600f60248201526e4f6e6c792053696c7665724665657360881b6044820152606490fd5b9081602091031261160a57516001600160a01b038116810361160a5790565b9081602091031261160a5751801515810361160a5790565b6001600160a01b03918216815291166020820152604081019190915260600190565b1561172157565b60405162461bcd60e51b815260206004820152600f60248201526e151c985b9cd9995c8819985a5b1959608a1b6044820152606490fd5b9190820180921161176557565b634e487b7160e01b600052601160045260246000fd5b818110611786575050565b6000815560010161177b565b80821061179d575050565b60116000526117d1917f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68918201910161177b565b565b60ff60095416156117d1576002546040805163179bbedf60e21b8152916001600160a01b039190600490602090819086908490829088165afa948515611ce157600095611cb2575b50600b5460019561182e81881c42611758565b6006548091101591828093611ca8575b80611c9e575b15611bd95750505050601354611baa576012549163ffffffff9283811690867f000000000000000000000000eda5b00fb33b13c730d004cf5d1ada1ac191ddc216968651916310c1b4d560e21b93848452808685015286846024818d5afa938415611b9f57600094611b70575b509186926118d68a9593955195869261ffff8c8360301c1692881c1690878501611634565b036118e9601f19918281018752866115d1565b8951948593630200057560e51b85528c8986015260248501526060604485015280519081606486015260005b828110611b585750506084848093601f84600085819786010152011681010301927f0000000000000000000000006f43ff82cca38001b6699a8ac47a2d0e66939407165af18015611b3057611b3b575b50845163fc2a88c360e01b8152838184818a5afa908115611b305790849291600091611b00575b506013556024856012541691875198899384928352868301525afa948515611af557600095611ac6575b508351946119c3866115b6565b60008652858580516119d481611585565b8381526000868201520152600f5560ff1994856010541660105580519160018060401b038311611ab157600160401b8311611ab157508290611a1c8360115481601155611792565b016011600052826000208760005b848110611a9f575050505050907fcc58b13ad3eab50626c6a6300b1d139cd6ebb1688a7cced9461c2f7e762665ee92916013549160125460301c16908351928352820152a160055416176005555b7fa82b306739f8d9062e2a6cb231e2b2b6010520832cf162c722a1c45c01ee3ce0600080a1565b85845194019381840155018890611a2a565b604190634e487b7160e01b6000525260246000fd5b90948282813d8311611aee575b611add81836115d1565b810103126105d257505193386119b6565b503d611ad3565b84513d6000823e3d90fd5b919282813d8311611b29575b611b1681836115d1565b810103126105d25750908391513861198c565b503d611b0c565b86513d6000823e3d90fd5b611b5190843d8611610c3d57610c2f81836115d1565b5038611965565b818101870151888201608401528a9688965001611915565b90938782813d8311611b98575b611b8781836115d1565b810103126105d257505192386118b1565b503d611b7d565b89513d6000823e3d90fd5b606492519162461bcd60e51b8352820152600c60248201526b185b1c9958591e481cd95b9d60a21b6044820152fd5b93509394508080965096919690611c95575b15611c05575050505050600e55611c00611ddb565b611a78565b84611c88575b84611c43575b50505050611c20575b50611a78565b611c28611f93565b60ff1960055416600555600e55611c3d611ddb565b38611c1a565b929594935090916001600160ff1b0382168203611c7357611c68939495501b90611758565b421138808080611c11565b601186634e487b7160e01b6000525260246000fd5b60055460ff169450611c0b565b50835415611beb565b5060135415611844565b508554151561183e565b90948582813d8311611cda575b611cc981836115d1565b810103126105d2575051933861181b565b503d611cbf565b83513d6000823e3d90fd5b6001600160a01b039091168152602081019190915260400190565b600354811015611d2257600360005260206000200190600090565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b0316600090815260076020526040902060018101546008541080611d6c575b611d685750600090565b5490565b5060ff60095416611d5e565b90611d9f5750805115611d8d57805190602001fd5b604051630a12f52160e11b8152600490fd5b81511580611dd2575b611db0575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b15611da8565b600d5480611f85575b50600e5480611efe575b50600c5460ff8116611dfd5750565b60095460ff8082161516918260ff198093161760095516600c55600014611ed757600254604051631e14e34360e31b815290602090829060049082906001600160a01b03165afa908115611ecb57600091611e99575b506040611e847ff630826129f37a41796127d038cfae85fe120359ddd6f9b0da24ee9e8ea8d8ac92600b5490611758565b80600655600a549082519182526020820152a1565b906020823d8211611ec3575b81611eb2602093836115d1565b810103126105d25750516040611e53565b3d9150611ea5565b6040513d6000823e3d90fd5b7f6f233cde5f47c07fa0d9895c54d2d2a3ab36f565c605df426bdd460a5bf3ea59600080a1565b600254604051631e14e34360e31b815290602090829060049082906001600160a01b03165afa8015611ecb578290600090611f4f575b611f3e9250611758565b600655600b556000600e5538611dee565b90506020823d8211611f7d575b81611f69602093836115d1565b810103126105d2575081611f3e9151611f34565b3d9150611f5c565b600a556000600d5538611de4565b60008060135560405190611fa6826115b6565b80825260405190611fb682611585565b8082528260406020938385820152015280600f5560ff196010541660105582519260018060401b03841161202c57600160401b841161202c5782906120018560115481601155611792565b019160118252808220915b84811061201a575050505050565b8351838201559281019260010161200c565b634e487b7160e01b82526041600452602482fdfef311bc4329e14d73a88accc37c5045c49a8f9a4e7d84c49c2f657b6fc13e5e348be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0a164736f6c6343000814000a

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

0000000000000000000000006f43ff82cca38001b6699a8ac47a2d0e66939407000000000000000000000000eda5b00fb33b13c730d004cf5d1ada1ac191ddc2

-----Decoded View---------------
Arg [0] : _link (address): 0x6F43FF82CCA38001B6699a8AC47A2d0E66939407
Arg [1] : _wrapper (address): 0xeDA5B00fB33B13c730D004Cf5D1aDa1ac191Ddc2

-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 0000000000000000000000006f43ff82cca38001b6699a8ac47a2d0e66939407
Arg [1] : 000000000000000000000000eda5b00fb33b13c730d004cf5d1ada1ac191ddc2


Block Transaction Gas Used Reward
view all blocks produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
[ Download: CSV Export  ]

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