false
true
0

Contract Address Details

0xf32B9398a7277609772F328Fc2005D7DA5420E77

Token
SparkSwap LPs (SparkSwap-LP)
Creator
0x955219–6906cd at 0xf613cb–d356b2
Balance
0 PLS ( )
Tokens
Fetching tokens...
Transactions
1,374 Transactions
Transfers
0 Transfers
Gas Used
0
Last Balance Update
25869690
Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB 0x207288d32a108dfa89733b6e7354da0a8e768973.
All metadata displayed below is from that contract. In order to verify current contract, click Verify & Publish button
Verify & Publish
Contract name:
SparkSwapPair




Optimization enabled
true
Compiler version
v0.8.19+commit.7dd6d404




Optimization runs
200
Verified at
2023-11-03T15:25:35.849673Z

contracts/SparkSwapPair.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

import "./SparkSwapERC20.sol";
import "./abstracts/Constants.sol";

import "./interfaces/IERC20.sol";
import "./interfaces/ISparkSwapPair.sol";
import "./interfaces/ISparkSwapFactory.sol";
import "./interfaces/ISparkSwapCallee.sol";

import "./libraries/Math.sol";
import "./libraries/UQ112x112.sol";

contract SparkSwapPair is ISparkSwapPair, SparkSwapERC20, Constants {
    using UQ112x112 for uint224;

    bytes4 private constant SELECTOR = bytes4(keccak256(bytes("transfer(address,uint256)")));
    uint256 public constant MINIMUM_LIQUIDITY = 10 ** 3;
    uint256 public constant MAX_FEE = 100;
    uint256 public constant MAX_PROTOCOL_SHARE = 100;

    uint256 public fee;
    uint256 public protocolShare;
    address public factory;
    address public token0;
    address public token1;

    uint256 public price0CumulativeLast;
    uint256 public price1CumulativeLast;
    uint256 public kLast;

    uint256 internal decimals0;
    uint256 internal decimals1;

    uint256 private blockTimestampLast;
    uint112 private reserve0;
    uint112 private reserve1;
    uint256 private unlocked = 1;

    function getAmountIn(uint256 amountOut, address tokenIn, address caller) public view returns (uint256 amountIn) {
        (uint256 _reserve0, uint256 _reserve1, ) = getReserves();
        require(amountOut > 0, "SparkSwapPair: INSUFFICIENT_INPUT_AMOUNT");
        require(_reserve0 > 0 && _reserve1 > 0, "SparkSwapPair: INSUFFICIENT_LIQUIDITY");
        if (tokenIn == token1) (_reserve0, _reserve1) = (_reserve1, _reserve0);
        uint256 fee_ = ISparkSwapFactory(factory).feeWhitelistContains(caller) ? 0 : fee;
        uint256 numerator = _reserve0 * amountOut * DIVIDER;
        uint256 denominator = (_reserve1 - amountOut) * (DIVIDER - fee_);
        amountIn = (numerator / denominator) + 1;
    }

    function getAmountOut(uint256 amountIn, address tokenIn, address caller) public view returns (uint256 amountOut) {
        (uint256 _reserve0, uint256 _reserve1, ) = getReserves();
        require(amountIn > 0, "SparkSwapPair: INSUFFICIENT_INPUT_AMOUNT");
        require(_reserve0 > 0 && _reserve1 > 0, "SparkSwapPair: INSUFFICIENT_LIQUIDITY");
        uint256 fee_ = ISparkSwapFactory(factory).feeWhitelistContains(caller) ? 0 : fee;
        if (tokenIn == token1) (_reserve0, _reserve1) = (_reserve1, _reserve0);
        uint amountInWithFee = amountIn * (DIVIDER - fee_);
        uint numerator = amountInWithFee * _reserve1;
        uint denominator = (_reserve0 * DIVIDER) + amountInWithFee;
        amountOut = numerator / denominator;
    }

    function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint256 _blockTimestampLast) {
        _reserve0 = reserve0;
        _reserve1 = reserve1;
        _blockTimestampLast = blockTimestampLast;
    }

    constructor() {
        factory = msg.sender;
    }

    function burn(address to) external lock returns (uint256 amount0, uint256 amount1) {
        (uint112 _reserve0, uint112 _reserve1, ) = getReserves();
        address _token0 = token0;
        address _token1 = token1;
        uint256 balance0 = IERC20(_token0).balanceOf(address(this));
        uint256 balance1 = IERC20(_token1).balanceOf(address(this));
        uint256 liquidity = balanceOf[address(this)];
        bool feeOn = _mintFee(_reserve0, _reserve1);
        uint256 _totalSupply = totalSupply;
        amount0 = (liquidity * balance0) / _totalSupply;
        amount1 = (liquidity * balance1) / _totalSupply;
        require(amount0 > 0 && amount1 > 0, "SparkSwapPair: INSUFFICIENT_LIQUIDITY_BURNED");
        _burn(address(this), liquidity);
        _safeTransfer(_token0, to, amount0);
        _safeTransfer(_token1, to, amount1);
        balance0 = IERC20(_token0).balanceOf(address(this));
        balance1 = IERC20(_token1).balanceOf(address(this));
        _update(balance0, balance1, _reserve0, _reserve1);
        if (feeOn) kLast = uint256(reserve0) * reserve1;
        emit Burn(msg.sender, amount0, amount1, to);
    }

    function initialize(address _token0, address _token1) external onlyFactory {
        token0 = _token0;
        token1 = _token1;
        decimals0 = 10 ** IERC20(_token0).decimals();
        decimals1 = 10 ** IERC20(_token1).decimals();
    }

    function mint(address to) external lock returns (uint256 liquidity) {
        (uint112 _reserve0, uint112 _reserve1, ) = getReserves();
        uint256 balance0 = IERC20(token0).balanceOf(address(this));
        uint256 balance1 = IERC20(token1).balanceOf(address(this));
        uint256 amount0 = balance0 - _reserve0;
        uint256 amount1 = balance1 - _reserve1;
        bool feeOn = _mintFee(_reserve0, _reserve1);
        uint256 _totalSupply = totalSupply;
        if (_totalSupply == 0) {
            liquidity = Math.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
            _mint(address(0), MINIMUM_LIQUIDITY);
        } else {
            liquidity = Math.min((amount0 * _totalSupply) / _reserve0, (amount1 * _totalSupply) / _reserve1);
        }
        require(liquidity > 0, "SparkSwapPair: INSUFFICIENT_LIQUIDITY_MINTED");
        _mint(to, liquidity);
        _update(balance0, balance1, _reserve0, _reserve1);
        if (feeOn) kLast = uint256(reserve0) * reserve1;
        emit Mint(msg.sender, amount0, amount1);
    }

    function skim(address to) external onlyFactory lock {
        address _token0 = token0;
        address _token1 = token1;
        _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)) - reserve0);
        _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)) - reserve1);
    }

    function sync() external lock {
        _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
    }

    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external {
        _swap(amount0Out, amount1Out, to, address(0), data);
    }

    function swapFromPeriphery(
        uint256 amount0Out,
        uint256 amount1Out,
        address to,
        address caller,
        bytes calldata data
    ) external {
        require(
            ISparkSwapFactory(factory).peripheryWhitelistContains(msg.sender),
            "SparkSwapPair: Caller is not periphery"
        );
        _swap(amount0Out, amount1Out, to, caller, data);
    }

    function updateFee(uint256 fee_) external onlyFactory returns (bool) {
        require(fee_ <= MAX_FEE, "SparkSwapFactory: Fee gt MAX_FEE");
        fee = fee_;
        emit FeeUpdated(fee_);
        return true;
    }

    function updateProtocolShare(uint256 share) external onlyFactory returns (bool) {
        require(share <= MAX_PROTOCOL_SHARE, "SparkSwapFactory: Share gt MAX_PROTOCOL_SHARE");
        protocolShare = share;
        emit ProtocolShareUpdated(share);
        return true;
    }

    function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
        address feeTo = ISparkSwapFactory(factory).feeTo();
        feeOn = feeTo != address(0) && protocolShare > 0;
        uint256 _kLast = kLast;
        if (feeOn) {
            if (_kLast != 0) {
                uint256 rootK = Math.sqrt(uint256(_reserve0) * _reserve1);
                uint256 rootKLast = Math.sqrt(_kLast);
                if (rootK > rootKLast) {
                    uint256 numerator = (totalSupply * (rootK - rootKLast)) * protocolShare;
                    uint256 denominator = (rootK * (MAX_PROTOCOL_SHARE - protocolShare)) + (rootKLast * protocolShare);
                    uint256 liquidity = numerator / denominator;
                    if (liquidity > 0) _mint(feeTo, liquidity);
                }
            }
        } else if (_kLast != 0) {
            kLast = 0;
        }
    }

    function _safeTransfer(address token, address to, uint256 value) private {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "SparkSwapPair: TRANSFER_FAILED");
    }

    function _swap(
        uint256 amount0Out,
        uint256 amount1Out,
        address to,
        address caller,
        bytes calldata data
    ) private lock {
        ISparkSwapFactory factory_ = ISparkSwapFactory(factory);
        require(
            factory_.contractsWhitelistContains(address(0)) ||
                msg.sender == tx.origin ||
                factory_.contractsWhitelistContains(msg.sender),
            "SparkSwapPair: Caller is invalid"
        );
        require(amount0Out > 0 || amount1Out > 0, "SparkSwapPair: INSUFFICIENT_OUTPUT_AMOUNT");
        (uint112 _reserve0, uint112 _reserve1, ) = getReserves();
        require(amount0Out < _reserve0 && amount1Out < _reserve1, "SparkSwapPair: INSUFFICIENT_LIQUIDITY");
        uint256 balance0;
        uint256 balance1;
        {
            address _token0 = token0;
            address _token1 = token1;
            require(to != _token0 && to != _token1, "SparkSwapPair: INVALID_TO");
            if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out);
            if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out);
            if (data.length > 0) ISparkSwapCallee(to).SparkSwapCall(msg.sender, amount0Out, amount1Out, data);
            balance0 = IERC20(_token0).balanceOf(address(this));
            balance1 = IERC20(_token1).balanceOf(address(this));
        }
        uint256 amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
        uint256 amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
        require(amount0In > 0 || amount1In > 0, "SparkSwapPair: INSUFFICIENT_INPUT_AMOUNT");
        {
            uint256 fee_ = (caller != address(0) && factory_.feeWhitelistContains(caller)) ? 0 : fee;
            uint256 balance0Adjusted = (balance0 * DIVIDER) - (amount0In * fee_);
            uint256 balance1Adjusted = (balance1 * DIVIDER) - (amount1In * fee_);
            require(
                balance0Adjusted * balance1Adjusted >= (uint256(_reserve0) * _reserve1) * DIVIDER ** 2,
                "SparkSwapPair: K"
            );
        }
        _update(balance0, balance1, _reserve0, _reserve1);
        emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
    }

    function _update(uint256 balance0, uint256 balance1, uint112 _reserve0, uint112 _reserve1) private {
        require(balance0 <= type(uint112).max && balance1 <= type(uint112).max, "SparkSwapPair: OVERFLOW");
        uint256 blockTimestamp = block.timestamp % 2 ** 32;
        uint256 timeElapsed = blockTimestamp - blockTimestampLast;
        if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
            price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
            price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
        }
        reserve0 = uint112(balance0);
        reserve1 = uint112(balance1);
        blockTimestampLast = blockTimestamp;
        emit Sync(reserve0, reserve1);
    }

    modifier lock() {
        require(unlocked == 1, "SparkSwapPair: LOCKED");
        unlocked = 0;
        _;
        unlocked = 1;
    }

    modifier onlyFactory() {
        require(msg.sender == factory, "SparkSwapPair: Caller is not factory");
        _;
    }
}
        

contracts/interfaces/ISparkSwapPair.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

import "./ISparkSwapERC20.sol";

interface ISparkSwapPair is ISparkSwapERC20 {
    event Mint(address indexed sender, uint256 amount0, uint256 amount1);
    event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to);
    event FeeUpdated(uint256 fee);
    event ProtocolShareUpdated(uint256 share);
    event Swap(
        address indexed sender,
        uint256 amount0In,
        uint256 amount1In,
        uint256 amount0Out,
        uint256 amount1Out,
        address indexed to
    );
    event Sync(uint112 reserve0, uint112 reserve1);

    function MINIMUM_LIQUIDITY() external view returns (uint256);

    function MAX_FEE() external view returns (uint256);

    function MAX_PROTOCOL_SHARE() external view returns (uint256);

    function factory() external view returns (address);

    function fee() external view returns (uint256);

    function protocolShare() external view returns (uint256);

    function token0() external view returns (address);

    function token1() external view returns (address);

    function getAmountOut(uint256 amountIn, address tokenIn, address caller) external view returns (uint256 amountOut);

    function getAmountIn(uint256 amountOut, address tokenIn, address caller) external view returns (uint256 amountIn);

    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint256 blockTimestampLast);

    function price0CumulativeLast() external view returns (uint256);

    function price1CumulativeLast() external view returns (uint256);

    function kLast() external view returns (uint256);

    function mint(address to) external returns (uint256 liquidity);

    function burn(address to) external returns (uint256 amount0, uint256 amount1);

    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;

    function swapFromPeriphery(
        uint256 amount0Out,
        uint256 amount1Out,
        address to,
        address caller,
        bytes calldata data
    ) external;

    function skim(address to) external;

    function sync() external;

    function initialize(address, address) external;

    function updateFee(uint256 fee_) external returns (bool);

    function updateProtocolShare(uint256 share) external returns (bool);
}
          

contracts/interfaces/ISparkSwapRouter01.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

interface ISparkSwapRouter01 {
    function factory() external view returns (address);

    function WETH() external view returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);

    function addLiquidityETH(
        address token,
        uint256 amountTokenDesired,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) external payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity);

    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB);

    function removeLiquidityETH(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountToken, uint256 amountETH);

    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountA, uint256 amountB);

    function removeLiquidityETHWithPermit(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountToken, uint256 amountETH);

    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapTokensForExactTokens(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapExactETHForTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable returns (uint256[] memory amounts);

    function swapTokensForExactETH(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapExactTokensForETH(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapETHForExactTokens(
        uint256 amountOut,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable returns (uint256[] memory amounts);

    function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) external pure returns (uint256 amountB);

    function getAmountOut(
        uint256 amountIn,
        address tokenIn,
        address tokenOut,
        address caller
    ) external view returns (uint256 amountOut);

    function getAmountIn(
        uint256 amountOut,
        address tokenIn,
        address tokenOut,
        address caller
    ) external view returns (uint256 amountIn);

    function getAmountsOut(
        uint256 amountIn,
        address[] calldata path,
        address caller
    ) external view returns (uint256[] memory amounts);

    function getAmountsIn(
        uint256 amountOut,
        address[] calldata path,
        address caller
    ) external view returns (uint256[] memory amounts);
}
          

@openzeppelin/contracts/access/Ownable.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../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.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

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

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @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 {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @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 {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _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);
    }
}
          

@openzeppelin/contracts/token/ERC20/ERC20.sol

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

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.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}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * 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.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => 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 override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override 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 override returns (uint8) {
        return 18;
    }

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

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override 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 `amount`.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

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

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` 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 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        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 `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `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.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _balances[account] += amount;
        }
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
            // Overflow not possible: amount <= accountBalance <= totalSupply.
            _totalSupply -= amount;
        }

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` 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.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

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

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}
          

@openzeppelin/contracts/token/ERC20/IERC20.sol

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

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
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);
}
          

@openzeppelin/contracts/utils/Context.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @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;
    }
}
          

@openzeppelin/contracts/utils/structs/EnumerableSet.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.0;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position of the value in the `values` array, plus 1 because index 0
        // means a value is not in the set.
        mapping(bytes32 => uint256) _indexes;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We read and store the value's index to prevent multiple reads from the same storage slot
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

            if (lastIndex != toDeleteIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastValue;
                // Update the index for the moved value
                set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the index for the deleted slot
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}
          

contracts/SparkSwapERC20.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

import "./interfaces/ISparkSwapERC20.sol";

contract SparkSwapERC20 is ISparkSwapERC20 {
    // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;

    string public constant name = "SparkSwap LPs";
    string public constant symbol = "SparkSwap-LP";
    uint8 public constant decimals = 18;

    bytes32 public override DOMAIN_SEPARATOR;

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;
    mapping(address => uint256) public nonces;

    constructor() {
        uint256 chainId;
        assembly {
            chainId := chainid()
        }
        DOMAIN_SEPARATOR = keccak256(
            abi.encode(
                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                keccak256(bytes(name)),
                keccak256(bytes("1")),
                chainId,
                address(this)
            )
        );
    }

    function _mint(address to, uint256 value) internal {
        totalSupply = totalSupply + value;
        balanceOf[to] = balanceOf[to] + value;
        emit Transfer(address(0), to, value);
    }

    function _burn(address from, uint256 value) internal {
        balanceOf[from] = balanceOf[from] - value;
        totalSupply = totalSupply - value;
        emit Transfer(from, address(0), value);
    }

    function _approve(address owner, address spender, uint256 value) private {
        allowance[owner][spender] = value;
        emit Approval(owner, spender, value);
    }

    function _transfer(address from, address to, uint256 value) private {
        balanceOf[from] = balanceOf[from] - value;
        balanceOf[to] = balanceOf[to] + value;
        emit Transfer(from, to, value);
    }

    function approve(address spender, uint256 value) external returns (bool) {
        _approve(msg.sender, spender, value);
        return true;
    }

    function transfer(address to, uint256 value) external returns (bool) {
        _transfer(msg.sender, to, value);
        return true;
    }

    function transferFrom(address from, address to, uint256 value) external returns (bool) {
        if (allowance[from][msg.sender] != type(uint256).max) {
            allowance[from][msg.sender] = allowance[from][msg.sender] - value;
        }
        _transfer(from, to, value);
        return true;
    }

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external {
        require(deadline >= block.timestamp, "SparkSwapERC20: EXPIRED");
        bytes32 digest = keccak256(
            abi.encodePacked(
                "\x19\x01",
                DOMAIN_SEPARATOR,
                keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
            )
        );
        address recoveredAddress = ecrecover(digest, v, r, s);
        require(recoveredAddress != address(0) && recoveredAddress == owner, "SparkSwapERC20: INVALID_SIGNATURE");
        _approve(owner, spender, value);
    }
}
          

contracts/SparkSwapFactory.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import "./interfaces/ISparkSwapFactory.sol";
import "./SparkSwapPair.sol";

contract SparkSwapFactory is ISparkSwapFactory, Ownable {
    using EnumerableSet for EnumerableSet.AddressSet;

    EnumerableSet.AddressSet private _feeWhitelist;
    EnumerableSet.AddressSet private _peripheryWhitelist;
    EnumerableSet.AddressSet private _contractsWhitelist;

    bytes32 public constant INIT_CODE_PAIR_HASH = keccak256(abi.encodePacked(type(SparkSwapPair).creationCode));

    uint256 public fee;
    address public feeTo;
    address public feeToSetter;
    uint256 public protocolShare;

    address[] public allPairs;

    mapping(address => mapping(address => address)) public getPair;

    function allPairsLength() external view returns (uint256) {
        return allPairs.length;
    }

    function contractsWhitelistList(uint256 offset, uint256 limit) external view returns (address[] memory output) {
        uint256 contractsWhitelistLength = _contractsWhitelist.length();
        if (offset >= contractsWhitelistLength) return new address[](0);
        uint256 to = offset + limit;
        if (contractsWhitelistLength < to) to = contractsWhitelistLength;
        output = new address[](to - offset);
        for (uint256 i = 0; i < output.length; i++) output[i] = _contractsWhitelist.at(offset + i);
    }

    function contractsWhitelist(uint256 index) external view returns (address) {
        return _contractsWhitelist.at(index);
    }

    function contractsWhitelistContains(address contract_) external view returns (bool) {
        return _contractsWhitelist.contains(contract_);
    }

    function contractsWhitelistCount() external view returns (uint256) {
        return _contractsWhitelist.length();
    }

    function feeWhitelistList(uint256 offset, uint256 limit) external view returns (address[] memory output) {
        uint256 feeWhitelistLength = _feeWhitelist.length();
        if (offset >= feeWhitelistLength) return new address[](0);
        uint256 to = offset + limit;
        if (feeWhitelistLength < to) to = feeWhitelistLength;
        output = new address[](to - offset);
        for (uint256 i = 0; i < output.length; i++) output[i] = _feeWhitelist.at(offset + i);
    }

    function feeWhitelist(uint256 index) external view returns (address) {
        return _feeWhitelist.at(index);
    }

    function feeWhitelistContains(address account) external view returns (bool) {
        return _feeWhitelist.contains(account);
    }

    function feeWhitelistCount() external view returns (uint256) {
        return _feeWhitelist.length();
    }

    function peripheryWhitelistList(uint256 offset, uint256 limit) external view returns (address[] memory output) {
        uint256 peripheryWhitelistLength = _peripheryWhitelist.length();
        if (offset >= peripheryWhitelistLength) return new address[](0);
        uint256 to = offset + limit;
        if (peripheryWhitelistLength < to) to = peripheryWhitelistLength;
        output = new address[](to - offset);
        for (uint256 i = 0; i < output.length; i++) output[i] = _peripheryWhitelist.at(offset + i);
    }

    function peripheryWhitelist(uint256 index) external view returns (address) {
        return _peripheryWhitelist.at(index);
    }

    function peripheryWhitelistContains(address account) external view returns (bool) {
        return _peripheryWhitelist.contains(account);
    }

    function peripheryWhitelistCount() external view returns (uint256) {
        return _peripheryWhitelist.length();
    }

    constructor(address _feeToSetter) {
        feeToSetter = _feeToSetter;
    }

    function createPair(address tokenA, address tokenB) external onlyOwner returns (address pair) {
        require(tokenA != tokenB, "SparkSwapFactory: IDENTICAL_ADDRESSES");
        (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        require(token0 != address(0), "SparkSwapFactory: ZERO_ADDRESS");
        require(getPair[token0][token1] == address(0), "SparkSwapFactory: PAIR_EXISTS");
        bytes memory bytecode = type(SparkSwapPair).creationCode;
        bytes32 salt = keccak256(abi.encodePacked(token0, token1));
        assembly {
            pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
        }
        ISparkSwapPair pair_ = ISparkSwapPair(pair);
        pair_.initialize(token0, token1);
        pair_.updateFee(fee);
        pair_.updateProtocolShare(protocolShare);
        getPair[token0][token1] = pair;
        getPair[token1][token0] = pair;
        allPairs.push(pair);
        emit PairCreated(token0, token1, pair, allPairs.length);
    }

    function addContractsWhitelist(address[] memory contracts) external onlyOwner returns (bool) {
        for (uint256 i = 0; i < contracts.length; i++) {
            require(contracts[i] != address(0), "SparkSwapFactory: Contract is zero address");
            _contractsWhitelist.add(contracts[i]);
        }
        emit ContractsWhitelistAdded(contracts);
        return true;
    }

    function addFeeWhitelist(address[] memory accounts) external onlyOwner returns (bool) {
        for (uint256 i = 0; i < accounts.length; i++) {
            require(accounts[i] != address(0), "SparkSwapFactory: Account is zero address");
            _feeWhitelist.add(accounts[i]);
        }
        emit FeeWhitelistAdded(accounts);
        return true;
    }

    function addPeripheryWhitelist(address[] memory periphery) external onlyOwner returns (bool) {
        for (uint256 i = 0; i < periphery.length; i++) {
            require(periphery[i] != address(0), "SparkSwapFactory: Periphery is zero address");
            _peripheryWhitelist.add(periphery[i]);
        }
        emit PeripheryWhitelistAdded(periphery);
        return true;
    }

    function removeContractsWhitelist(address[] memory contracts) external onlyOwner returns (bool) {
        for (uint256 i = 0; i < contracts.length; i++) {
            _contractsWhitelist.remove(contracts[i]);
        }
        emit ContractsWhitelistRemoved(contracts);
        return true;
    }

    function removeFeeWhitelist(address[] memory accounts) external onlyOwner returns (bool) {
        for (uint256 i = 0; i < accounts.length; i++) {
            _feeWhitelist.remove(accounts[i]);
        }
        emit FeeWhitelistRemoved(accounts);
        return true;
    }

    function removePeripheryWhitelist(address[] memory periphery) external onlyOwner returns (bool) {
        for (uint256 i = 0; i < periphery.length; i++) {
            _peripheryWhitelist.remove(periphery[i]);
        }
        emit PeripheryWhitelistRemoved(periphery);
        return true;
    }

    function skim(address token0, address token1, address to) external onlyOwner returns (bool) {
        require(to != address(0), "SparkSwapFactory: Recipient is zero address");
        ISparkSwapPair(getPair[token0][token1]).skim(to);
        emit Skimmed(token0, token1, to);
        return true;
    }

    function updateFee(uint256 fee_) external onlyOwner returns (bool) {
        fee = fee_;
        emit FeeUpdated(fee_);
        return true;
    }

    function updateProtocolShare(uint256 share) external onlyOwner returns (bool) {
        protocolShare = share;
        emit ProtocolShareUpdated(share);
        return true;
    }

    function updateFeePair(address token0, address token1, uint256 fee_) external onlyOwner returns (bool) {
        ISparkSwapPair(getPair[token0][token1]).updateFee(fee_);
        emit FeePairUpdated(token0, token1, fee_);
        return true;
    }

    function updateProtocolSharePair(address token0, address token1, uint256 share) external onlyOwner returns (bool) {
        ISparkSwapPair(getPair[token0][token1]).updateProtocolShare(share);
        emit ProtocolSharePairUpdated(token0, token1, share);
        return true;
    }

    function setFeeTo(address _feeTo) external {
        require(msg.sender == feeToSetter, "SparkSwapFactory: FORBIDDEN");
        feeTo = _feeTo;
    }

    function setFeeToSetter(address _feeToSetter) external {
        require(msg.sender == feeToSetter, "SparkSwapFactory: FORBIDDEN");
        feeToSetter = _feeToSetter;
    }
}
          

contracts/SparkSwapMigrator.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/ISparkSwapRouter01.sol";
import "./interfaces/ISparkSwapFactory.sol";
import "./interfaces/ISparkSwapPair.sol";
import "./libraries/TransferHelper.sol";

contract SparkSwapMigrator {
    ISparkSwapRouter01 public previousRouter;
    ISparkSwapFactory public previousFactory;
    ISparkSwapRouter01 public nextRouter;
    ISparkSwapFactory public nextFactory;

    constructor(address previousRouter_, address nextRouter_) {
        require(previousRouter_ != address(0), "SparkSwapMigrator: Previous router zero");
        require(nextRouter_ != address(0), "SparkSwapMigrator: Next router zero");
        previousRouter = ISparkSwapRouter01(previousRouter_);
        previousFactory = ISparkSwapFactory(previousRouter.factory());
        nextRouter = ISparkSwapRouter01(nextRouter_);
        nextFactory = ISparkSwapFactory(nextRouter.factory());
    }

    function migrate(address token0, address token1) external returns (bool) {
        address previousPairAddress = previousFactory.getPair(token0, token1);
        require(previousPairAddress != address(0), "SparkSwapMigrator: Invalid previous pair");
        uint256 availableAmount = ISparkSwapPair(previousPairAddress).balanceOf(msg.sender);
        require(availableAmount > 0, "SparkSwapMigrator: Available amount zero");
        TransferHelper.safeTransferFrom(previousPairAddress, msg.sender, address(this), availableAmount);
        TransferHelper.safeApprove(previousPairAddress, address(previousRouter), availableAmount);
        previousRouter.removeLiquidity(token0, token1, availableAmount, 1, 1, address(this), block.timestamp);
        uint256 token0Balance = 0;
        uint256 token1Balance = 0;
        (token0, token1, token0Balance, token1Balance) = _correctiveSwap(token0, token1);
        TransferHelper.safeApprove(token0, address(nextRouter), token0Balance);
        TransferHelper.safeApprove(token1, address(nextRouter), token1Balance);
        nextRouter.addLiquidity(token0, token1, token0Balance, token1Balance, 1, 1, msg.sender, block.timestamp);
        _returnAsset(token0);
        _returnAsset(token1);
        return true;
    }

    function _correctiveSwap(
        address token0_,
        address token1_
    ) private returns (address token0, address token1, uint256 token0Balance, uint256 token1Balance) {
        address nextPairAddress = nextFactory.getPair(token0, token1);
        require(nextPairAddress != address(0), "SparSwapMigrator: Invalid next pair");
        (token0, token1) = token0_ < token1_ ? (token0_, token1_) : (token1_, token0_);
        IERC20 token0ERC = IERC20(token0);
        IERC20 token1ERC = IERC20(token1);
        token0Balance = token0ERC.balanceOf(address(this));
        token1Balance = token1ERC.balanceOf(address(this));
        (uint256 reserve0, uint256 reserve1, ) = ISparkSwapPair(nextPairAddress).getReserves();
        uint256 swapAmount;
        address[] memory swapPath = new address[](2);
        if (reserve1 > (token1Balance * reserve0) / token0Balance) {
            swapPath[0] = token0;
            swapPath[1] = token1;
            swapAmount = uint256(
                (int256(reserve0) * int256(token1Balance * reserve0 - token0Balance * reserve1) * int256(-1)) /
                    int256(token1Balance * reserve0 + token0Balance * reserve1 + 2 * reserve1 * reserve0)
            );
        } else {
            swapPath[0] = token1;
            swapPath[1] = token0;
            swapAmount = uint256(
                (int256(reserve1) * int256(token0Balance * reserve1 - token1Balance * reserve0)) /
                    int256(token1Balance * reserve0 + token0Balance * reserve1 + 2 * reserve1 * reserve0)
            );
        }
        TransferHelper.safeApprove(swapPath[0], address(nextRouter), swapAmount);
        nextRouter.swapExactTokensForTokens(swapAmount, 1, swapPath, address(this), block.timestamp);
        token0Balance = token0ERC.balanceOf(address(this));
        token1Balance = token1ERC.balanceOf(address(this));
    }

    function _returnAsset(address token) private {
        uint256 balance = IERC20(token).balanceOf(address(this));
        if (balance > 0) TransferHelper.safeTransfer(token, msg.sender, balance);
    }
}
          

contracts/SparkSwapRouter.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

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

import "./interfaces/IWETH.sol";
import "./interfaces/ISparkSwapPair.sol";
import "./interfaces/ISparkSwapFactory.sol";
import "./interfaces/ISparkSwapRouter02.sol";

import "./abstracts/Constants.sol";
import "./libraries/TransferHelper.sol";

contract SparkSwapRouter is ISparkSwapRouter02, Constants {
    address public immutable override factory;
    address public immutable override WETH;

    modifier ensure(uint256 deadline) {
        require(deadline >= block.timestamp, "SparkSwapRouter: EXPIRED");
        _;
    }

    function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) public pure returns (uint256 amountB) {
        require(amountA > 0, "SparkSwapRouter: INSUFFICIENT_AMOUNT");
        require(reserveA > 0 && reserveB > 0, "SparkSwapRouter: INSUFFICIENT_LIQUIDITY");
        amountB = (amountA * reserveB) / reserveA;
    }

    function getAmountOut(
        uint256 amountIn,
        address tokenIn,
        address tokenOut,
        address caller
    ) public view returns (uint256 amountOut) {
        ISparkSwapPair pair = ISparkSwapPair(pairFor(tokenIn, tokenOut));
        return pair.getAmountOut(amountIn, tokenIn, caller);
    }

    function getAmountIn(
        uint256 amountOut,
        address tokenIn,
        address tokenOut,
        address caller
    ) public view returns (uint256 amountIn) {
        ISparkSwapPair pair = ISparkSwapPair(pairFor(tokenIn, tokenOut));
        return pair.getAmountIn(amountOut, tokenIn, caller);
    }

    function getAmountsOut(
        uint256 amountIn,
        address[] memory path,
        address caller
    ) public view returns (uint256[] memory amounts) {
        require(path.length >= 2, "SparkSwapRouter: INVALID_PATH");
        amounts = new uint256[](path.length);
        amounts[0] = amountIn;
        for (uint256 i; i < path.length - 1; i++) {
            amounts[i + 1] = getAmountOut(amounts[i], path[i], path[i + 1], caller);
        }
    }

    function getAmountsIn(
        uint256 amountOut,
        address[] memory path,
        address caller
    ) public view returns (uint256[] memory amounts) {
        require(path.length >= 2, "SparkSwapRouter: INVALID_PATH");
        amounts = new uint256[](path.length);
        amounts[amounts.length - 1] = amountOut;
        for (uint256 i = path.length - 1; i > 0; i--) {
            amounts[i - 1] = getAmountIn(amounts[i], path[i - 1], path[i], caller);
        }
    }

    function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
        require(tokenA != tokenB, "SparkSwapRouter: IDENTICAL_ADDRESSES");
        (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        require(token0 != address(0), "SparkSwapRouter: ZERO_ADDRESS");
    }

    function pairFor(address tokenA, address tokenB) internal view returns (address pair) {
        (address token0, address token1) = sortTokens(tokenA, tokenB);
        pair = address(
            uint160(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            hex"ff",
                            factory,
                            keccak256(abi.encodePacked(token0, token1)),
                            hex"d98d5db929ea0eea7a1e5d6b95fe5a6bca2f96da961c8c025a31487e3354fd80"
                        )
                    )
                )
            )
        );
    }

    function getReserves(address tokenA, address tokenB) internal view returns (uint256 reserveA, uint256 reserveB) {
        (address token0, ) = sortTokens(tokenA, tokenB);
        (uint256 reserve0, uint256 reserve1, ) = ISparkSwapPair(pairFor(tokenA, tokenB)).getReserves();
        (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
    }

    constructor(address factory_, address WETH_) {
        factory = factory_;
        WETH = WETH_;
    }

    receive() external payable {
        assert(msg.sender == WETH);
    }

    function _addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin
    ) internal virtual returns (uint256 amountA, uint256 amountB) {
        require(
            ISparkSwapFactory(factory).getPair(tokenA, tokenB) != address(0),
            "SparkSwapRouter: Pair is zero address"
        );
        (uint256 reserveA, uint256 reserveB) = getReserves(tokenA, tokenB);
        if (reserveA == 0 && reserveB == 0) {
            (amountA, amountB) = (amountADesired, amountBDesired);
        } else {
            uint256 amountBOptimal = quote(amountADesired, reserveA, reserveB);
            if (amountBOptimal <= amountBDesired) {
                require(amountBOptimal >= amountBMin, "SparkSwapRouter: INSUFFICIENT_B_AMOUNT");
                (amountA, amountB) = (amountADesired, amountBOptimal);
            } else {
                uint256 amountAOptimal = quote(amountBDesired, reserveB, reserveA);
                assert(amountAOptimal <= amountADesired);
                require(amountAOptimal >= amountAMin, "SparkSwapRouter: INSUFFICIENT_A_AMOUNT");
                (amountA, amountB) = (amountAOptimal, amountBDesired);
            }
        }
    }

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external virtual override ensure(deadline) returns (uint256 amountA, uint256 amountB, uint256 liquidity) {
        (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
        address pair = pairFor(tokenA, tokenB);
        TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
        TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
        liquidity = ISparkSwapPair(pair).mint(to);
    }

    function addLiquidityETH(
        address token,
        uint256 amountTokenDesired,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    )
        external
        payable
        virtual
        override
        ensure(deadline)
        returns (uint256 amountToken, uint256 amountETH, uint256 liquidity)
    {
        (amountToken, amountETH) = _addLiquidity(
            token,
            WETH,
            amountTokenDesired,
            msg.value,
            amountTokenMin,
            amountETHMin
        );
        address pair = pairFor(token, WETH);
        TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
        IWETH(WETH).deposit{value: amountETH}();
        assert(IWETH(WETH).transfer(pair, amountETH));
        liquidity = ISparkSwapPair(pair).mint(to);
        if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
    }

    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) public virtual override ensure(deadline) returns (uint256 amountA, uint256 amountB) {
        address pair = pairFor(tokenA, tokenB);
        ISparkSwapPair(pair).transferFrom(msg.sender, pair, liquidity);
        (uint256 amount0, uint256 amount1) = ISparkSwapPair(pair).burn(to);
        (address token0, ) = sortTokens(tokenA, tokenB);
        (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
        require(amountA >= amountAMin, "SparkSwapRouter: INSUFFICIENT_A_AMOUNT");
        require(amountB >= amountBMin, "SparkSwapRouter: INSUFFICIENT_B_AMOUNT");
    }

    function removeLiquidityETH(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) public virtual override ensure(deadline) returns (uint256 amountToken, uint256 amountETH) {
        (amountToken, amountETH) = removeLiquidity(
            token,
            WETH,
            liquidity,
            amountTokenMin,
            amountETHMin,
            address(this),
            deadline
        );
        TransferHelper.safeTransfer(token, to, amountToken);
        IWETH(WETH).withdraw(amountETH);
        TransferHelper.safeTransferETH(to, amountETH);
    }

    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external virtual override returns (uint256 amountA, uint256 amountB) {
        address pair = pairFor(tokenA, tokenB);
        uint256 value = approveMax ? type(uint256).max : liquidity;
        ISparkSwapPair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
        (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
    }

    function removeLiquidityETHWithPermit(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external virtual override returns (uint256 amountToken, uint256 amountETH) {
        address pair = pairFor(token, WETH);
        uint256 value = approveMax ? type(uint256).max : liquidity;
        ISparkSwapPair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
        (amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
    }

    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) public virtual override ensure(deadline) returns (uint256 amountETH) {
        (, amountETH) = removeLiquidity(token, WETH, liquidity, amountTokenMin, amountETHMin, address(this), deadline);
        TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
        IWETH(WETH).withdraw(amountETH);
        TransferHelper.safeTransferETH(to, amountETH);
    }

    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external virtual override returns (uint256 amountETH) {
        address pair = pairFor(token, WETH);
        uint256 value = approveMax ? type(uint256).max : liquidity;
        ISparkSwapPair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
        amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
            token,
            liquidity,
            amountTokenMin,
            amountETHMin,
            to,
            deadline
        );
    }

    function _swap(
        uint256[] memory amounts,
        address[] memory path,
        address _to
    ) internal virtual onlyContractsWhitelist {
        for (uint256 i; i < path.length - 1; i++) {
            (address input, address output) = (path[i], path[i + 1]);
            (address token0, ) = sortTokens(input, output);
            uint256 amountOut = amounts[i + 1];
            (uint256 amount0Out, uint256 amount1Out) = input == token0
                ? (uint256(0), amountOut)
                : (amountOut, uint256(0));
            address to = i < path.length - 2 ? pairFor(output, path[i + 2]) : _to;
            ISparkSwapPair(pairFor(input, output)).swapFromPeriphery(
                amount0Out,
                amount1Out,
                to,
                msg.sender,
                new bytes(0)
            );
        }
    }

    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external virtual override ensure(deadline) returns (uint256[] memory amounts) {
        amounts = getAmountsOut(amountIn, path, msg.sender);
        require(amounts[amounts.length - 1] >= amountOutMin, "SparkSwapRouter: INSUFFICIENT_OUTPUT_AMOUNT");
        TransferHelper.safeTransferFrom(path[0], msg.sender, pairFor(path[0], path[1]), amounts[0]);
        _swap(amounts, path, to);
    }

    function swapTokensForExactTokens(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external virtual override ensure(deadline) returns (uint256[] memory amounts) {
        amounts = getAmountsIn(amountOut, path, msg.sender);
        require(amounts[0] <= amountInMax, "SparkSwapRouter: EXCESSIVE_INPUT_AMOUNT");
        TransferHelper.safeTransferFrom(path[0], msg.sender, pairFor(path[0], path[1]), amounts[0]);
        _swap(amounts, path, to);
    }

    function swapExactETHForTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable virtual override ensure(deadline) returns (uint256[] memory amounts) {
        require(path[0] == WETH, "SparkSwapRouter: INVALID_PATH");
        amounts = getAmountsOut(msg.value, path, msg.sender);
        require(amounts[amounts.length - 1] >= amountOutMin, "SparkSwapRouter: INSUFFICIENT_OUTPUT_AMOUNT");
        IWETH(WETH).deposit{value: amounts[0]}();
        assert(IWETH(WETH).transfer(pairFor(path[0], path[1]), amounts[0]));
        _swap(amounts, path, to);
    }

    function swapTokensForExactETH(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external virtual override ensure(deadline) returns (uint256[] memory amounts) {
        require(path[path.length - 1] == WETH, "SparkSwapRouter: INVALID_PATH");
        amounts = getAmountsIn(amountOut, path, msg.sender);
        require(amounts[0] <= amountInMax, "SparkSwapRouter: EXCESSIVE_INPUT_AMOUNT");
        TransferHelper.safeTransferFrom(path[0], msg.sender, pairFor(path[0], path[1]), amounts[0]);
        _swap(amounts, path, address(this));
        IWETH(WETH).withdraw(amounts[amounts.length - 1]);
        TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
    }

    function swapExactTokensForETH(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external virtual override ensure(deadline) returns (uint256[] memory amounts) {
        require(path[path.length - 1] == WETH, "SparkSwapRouter: INVALID_PATH");
        amounts = getAmountsOut(amountIn, path, msg.sender);
        require(amounts[amounts.length - 1] >= amountOutMin, "SparkSwapRouter: INSUFFICIENT_OUTPUT_AMOUNT");
        TransferHelper.safeTransferFrom(path[0], msg.sender, pairFor(path[0], path[1]), amounts[0]);
        _swap(amounts, path, address(this));
        IWETH(WETH).withdraw(amounts[amounts.length - 1]);
        TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
    }

    function swapETHForExactTokens(
        uint256 amountOut,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable virtual override ensure(deadline) returns (uint256[] memory amounts) {
        require(path[0] == WETH, "SparkSwapRouter: INVALID_PATH");
        amounts = getAmountsIn(amountOut, path, msg.sender);
        require(amounts[0] <= msg.value, "SparkSwapRouter: EXCESSIVE_INPUT_AMOUNT");
        IWETH(WETH).deposit{value: amounts[0]}();
        assert(IWETH(WETH).transfer(pairFor(path[0], path[1]), amounts[0]));
        _swap(amounts, path, to);
        if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
    }

    function _swapSupportingFeeOnTransferTokens(
        address[] memory path,
        address _to
    ) internal virtual onlyContractsWhitelist {
        for (uint256 i; i < path.length - 1; i++) {
            (address input, address output) = (path[i], path[i + 1]);
            (address token0, ) = sortTokens(input, output);
            ISparkSwapPair pair = ISparkSwapPair(pairFor(input, output));
            uint256 amountInput;
            uint256 amountOutput;
            {
                (uint256 reserve0, uint256 reserve1, ) = pair.getReserves();
                (uint256 reserveInput, ) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
                amountInput = IERC20(input).balanceOf(address(pair)) - reserveInput;
                amountOutput = getAmountOut(amountInput, path[i], path[i + 1], msg.sender);
            }
            (uint256 amount0Out, uint256 amount1Out) = input == token0
                ? (uint256(0), amountOutput)
                : (amountOutput, uint256(0));
            address to = i < path.length - 2 ? pairFor(output, path[i + 2]) : _to;
            pair.swapFromPeriphery(amount0Out, amount1Out, to, msg.sender, new bytes(0));
        }
    }

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external virtual override ensure(deadline) {
        TransferHelper.safeTransferFrom(path[0], msg.sender, pairFor(path[0], path[1]), amountIn);
        uint256 balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
        _swapSupportingFeeOnTransferTokens(path, to);
        require(
            IERC20(path[path.length - 1]).balanceOf(to) - balanceBefore >= amountOutMin,
            "SparkSwapRouter: INSUFFICIENT_OUTPUT_AMOUNT"
        );
    }

    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable virtual override ensure(deadline) {
        require(path[0] == WETH, "SparkSwapRouter: INVALID_PATH");
        uint256 amountIn = msg.value;
        IWETH(WETH).deposit{value: amountIn}();
        assert(IWETH(WETH).transfer(pairFor(path[0], path[1]), amountIn));
        uint256 balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
        _swapSupportingFeeOnTransferTokens(path, to);
        require(
            IERC20(path[path.length - 1]).balanceOf(to) - balanceBefore >= amountOutMin,
            "SparkSwapRouter: INSUFFICIENT_OUTPUT_AMOUNT"
        );
    }

    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external virtual override ensure(deadline) {
        require(path[path.length - 1] == WETH, "SparkSwapRouter: INVALID_PATH");
        TransferHelper.safeTransferFrom(path[0], msg.sender, pairFor(path[0], path[1]), amountIn);
        _swapSupportingFeeOnTransferTokens(path, address(this));
        uint256 amountOut = IERC20(WETH).balanceOf(address(this));
        require(amountOut >= amountOutMin, "SparkSwapRouter: INSUFFICIENT_OUTPUT_AMOUNT");
        IWETH(WETH).withdraw(amountOut);
        TransferHelper.safeTransferETH(to, amountOut);
    }

    modifier onlyContractsWhitelist() {
        ISparkSwapFactory factory_ = ISparkSwapFactory(factory);
        require(
            factory_.contractsWhitelistContains(address(0)) ||
                msg.sender == tx.origin ||
                factory_.contractsWhitelistContains(msg.sender),
            "SparkSwapRouter: Caller is invalid"
        );
        _;
    }
}
          

contracts/abstracts/Constants.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

abstract contract Constants {
    uint256 public constant DIVIDER = 10000;
}
          

contracts/interfaces/IERC20.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

interface IERC20 {
    event Approval(address indexed owner, address indexed spender, uint256 value);
    event Transfer(address indexed from, address indexed to, uint256 value);

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

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

    function decimals() external view returns (uint8);

    function totalSupply() external view returns (uint256);

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

    function allowance(address owner, address spender) external view returns (uint256);

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

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

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

contracts/interfaces/ISparkSwapCallee.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

interface ISparkSwapCallee {
    function SparkSwapCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external;
}
          

contracts/interfaces/ISparkSwapERC20.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

interface ISparkSwapERC20 {
    event Approval(address indexed owner, address indexed spender, uint256 value);
    event Transfer(address indexed from, address indexed to, uint256 value);

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

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

    function decimals() external view returns (uint8);

    function totalSupply() external view returns (uint256);

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

    function allowance(address owner, address spender) external view returns (uint256);

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

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

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

    function DOMAIN_SEPARATOR() external view returns (bytes32);

    function PERMIT_TYPEHASH() external view returns (bytes32);

    function nonces(address owner) external view returns (uint256);

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;
}
          

contracts/interfaces/ISparkSwapFactory.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

interface ISparkSwapFactory {
    event ContractsWhitelistAdded(address[] contracts);
    event ContractsWhitelistRemoved(address[] contracts);
    event FeeUpdated(uint256 fee);
    event ProtocolShareUpdated(uint256 share);
    event FeePairUpdated(address indexed token0, address indexed token1, uint256 fee);
    event ProtocolSharePairUpdated(address indexed token0, address indexed token1, uint256 share);
    event FeeWhitelistAdded(address[] accounts);
    event FeeWhitelistRemoved(address[] accounts);
    event PairCreated(address indexed token0, address indexed token1, address pair, uint256);
    event PeripheryWhitelistAdded(address[] periphery);
    event PeripheryWhitelistRemoved(address[] periphery);
    event Skimmed(address indexed token0, address indexed token1, address to);

    function INIT_CODE_PAIR_HASH() external view returns (bytes32);

    function contractsWhitelistList(uint256 offset, uint256 limit) external view returns (address[] memory output);

    function contractsWhitelist(uint256 index) external view returns (address);

    function contractsWhitelistContains(address contract_) external view returns (bool);

    function contractsWhitelistCount() external view returns (uint256);

    function protocolShare() external view returns (uint256);

    function fee() external view returns (uint256);

    function feeWhitelistList(uint256 offset, uint256 limit) external view returns (address[] memory output);

    function feeWhitelist(uint256 index) external view returns (address);

    function feeWhitelistContains(address account) external view returns (bool);

    function feeWhitelistCount() external view returns (uint256);

    function feeTo() external view returns (address);

    function feeToSetter() external view returns (address);

    function peripheryWhitelistList(uint256 offset, uint256 limit) external view returns (address[] memory output);

    function peripheryWhitelist(uint256 index) external view returns (address);

    function peripheryWhitelistContains(address account) external view returns (bool);

    function peripheryWhitelistCount() external view returns (uint256);

    function getPair(address tokenA, address tokenB) external view returns (address pair);

    function allPairs(uint256) external view returns (address pair);

    function allPairsLength() external view returns (uint256);

    function addContractsWhitelist(address[] memory contracts) external returns (bool);

    function addFeeWhitelist(address[] memory accounts) external returns (bool);

    function addPeripheryWhitelist(address[] memory periphery) external returns (bool);

    function removeContractsWhitelist(address[] memory contracts) external returns (bool);

    function removeFeeWhitelist(address[] memory accounts) external returns (bool);

    function removePeripheryWhitelist(address[] memory periphery) external returns (bool);

    function createPair(address tokenA, address tokenB) external returns (address pair);

    function updateFee(uint256 fee_) external returns (bool);

    function updateProtocolShare(uint256 share) external returns (bool);

    function updateFeePair(address token0, address token1, uint256 fee_) external returns (bool);

    function updateProtocolSharePair(address token0, address token1, uint256 share) external returns (bool);

    function setFeeTo(address) external;

    function setFeeToSetter(address) external;

    function skim(address token0, address token1, address to) external returns (bool);
}
          

contracts/interfaces/ISparkSwapRouter02.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

import "./ISparkSwapRouter01.sol";

interface ISparkSwapRouter02 is ISparkSwapRouter01 {
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountETH);

    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountETH);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;

    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable;

    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;
}
          

contracts/interfaces/IWETH.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

interface IWETH {
    function deposit() external payable;

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

    function withdraw(uint256) external;
}
          

contracts/libraries/Math.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

// a library for performing various math operations

library Math {
    function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = x < y ? x : y;
    }

    // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
    function sqrt(uint256 y) internal pure returns (uint256 z) {
        if (y > 3) {
            z = y;
            uint256 x = y / 2 + 1;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }
}
          

contracts/libraries/TransferHelper.sol

// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity 0.8.19;

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

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

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

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

contracts/libraries/UQ112x112.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

// a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format))

// range: [0, 2**112 - 1]
// resolution: 1 / 2**112

library UQ112x112 {
    uint224 constant Q112 = 2 ** 112;

    // encode a uint112 as a UQ112x112
    function encode(uint112 y) internal pure returns (uint224 z) {
        z = uint224(y) * Q112; // never overflows
    }

    // divide a UQ112x112 by a uint112, returning a UQ112x112
    function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
        z = x / uint224(y);
    }
}
          

contracts/libraries/WBNB.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

contract WBNB {
    string public name = "Wrapped BNB";
    string public symbol = "WBNB";
    uint8 public decimals = 18;

    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    event Approval(address indexed src, address indexed guy, uint256 wad);
    event Transfer(address indexed src, address indexed dst, uint256 wad);
    event Deposit(address indexed dst, uint256 wad);
    event Withdrawal(address indexed src, uint256 wad);

    receive() external payable {
        deposit();
    }

    function deposit() public payable {
        balanceOf[msg.sender] += msg.value;
        emit Deposit(msg.sender, msg.value);
    }

    function withdraw(uint256 wad) public {
        require(balanceOf[msg.sender] >= wad);
        balanceOf[msg.sender] -= wad;
        payable(msg.sender).transfer(wad);
        emit Withdrawal(msg.sender, wad);
    }

    function totalSupply() public view returns (uint256) {
        return address(this).balance;
    }

    function approve(address guy, uint256 wad) public returns (bool) {
        allowance[msg.sender][guy] = wad;
        emit Approval(msg.sender, guy, wad);
        return true;
    }

    function transfer(address dst, uint256 wad) public returns (bool) {
        return transferFrom(msg.sender, dst, wad);
    }

    function transferFrom(address src, address dst, uint256 wad) public returns (bool) {
        require(balanceOf[src] >= wad);
        if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
            require(allowance[src][msg.sender] >= wad);
            allowance[src][msg.sender] -= wad;
        }
        balanceOf[src] -= wad;
        balanceOf[dst] += wad;
        emit Transfer(src, dst, wad);
        return true;
    }
}
          

contracts/mocks/MockToken.sol

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../interfaces/IWETH.sol";

contract MockToken is ERC20, IWETH {
    event Deposit(address indexed dst, uint256 wad);
    event Withdrawal(address indexed src, uint256 wad);

    constructor(string memory name_, string memory symbol_, uint256 supply_) ERC20(name_, symbol_) {
        _mint(msg.sender, supply_);
    }

    receive() external payable {
        deposit();
    }

    function deposit() public payable {
        mint(msg.value);
        emit Deposit(msg.sender, msg.value);
    }

    function mint(uint256 amount) public returns (bool) {
        _mint(msg.sender, amount);
        return true;
    }

    function transfer(address to, uint256 amount) public override(ERC20, IWETH) returns (bool) {
        return super.transfer(to, amount);
    }

    function withdraw(uint256 wad) public {
        _burn(msg.sender, wad);
        payable(msg.sender).transfer(wad);
        emit Withdrawal(msg.sender, wad);
    }
}
          

Compiler Settings

{"viaIR":true,"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers"]}},"optimizer":{"runs":200,"enabled":true},"metadata":{"useLiteralContent":true},"libraries":{}}
              

Contract ABI

[{"type":"constructor","stateMutability":"nonpayable","inputs":[]},{"type":"event","name":"Approval","inputs":[{"type":"address","name":"owner","internalType":"address","indexed":true},{"type":"address","name":"spender","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Burn","inputs":[{"type":"address","name":"sender","internalType":"address","indexed":true},{"type":"uint256","name":"amount0","internalType":"uint256","indexed":false},{"type":"uint256","name":"amount1","internalType":"uint256","indexed":false},{"type":"address","name":"to","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"FeeUpdated","inputs":[{"type":"uint256","name":"fee","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Mint","inputs":[{"type":"address","name":"sender","internalType":"address","indexed":true},{"type":"uint256","name":"amount0","internalType":"uint256","indexed":false},{"type":"uint256","name":"amount1","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"ProtocolShareUpdated","inputs":[{"type":"uint256","name":"share","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Swap","inputs":[{"type":"address","name":"sender","internalType":"address","indexed":true},{"type":"uint256","name":"amount0In","internalType":"uint256","indexed":false},{"type":"uint256","name":"amount1In","internalType":"uint256","indexed":false},{"type":"uint256","name":"amount0Out","internalType":"uint256","indexed":false},{"type":"uint256","name":"amount1Out","internalType":"uint256","indexed":false},{"type":"address","name":"to","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"Sync","inputs":[{"type":"uint112","name":"reserve0","internalType":"uint112","indexed":false},{"type":"uint112","name":"reserve1","internalType":"uint112","indexed":false}],"anonymous":false},{"type":"event","name":"Transfer","inputs":[{"type":"address","name":"from","internalType":"address","indexed":true},{"type":"address","name":"to","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"DIVIDER","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"DOMAIN_SEPARATOR","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"MAX_FEE","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"MAX_PROTOCOL_SHARE","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"MINIMUM_LIQUIDITY","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"PERMIT_TYPEHASH","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"allowance","inputs":[{"type":"address","name":"","internalType":"address"},{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"approve","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"balanceOf","inputs":[{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"amount0","internalType":"uint256"},{"type":"uint256","name":"amount1","internalType":"uint256"}],"name":"burn","inputs":[{"type":"address","name":"to","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint8","name":"","internalType":"uint8"}],"name":"decimals","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"factory","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"fee","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"amountIn","internalType":"uint256"}],"name":"getAmountIn","inputs":[{"type":"uint256","name":"amountOut","internalType":"uint256"},{"type":"address","name":"tokenIn","internalType":"address"},{"type":"address","name":"caller","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"amountOut","internalType":"uint256"}],"name":"getAmountOut","inputs":[{"type":"uint256","name":"amountIn","internalType":"uint256"},{"type":"address","name":"tokenIn","internalType":"address"},{"type":"address","name":"caller","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint112","name":"_reserve0","internalType":"uint112"},{"type":"uint112","name":"_reserve1","internalType":"uint112"},{"type":"uint256","name":"_blockTimestampLast","internalType":"uint256"}],"name":"getReserves","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"initialize","inputs":[{"type":"address","name":"_token0","internalType":"address"},{"type":"address","name":"_token1","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"kLast","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"liquidity","internalType":"uint256"}],"name":"mint","inputs":[{"type":"address","name":"to","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"name","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"nonces","inputs":[{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"permit","inputs":[{"type":"address","name":"owner","internalType":"address"},{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"},{"type":"uint256","name":"deadline","internalType":"uint256"},{"type":"uint8","name":"v","internalType":"uint8"},{"type":"bytes32","name":"r","internalType":"bytes32"},{"type":"bytes32","name":"s","internalType":"bytes32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"price0CumulativeLast","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"price1CumulativeLast","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"protocolShare","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"skim","inputs":[{"type":"address","name":"to","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"swap","inputs":[{"type":"uint256","name":"amount0Out","internalType":"uint256"},{"type":"uint256","name":"amount1Out","internalType":"uint256"},{"type":"address","name":"to","internalType":"address"},{"type":"bytes","name":"data","internalType":"bytes"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"swapFromPeriphery","inputs":[{"type":"uint256","name":"amount0Out","internalType":"uint256"},{"type":"uint256","name":"amount1Out","internalType":"uint256"},{"type":"address","name":"to","internalType":"address"},{"type":"address","name":"caller","internalType":"address"},{"type":"bytes","name":"data","internalType":"bytes"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"symbol","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"sync","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"token0","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"token1","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"totalSupply","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"transfer","inputs":[{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"transferFrom","inputs":[{"type":"address","name":"from","internalType":"address"},{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"updateFee","inputs":[{"type":"uint256","name":"fee_","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"updateProtocolShare","inputs":[{"type":"uint256","name":"share","internalType":"uint256"}]}]
              

Contract Creation Code

Verify & Publish
0x60808060405234620000f4576020816200001b600d93620000f9565b828152016c537061726b53776170204c507360981b815220600160206040516200004581620000f9565b82815201603160f81b815220906040519160208301917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8352604084015260608301524660808301523060a083015260a0825260c082019180831060018060401b03841117620000de5760408390525190206000556001601155600780546001600160a01b03191633179055612c5f9081620001168239f35b634e487b7160e01b600052604160045260246000fd5b600080fd5b604081019081106001600160401b03821117620000de5760405256fe6080604052600436101561001257600080fd5b6000803560e01c8063022c0d9f14611de757806306fdde0314611da15780630902f1ac14611d50578063095ea7b314611d295780630a9a2b7214611bd45780630dfe168114611bab5780631103f31514611b8d57806318160ddd14611b6f57806323b872dd14611ace57806330adf81f14611a93578063313ce56714611a775780633644e51514611a5a578063485cc955146119475780635909c0d5146119295780635a3d54931461190b5780635e1e6325146117ba57806362043bd81461179d5780636a6278421461148557806370a082311461144c5780637464fc3d1461142e5780637ecebe00146113f557806389afcb44146110905780639012c4a814610fe057806395d89b4114610f97578063a9059cbb14610f65578063a931208f146107e7578063b239683214610809578063ba9a7a56146107ec578063bc063e1a146107e7578063bc25cf7714610691578063c45a015514610668578063c98ec7a4146105a1578063d21220a714610578578063d505accf14610355578063dd62ed3e14610306578063ddca3f43146102e85763fff6cae9146101b457600080fd5b346102e557806003193601126102e5576101d26001601154146125e3565b60118190556008546040516370a0823160e01b808252306004830152916020916001600160a01b039183908290602490829086165afa9182156102da57839186936102a9575b50600954169360246040518096819382523060048301525afa91821561029e578492610267575b5061025f9250601054916001600160701b03808460701c169316916129f6565b600160115580f35b90915082813d8311610297575b61027e818361234a565b810103126102925761025f9151903861023f565b600080fd5b503d610274565b6040513d86823e3d90fd5b8281939294503d83116102d3575b6102c1818361234a565b81010312610292578290519138610218565b503d6102b7565b6040513d87823e3d90fd5b80fd5b50346102e557806003193601126102e5576020600554604051908152f35b50346102e55760403660031901126102e5576103206122a4565b604061032a6122ba565b9260018060a01b03809316815260036020522091166000526020526020604060002054604051908152f35b50346102e55760e03660031901126102e55761036f6122a4565b6103776122ba565b604435906064356084359060ff82168092036105745742811061052f5785546001600160a01b038681168089526004602052604089208054919592949290600019831461051b576001830190556040519260208401927f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98452876040860152868916606086015289608086015260a085015260c084015260c0835260e083019383851067ffffffffffffffff86111761050557848b94610122608095602098604052825190209161010081019461190160f01b86526101028201520152604281526104618161232e565b519020906040519182528482015260a435604082015260c435606082015282805260015afa156102da578551169081151591826104fb575b5050156104ac576104a992612406565b80f35b60405162461bcd60e51b815260206004820152602160248201527f537061726b5377617045524332303a20494e56414c49445f5349474e415455526044820152604560f81b6064820152608490fd5b1490503880610499565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b8b52601160045260248bfd5b60405162461bcd60e51b815260206004820152601760248201527f537061726b5377617045524332303a20455850495245440000000000000000006044820152606490fd5b8580fd5b50346102e557806003193601126102e5576009546040516001600160a01b039091168152602090f35b50346102e55760203660031901126102e5576004356105cb60018060a01b03600754163314612627565b6064811161060d576020817fd82bf55fab0a06d972b148305a6b58062e33e048f53695742fc3e9f07585259292600655604051908152a1602060405160018152f35b60405162461bcd60e51b815260206004820152602d60248201527f537061726b53776170466163746f72793a205368617265206774204d41585f5060448201526c524f544f434f4c5f534841524560981b6064820152608490fd5b50346102e557806003193601126102e5576007546040516001600160a01b039091168152602090f35b50346102e5576020806003193601126107e3576106ac6122a4565b6007546001600160a01b039291906106c79084163314612627565b6106d56001601154146125e3565b836011558260085416926009541691604051936370a0823160e01b908186523060048701528286602481845afa9586156107d85787966107a7575b50610733908461072d6001600160701b039889601054169061245d565b9161285a565b6040519081523060048201528181602481875afa91821561079c57869261076c575b505061072d9061025f9460105460701c169061245d565b90809250813d8311610795575b610783818361234a565b8101031261029257518361072d610755565b503d610779565b6040513d88823e3d90fd5b9095508281813d83116107d1575b6107bf818361234a565b81010312610292575194610733610710565b503d6107b5565b6040513d89823e3d90fd5b5080fd5b6123ea565b50346102e557806003193601126102e55760206040516103e88152f35b50346102e55760a03660031901126102e55761082361228e565b606435906001600160a01b03821682036102925760843567ffffffffffffffff8111610f61576108579036906004016122d0565b60075460405163effe8ce160e01b81523360048201526001600160a01b039091169290602081602481875afa9081156107d8578791610f42575b5015610eee576108a56001601154146125e3565b856011556040516304ad971560e21b808252876004830152602082602481885afa918215610c5b578892610ecd575b508115610ec3575b8115610e78575b5015610e34576004351580158091610e29575b15610dd2576010546001600160701b03607082901c811693911691600435831180610dbe575b6109259061253e565b6008546009546001600160a01b0391821696908216939091891687141580610dab575b15610d66578a92610d54575b602435610d42575b81610c9b575b505050604051936020856024816370a0823160e01b948582523060048301525afa948515610c07578995610c66575b5060209060246040518094819382523060048301525afa908115610c5b578891610c29575b506109cc6004356001600160701b03841661245d565b841115610c22576109f16109eb6004356001600160701b03851661245d565b8561245d565b965b610a086024356001600160701b03861661245d565b821115610c1b57610a2d610a276024356001600160701b03871661245d565b8361245d565b955b8815801590610c12575b610a42906124e1565b6001600160a01b03821615159182610b97575b505015610b8f57875b846127108102046127101485151715610b7b57610a88610a7e828a6125b0565b612710870261245d565b90612710830290838204612710148415171561051b57610abc9291610ab0610ab6928a6125b0565b9061245d565b906125b0565b610ae3610adb6001600160701b0386166001600160701b0386166125b0565b610ab66129ac565b11610b4357610af1936129f6565b60405192835260208301526004356040830152602435606083015260018060a01b0316907fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d82260803392a3600160115580f35b60405162461bcd60e51b815260206004820152601060248201526f537061726b53776170506169723a204b60801b6044820152606490fd5b634e487b7160e01b89526011600452602489fd5b600554610a5e565b604051631e45990d60e31b81526001600160a01b0390911660048201529150602090829060249082905afa908115610c07578991610bd8575b503880610a55565b610bfa915060203d602011610c00575b610bf2818361234a565b810190612598565b38610bd0565b503d610be8565b6040513d8b823e3d90fd5b50861515610a39565b8895610a2f565b87966109f3565b90506020813d602011610c53575b81610c446020938361234a565b810103126102925751386109b6565b3d9150610c37565b6040513d8a823e3d90fd5b9094506020813d602011610c93575b81610c826020938361234a565b810103126102925751936020610991565b3d9150610c75565b6001600160a01b0389163b15610d3e578160a484926040519485938492630da4f2fd60e21b84523360048501526004356024850152602435604485015260806064850152816084850152848401378181018301859052601f01601f19168101030181836001600160a01b038d165af18015610d3357610d1b575b80610962565b610d24906122fe565b610d2f578738610d15565b8780fd5b6040513d84823e3d90fd5b8280fd5b610d4f6024358a8661285a565b61095c565b610d616004358a8961285a565b610954565b60405162461bcd60e51b815260206004820152601960248201527f537061726b53776170506169723a20494e56414c49445f544f000000000000006044820152606490fd5b506001600160a01b038916841415610948565b506001600160701b0384166024351061091c565b60405162461bcd60e51b815260206004820152602960248201527f537061726b53776170506169723a20494e53554646494349454e545f4f555450604482015268155517d05353d5539560ba1b6064820152608490fd5b5060243515156108f6565b606460405162461bcd60e51b815260206004820152602060248201527f537061726b53776170506169723a2043616c6c657220697320696e76616c69646044820152fd5b9050604051908152336004820152602081602481875afa9081156107d8578791610ea4575b50386108e3565b610ebd915060203d602011610c0057610bf2818361234a565b38610e9d565b33321491506108dc565b610ee791925060203d602011610c0057610bf2818361234a565b90386108d4565b60405162461bcd60e51b815260206004820152602660248201527f537061726b53776170506169723a2043616c6c6572206973206e6f742070657260448201526569706865727960d01b6064820152608490fd5b610f5b915060203d602011610c0057610bf2818361234a565b38610891565b8380fd5b50346102e55760403660031901126102e557610f8c610f826122a4565b6024359033612477565b602060405160018152f35b50346102e557806003193601126102e557610fdc604051610fb781612312565b600c81526b0537061726b537761702d4c560a41b60208201526040519182918261236c565b0390f35b50346102e55760203660031901126102e55760043561100a60018060a01b03600754163314612627565b6064811161104c576020817f8c4d35e54a3f2ef1134138fd8ea3daee6a3c89e10d2665996babdf70261e2c7692600555604051908152a1602060405160018152f35b606460405162461bcd60e51b815260206004820152602060248201527f537061726b53776170466163746f72793a20466565206774204d41585f4645456044820152fd5b50346102e557602090816003193601126102e5576110ac6122a4565b906110bb6001601154146125e3565b6011819055601054600f546001600160701b038083169260701c1690506008546009546040516370a0823160e01b8082523060048301529794966001600160a01b039384169690949192841686846024818b5afa938415610d335782946113c6575b5060405199808b523060048c0152878b602481855afa9889156113bb578a9b849b9a611388575b503084526002895261118061115e8660408720549c6126ff565b966111746111798d6111746001549485926125b0565b6125c3565b9d8d6125b0565b998b15158061137f575b15611325576024939291858b92308252600284526111ac81604084205461245d565b3083526002855260408320556111c48160015461245d565b600155604051908152600080516020612c0a833981519152843092a36111eb8d8b8361285a565b6111f68c8b8661285a565b604051948580928582523060048301525afa92831561029e5790899185946112f4575b5060246040518094819382523060048301525afa9283156112e857926112b2575b5098611249929160409a6129f6565b61128f575b855191858352848484015216907fdccd412f0b1252819cb1fd330b93224ca42612892bb3f4f789976e6d81936496863392a360016011558351928352820152f35b6112aa6010546001600160701b03808260701c1691166125b0565b600c5561124e565b929150988683813d83116112e1575b6112cb818361234a565b810103126102925791519198909190604061123a565b503d6112c1565b604051903d90823e3d90fd5b8281939295503d831161131e575b61130c818361234a565b81010312610292578890519238611219565b503d611302565b60405162461bcd60e51b8152600481018b9052602c60248201527f537061726b53776170506169723a20494e53554646494349454e545f4c49515560448201526b125112551657d0955493915160a21b6064820152608490fd5b508a151561118a565b8980929c50819b503d83116113b4575b6113a2818361234a565b81010312610292578a98519938611144565b503d611398565b6040513d85823e3d90fd5b9093508681813d83116113ee575b6113de818361234a565b810103126102925751923861111d565b503d6113d4565b50346102e55760203660031901126102e5576020906040906001600160a01b0361141d6122a4565b168152600483522054604051908152f35b50346102e557806003193601126102e5576020600c54604051908152f35b50346102e55760203660031901126102e5576020906040906001600160a01b036114746122a4565b168152600283522054604051908152f35b50346102e55760203660031901126102e55761149f6122a4565b906114ae6001601154146125e3565b60118190556010546008546040516370a0823160e01b80825230600483015290946001600160701b03607085901c811695941693926001600160a01b03906020908890602490829085165afa96871561029e578497611768575b50602090600954169260246040518095819382523060048301525afa9182156113bb578392611734575b506001600160701b03841694611548868861245d565b946001600160701b0382169361155e858261245d565b9561156984846126ff565b95600154998a156000146116fd57505061158b611586888a6125b0565b612b5b565b6103e7198101919082116116e95750976103e8810181116116d3576103e8016001556000805260026020526040600020546103e8810181116116d3576103e89060008052600260205201604060002055600080600080516020612c0a83398151915260206040516103e88152a35b87156116795760209861160f89611614966126a9565b6129f6565b611656575b604051918252838201527f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f60403392a26001601155604051908152f35b6116716010546001600160701b03808260701c1691166125b0565b600c55611619565b60405162461bcd60e51b815260206004820152602c60248201527f537061726b53776170506169723a20494e53554646494349454e545f4c49515560448201526b125112551657d3525395115160a21b6064820152608490fd5b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b81526011600452602490fd5b9061117461171461171b936111748e979e8e6125b0565b948a6125b0565b90508082101561172d57505b966115f9565b9050611727565b9091506020813d602011611760575b816117506020938361234a565b8101031261029257519038611532565b3d9150611743565b9096506020813d602011611795575b816117846020938361234a565b810103126102925751956020611508565b3d9150611777565b50346102e557806003193601126102e55760206040516127108152f35b50346102e5576117c9366123b5565b9091926117ea601054906001600160701b038083169260701c1690600f5490565b506001600160701b039182169591169384906118078315156124e1565b86151580611902575b6118199061253e565b600754604051631e45990d60e31b81526001600160a01b03968716600482015290602090829060249082908a165afa9081156102da5785916118e4575b50156118db5783945b80600954169116146118d2575b50612710928303908382116118be5761188f91611888916125b0565b93846125b0565b938281029281840414901517156116e95760206118b6856118b0868661246a565b906125c3565b604051908152f35b634e487b7160e01b83526011600452602483fd5b9493503861186c565b6005549461185f565b6118fc915060203d8111610c0057610bf2818361234a565b38611856565b50851515611810565b50346102e557806003193601126102e5576020600b54604051908152f35b50346102e557806003193601126102e5576020600a54604051908152f35b50346102e55760403660031901126102e5576119616122a4565b6119696122ba565b6007546001600160a01b039283916119849083163314612627565b16916bffffffffffffffffffffffff60a01b91838360085416176008551680916009541617600955604051809263313ce56760e01b9081835282600460209687935afa9182156102da5784926119e1918791611a3d575b50612698565b600d5560046040518094819382525afa9081156113bb57611a0a928492611a10575b5050612698565b600e5580f35b611a2f9250803d10611a36575b611a27818361234a565b81019061267f565b3880611a03565b503d611a1d565b611a549150843d8611611a3657611a27818361234a565b386119db565b50346102e557806003193601126102e55760209054604051908152f35b50346102e557806003193601126102e557602060405160128152f35b50346102e557806003193601126102e55760206040517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98152f35b50346102e55760603660031901126102e557611b2a611aeb6122a4565b611af36122ba565b6044359160018060a01b038116808652602095600387526040812033600052875260001960406000205403611b33575b5050612477565b60405160018152f35b80826040925260038852818120336000528852611b5486836000205461245d565b92815260038852203360005286526040600020553880611b23565b50346102e557806003193601126102e5576020600154604051908152f35b50346102e557806003193601126102e5576020600654604051908152f35b50346102e557806003193601126102e5576008546040516001600160a01b039091168152602090f35b50346102e557611be3366123b5565b6010546001600160701b03607082901c8116939116919083611c068615156124e1565b83151580611d20575b611c189061253e565b6009546001600160a01b0392831690831614611d16575b50600754604051631e45990d60e31b8152928216600484015260209183916024918391165afa9081156102da578591611cf8575b5015611ceb57611c748385926125b0565b926127109283850294808604851490151715611cd75790611c949161245d565b908203918211611cc357611cac92916118b0916125b0565b90600182018092116116e957602082604051908152f35b634e487b7160e01b84526011600452602484fd5b634e487b7160e01b86526011600452602486fd5b611c7483600554926125b0565b611d10915060203d8111610c0057610bf2818361234a565b38611c63565b9293506020611c2f565b50841515611c0f565b50346102e55760403660031901126102e557610f8c611d466122a4565b6024359033612406565b50346102e557806003193601126102e5576060611d81601054906001600160701b038083169260701c1690600f5490565b9091604051926001600160701b0380921684521660208301526040820152f35b50346102e557806003193601126102e557610fdc604051611dc181612312565b600d81526c537061726b53776170204c507360981b60208201526040519182918261236c565b50346102e55760803660031901126102e557611e0161228e565b60643567ffffffffffffffff8111610d3e57611e219036906004016122d0565b91611e306001601154146125e3565b60118490556007546040516304ad971560e21b808252600482018790526001600160a01b039492851690602083602481855afa928315610c5b57889361226d575b508215612263575b8215612216575b505015610e3457600435158015809161220b575b15610dd2576010546001600160701b038082169660709290921c16906004358711806121f7575b611ec49061253e565b856008541692866009541694848888161415806121eb575b15610d665789916121d9575b6024356121c7575b82612142575b505050604051916020836024816370a0823160e01b948582523060048301525afa928315610c5b57889361210d575b5060209060246040518096819382523060048301525afa9283156107d85787936120d9575b50611f606004356001600160701b03881661245d565b8211156120d257611f7f610a276004356001600160701b03891661245d565b955b611f966024356001600160701b03841661245d565b8411156120cb57611fb56109eb6024356001600160701b03851661245d565b935b87158015906120c2575b611fca906124e1565b88156120ba57885b8461271081020461271014851517156120a657611ff2610a7e828b6125b0565b9061271083029083820461271014841517156120925761201a9291610ab0610ab6928a6125b0565b612039610adb6001600160701b0386166001600160701b0386166125b0565b11610b4357612047936129f6565b60405193845260208401526004356040840152602435606084015216907fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d82260803392a3600160115580f35b634e487b7160e01b8c52601160045260248cfd5b634e487b7160e01b8a52601160045260248afd5b600554611fd2565b50841515611fc1565b8793611fb7565b8695611f81565b9092506020813d602011612105575b816120f56020938361234a565b8101031261029257519138611f4a565b3d91506120e8565b9092506020813d60201161213a575b816121296020938361234a565b810103126102925751916020611f25565b3d915061211c565b8787163b156107e35760a483916040519485938492630da4f2fd60e21b84523360048501526004356024850152602435604485015260806064850152816084850152848401378181018301859052601f01601f1916810103018183898b165af18015610c5b576121b4575b8781611ef6565b6121c0909791976122fe565b95386121ad565b6121d4602435888861285a565b611ef0565b6121e6600435888761285a565b611ee8565b50858888161415611edc565b506001600160701b03821660243510611ebb565b506024351515611e94565b602091925060246040518094819382523360048301525afa90811561079c578691612244575b503880611e80565b61225d915060203d602011610c0057610bf2818361234a565b3861223c565b3332149250611e79565b61228791935060203d602011610c0057610bf2818361234a565b9138611e71565b604435906001600160a01b038216820361029257565b600435906001600160a01b038216820361029257565b602435906001600160a01b038216820361029257565b9181601f840112156102925782359167ffffffffffffffff8311610292576020838186019501011161029257565b67ffffffffffffffff811161050557604052565b6040810190811067ffffffffffffffff82111761050557604052565b6080810190811067ffffffffffffffff82111761050557604052565b90601f8019910116810190811067ffffffffffffffff82111761050557604052565b6020808252825181830181905290939260005b8281106123a157505060409293506000838284010152601f8019910116010190565b81810186015184820160400152850161237f565b606090600319011261029257600435906001600160a01b03906024358281168103610292579160443590811681036102925790565b3461029257600036600319011261029257602060405160648152f35b909160207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259160018060a01b03809416938460005260038352604060002095169485600052825280604060002055604051908152a3565b919082039182116116d357565b919082018092116116d357565b90916020600080516020612c0a8339815191529160018060a01b0380941693600095858752600284526124ae83604089205461245d565b868852600285526040882055169485815260406124ce838284205461246a565b91878152600285522055604051908152a3565b156124e857565b60405162461bcd60e51b815260206004820152602860248201527f537061726b53776170506169723a20494e53554646494349454e545f494e50556044820152671517d05353d5539560c21b6064820152608490fd5b1561254557565b60405162461bcd60e51b815260206004820152602560248201527f537061726b53776170506169723a20494e53554646494349454e545f4c495155604482015264494449545960d81b6064820152608490fd5b90816020910312610292575180151581036102925790565b818102929181159184041417156116d357565b81156125cd570490565b634e487b7160e01b600052601260045260246000fd5b156125ea57565b60405162461bcd60e51b815260206004820152601560248201527414dc185c9ad4ddd85c14185a5c8e881313d0d2d151605a1b6044820152606490fd5b1561262e57565b60405162461bcd60e51b8152602060048201526024808201527f537061726b53776170506169723a2043616c6c6572206973206e6f7420666163604482015263746f727960e01b6064820152608490fd5b90816020910312610292575160ff811681036102925790565b60ff16604d81116116d357600a0a90565b600080516020612c0a83398151915260206000926126c98560015461246a565b60015560018060a01b031693848452600282526126ea81604086205461246a565b858552600283526040852055604051908152a3565b600754604051622fcfcb60e31b8152919392906001600160a01b03906020908490600490829085165afa92831561284e57600093612810575b50821615159081612804575b600c54919485156127f3578261275b575b50505050565b6115866127789161277e936001600160701b0380911691166125b0565b91612b5b565b80821161278c575b80612755565b61279c600154610ab6838561245d565b916127aa60065480946125b0565b9183606403606481116116d3576127d7946127cb6127d1926118b0956125b0565b926125b0565b9061246a565b806127e3575b80612786565b6127ec916126a9565b38806127dd565b505090506127fd57565b6000600c55565b60065415159150612744565b6020939193813d8211612846575b8161282b6020938361234a565b810103126107e357519083821682036102e557509138612738565b3d915061281e565b6040513d6000823e3d90fd5b906040519261286884612312565b601984527f7472616e7366657228616464726573732c75696e74323536290000000000000060209485015260405163a9059cbb60e01b8186019081526001600160a01b0390931660248201526044808201929092529081526128c98161232e565b600092839283809351925af1903d156129a4573d67ffffffffffffffff81116129905760405190612903601f8201601f191686018361234a565b81528091843d92013e5b81612960575b501561291c5750565b6064906040519062461bcd60e51b82526004820152601e60248201527f537061726b53776170506169723a205452414e534645525f4641494c454400006044820152fd5b80518015925083908315612978575b50505038612913565b6129889350820181019101612598565b38828161296f565b634e487b7160e01b82526041600452602482fd5b50606061290d565b61271060016002815b8082116129cd575050816000190481116116d3570290565b9092806000190481116116d3578184166129ed575b800292811c906129b5565b809202916129e2565b91926001600160701b03908184111580612b51575b15612b0c57816040947f1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad19663ffffffff421693612a4a600f548661245d565b80151580612b01575b80612af6575b612a9d575b505050169283601054916001600160701b0360701b9060701b169163ffffffff60e01b1617179081601055600f55835192835260701c166020820152a1565b612aeb92612ae3926001600160e01b0391612acd9190612ac890612adb612ad3878787858789612bb5565b612beb565b166125b0565b600a5461246a565b600a55612bb5565b600b5461246a565b600b55388080612a5e565b508482161515612a59565b508483161515612a53565b60405162461bcd60e51b815260206004820152601760248201527f537061726b53776170506169723a204f564552464c4f570000000000000000006044820152606490fd5b5081831115612a0b565b9060006003831115612ba857508160019080821c8281018091116116d35791905b848310612b8857505050565b90919350612b9f84612b9a81846125c3565b61246a565b821c9190612b7c565b91612baf57565b60019150565b6dffffffffffffffffffffffffffff60701b607082901b16906001600160701b0316808204600160701b14901517156116d35790565b906001600160701b03169081156125cd576001600160e01b0316049056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa264697066735822122006280758518c180494fe710957cf10bd3f48348fea2a289b77823eb8356759ca64736f6c63430008130033

Deployed ByteCode

0x6080604052600436101561001257600080fd5b6000803560e01c8063022c0d9f14611de757806306fdde0314611da15780630902f1ac14611d50578063095ea7b314611d295780630a9a2b7214611bd45780630dfe168114611bab5780631103f31514611b8d57806318160ddd14611b6f57806323b872dd14611ace57806330adf81f14611a93578063313ce56714611a775780633644e51514611a5a578063485cc955146119475780635909c0d5146119295780635a3d54931461190b5780635e1e6325146117ba57806362043bd81461179d5780636a6278421461148557806370a082311461144c5780637464fc3d1461142e5780637ecebe00146113f557806389afcb44146110905780639012c4a814610fe057806395d89b4114610f97578063a9059cbb14610f65578063a931208f146107e7578063b239683214610809578063ba9a7a56146107ec578063bc063e1a146107e7578063bc25cf7714610691578063c45a015514610668578063c98ec7a4146105a1578063d21220a714610578578063d505accf14610355578063dd62ed3e14610306578063ddca3f43146102e85763fff6cae9146101b457600080fd5b346102e557806003193601126102e5576101d26001601154146125e3565b60118190556008546040516370a0823160e01b808252306004830152916020916001600160a01b039183908290602490829086165afa9182156102da57839186936102a9575b50600954169360246040518096819382523060048301525afa91821561029e578492610267575b5061025f9250601054916001600160701b03808460701c169316916129f6565b600160115580f35b90915082813d8311610297575b61027e818361234a565b810103126102925761025f9151903861023f565b600080fd5b503d610274565b6040513d86823e3d90fd5b8281939294503d83116102d3575b6102c1818361234a565b81010312610292578290519138610218565b503d6102b7565b6040513d87823e3d90fd5b80fd5b50346102e557806003193601126102e5576020600554604051908152f35b50346102e55760403660031901126102e5576103206122a4565b604061032a6122ba565b9260018060a01b03809316815260036020522091166000526020526020604060002054604051908152f35b50346102e55760e03660031901126102e55761036f6122a4565b6103776122ba565b604435906064356084359060ff82168092036105745742811061052f5785546001600160a01b038681168089526004602052604089208054919592949290600019831461051b576001830190556040519260208401927f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98452876040860152868916606086015289608086015260a085015260c084015260c0835260e083019383851067ffffffffffffffff86111761050557848b94610122608095602098604052825190209161010081019461190160f01b86526101028201520152604281526104618161232e565b519020906040519182528482015260a435604082015260c435606082015282805260015afa156102da578551169081151591826104fb575b5050156104ac576104a992612406565b80f35b60405162461bcd60e51b815260206004820152602160248201527f537061726b5377617045524332303a20494e56414c49445f5349474e415455526044820152604560f81b6064820152608490fd5b1490503880610499565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b8b52601160045260248bfd5b60405162461bcd60e51b815260206004820152601760248201527f537061726b5377617045524332303a20455850495245440000000000000000006044820152606490fd5b8580fd5b50346102e557806003193601126102e5576009546040516001600160a01b039091168152602090f35b50346102e55760203660031901126102e5576004356105cb60018060a01b03600754163314612627565b6064811161060d576020817fd82bf55fab0a06d972b148305a6b58062e33e048f53695742fc3e9f07585259292600655604051908152a1602060405160018152f35b60405162461bcd60e51b815260206004820152602d60248201527f537061726b53776170466163746f72793a205368617265206774204d41585f5060448201526c524f544f434f4c5f534841524560981b6064820152608490fd5b50346102e557806003193601126102e5576007546040516001600160a01b039091168152602090f35b50346102e5576020806003193601126107e3576106ac6122a4565b6007546001600160a01b039291906106c79084163314612627565b6106d56001601154146125e3565b836011558260085416926009541691604051936370a0823160e01b908186523060048701528286602481845afa9586156107d85787966107a7575b50610733908461072d6001600160701b039889601054169061245d565b9161285a565b6040519081523060048201528181602481875afa91821561079c57869261076c575b505061072d9061025f9460105460701c169061245d565b90809250813d8311610795575b610783818361234a565b8101031261029257518361072d610755565b503d610779565b6040513d88823e3d90fd5b9095508281813d83116107d1575b6107bf818361234a565b81010312610292575194610733610710565b503d6107b5565b6040513d89823e3d90fd5b5080fd5b6123ea565b50346102e557806003193601126102e55760206040516103e88152f35b50346102e55760a03660031901126102e55761082361228e565b606435906001600160a01b03821682036102925760843567ffffffffffffffff8111610f61576108579036906004016122d0565b60075460405163effe8ce160e01b81523360048201526001600160a01b039091169290602081602481875afa9081156107d8578791610f42575b5015610eee576108a56001601154146125e3565b856011556040516304ad971560e21b808252876004830152602082602481885afa918215610c5b578892610ecd575b508115610ec3575b8115610e78575b5015610e34576004351580158091610e29575b15610dd2576010546001600160701b03607082901c811693911691600435831180610dbe575b6109259061253e565b6008546009546001600160a01b0391821696908216939091891687141580610dab575b15610d66578a92610d54575b602435610d42575b81610c9b575b505050604051936020856024816370a0823160e01b948582523060048301525afa948515610c07578995610c66575b5060209060246040518094819382523060048301525afa908115610c5b578891610c29575b506109cc6004356001600160701b03841661245d565b841115610c22576109f16109eb6004356001600160701b03851661245d565b8561245d565b965b610a086024356001600160701b03861661245d565b821115610c1b57610a2d610a276024356001600160701b03871661245d565b8361245d565b955b8815801590610c12575b610a42906124e1565b6001600160a01b03821615159182610b97575b505015610b8f57875b846127108102046127101485151715610b7b57610a88610a7e828a6125b0565b612710870261245d565b90612710830290838204612710148415171561051b57610abc9291610ab0610ab6928a6125b0565b9061245d565b906125b0565b610ae3610adb6001600160701b0386166001600160701b0386166125b0565b610ab66129ac565b11610b4357610af1936129f6565b60405192835260208301526004356040830152602435606083015260018060a01b0316907fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d82260803392a3600160115580f35b60405162461bcd60e51b815260206004820152601060248201526f537061726b53776170506169723a204b60801b6044820152606490fd5b634e487b7160e01b89526011600452602489fd5b600554610a5e565b604051631e45990d60e31b81526001600160a01b0390911660048201529150602090829060249082905afa908115610c07578991610bd8575b503880610a55565b610bfa915060203d602011610c00575b610bf2818361234a565b810190612598565b38610bd0565b503d610be8565b6040513d8b823e3d90fd5b50861515610a39565b8895610a2f565b87966109f3565b90506020813d602011610c53575b81610c446020938361234a565b810103126102925751386109b6565b3d9150610c37565b6040513d8a823e3d90fd5b9094506020813d602011610c93575b81610c826020938361234a565b810103126102925751936020610991565b3d9150610c75565b6001600160a01b0389163b15610d3e578160a484926040519485938492630da4f2fd60e21b84523360048501526004356024850152602435604485015260806064850152816084850152848401378181018301859052601f01601f19168101030181836001600160a01b038d165af18015610d3357610d1b575b80610962565b610d24906122fe565b610d2f578738610d15565b8780fd5b6040513d84823e3d90fd5b8280fd5b610d4f6024358a8661285a565b61095c565b610d616004358a8961285a565b610954565b60405162461bcd60e51b815260206004820152601960248201527f537061726b53776170506169723a20494e56414c49445f544f000000000000006044820152606490fd5b506001600160a01b038916841415610948565b506001600160701b0384166024351061091c565b60405162461bcd60e51b815260206004820152602960248201527f537061726b53776170506169723a20494e53554646494349454e545f4f555450604482015268155517d05353d5539560ba1b6064820152608490fd5b5060243515156108f6565b606460405162461bcd60e51b815260206004820152602060248201527f537061726b53776170506169723a2043616c6c657220697320696e76616c69646044820152fd5b9050604051908152336004820152602081602481875afa9081156107d8578791610ea4575b50386108e3565b610ebd915060203d602011610c0057610bf2818361234a565b38610e9d565b33321491506108dc565b610ee791925060203d602011610c0057610bf2818361234a565b90386108d4565b60405162461bcd60e51b815260206004820152602660248201527f537061726b53776170506169723a2043616c6c6572206973206e6f742070657260448201526569706865727960d01b6064820152608490fd5b610f5b915060203d602011610c0057610bf2818361234a565b38610891565b8380fd5b50346102e55760403660031901126102e557610f8c610f826122a4565b6024359033612477565b602060405160018152f35b50346102e557806003193601126102e557610fdc604051610fb781612312565b600c81526b0537061726b537761702d4c560a41b60208201526040519182918261236c565b0390f35b50346102e55760203660031901126102e55760043561100a60018060a01b03600754163314612627565b6064811161104c576020817f8c4d35e54a3f2ef1134138fd8ea3daee6a3c89e10d2665996babdf70261e2c7692600555604051908152a1602060405160018152f35b606460405162461bcd60e51b815260206004820152602060248201527f537061726b53776170466163746f72793a20466565206774204d41585f4645456044820152fd5b50346102e557602090816003193601126102e5576110ac6122a4565b906110bb6001601154146125e3565b6011819055601054600f546001600160701b038083169260701c1690506008546009546040516370a0823160e01b8082523060048301529794966001600160a01b039384169690949192841686846024818b5afa938415610d335782946113c6575b5060405199808b523060048c0152878b602481855afa9889156113bb578a9b849b9a611388575b503084526002895261118061115e8660408720549c6126ff565b966111746111798d6111746001549485926125b0565b6125c3565b9d8d6125b0565b998b15158061137f575b15611325576024939291858b92308252600284526111ac81604084205461245d565b3083526002855260408320556111c48160015461245d565b600155604051908152600080516020612c0a833981519152843092a36111eb8d8b8361285a565b6111f68c8b8661285a565b604051948580928582523060048301525afa92831561029e5790899185946112f4575b5060246040518094819382523060048301525afa9283156112e857926112b2575b5098611249929160409a6129f6565b61128f575b855191858352848484015216907fdccd412f0b1252819cb1fd330b93224ca42612892bb3f4f789976e6d81936496863392a360016011558351928352820152f35b6112aa6010546001600160701b03808260701c1691166125b0565b600c5561124e565b929150988683813d83116112e1575b6112cb818361234a565b810103126102925791519198909190604061123a565b503d6112c1565b604051903d90823e3d90fd5b8281939295503d831161131e575b61130c818361234a565b81010312610292578890519238611219565b503d611302565b60405162461bcd60e51b8152600481018b9052602c60248201527f537061726b53776170506169723a20494e53554646494349454e545f4c49515560448201526b125112551657d0955493915160a21b6064820152608490fd5b508a151561118a565b8980929c50819b503d83116113b4575b6113a2818361234a565b81010312610292578a98519938611144565b503d611398565b6040513d85823e3d90fd5b9093508681813d83116113ee575b6113de818361234a565b810103126102925751923861111d565b503d6113d4565b50346102e55760203660031901126102e5576020906040906001600160a01b0361141d6122a4565b168152600483522054604051908152f35b50346102e557806003193601126102e5576020600c54604051908152f35b50346102e55760203660031901126102e5576020906040906001600160a01b036114746122a4565b168152600283522054604051908152f35b50346102e55760203660031901126102e55761149f6122a4565b906114ae6001601154146125e3565b60118190556010546008546040516370a0823160e01b80825230600483015290946001600160701b03607085901c811695941693926001600160a01b03906020908890602490829085165afa96871561029e578497611768575b50602090600954169260246040518095819382523060048301525afa9182156113bb578392611734575b506001600160701b03841694611548868861245d565b946001600160701b0382169361155e858261245d565b9561156984846126ff565b95600154998a156000146116fd57505061158b611586888a6125b0565b612b5b565b6103e7198101919082116116e95750976103e8810181116116d3576103e8016001556000805260026020526040600020546103e8810181116116d3576103e89060008052600260205201604060002055600080600080516020612c0a83398151915260206040516103e88152a35b87156116795760209861160f89611614966126a9565b6129f6565b611656575b604051918252838201527f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f60403392a26001601155604051908152f35b6116716010546001600160701b03808260701c1691166125b0565b600c55611619565b60405162461bcd60e51b815260206004820152602c60248201527f537061726b53776170506169723a20494e53554646494349454e545f4c49515560448201526b125112551657d3525395115160a21b6064820152608490fd5b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b81526011600452602490fd5b9061117461171461171b936111748e979e8e6125b0565b948a6125b0565b90508082101561172d57505b966115f9565b9050611727565b9091506020813d602011611760575b816117506020938361234a565b8101031261029257519038611532565b3d9150611743565b9096506020813d602011611795575b816117846020938361234a565b810103126102925751956020611508565b3d9150611777565b50346102e557806003193601126102e55760206040516127108152f35b50346102e5576117c9366123b5565b9091926117ea601054906001600160701b038083169260701c1690600f5490565b506001600160701b039182169591169384906118078315156124e1565b86151580611902575b6118199061253e565b600754604051631e45990d60e31b81526001600160a01b03968716600482015290602090829060249082908a165afa9081156102da5785916118e4575b50156118db5783945b80600954169116146118d2575b50612710928303908382116118be5761188f91611888916125b0565b93846125b0565b938281029281840414901517156116e95760206118b6856118b0868661246a565b906125c3565b604051908152f35b634e487b7160e01b83526011600452602483fd5b9493503861186c565b6005549461185f565b6118fc915060203d8111610c0057610bf2818361234a565b38611856565b50851515611810565b50346102e557806003193601126102e5576020600b54604051908152f35b50346102e557806003193601126102e5576020600a54604051908152f35b50346102e55760403660031901126102e5576119616122a4565b6119696122ba565b6007546001600160a01b039283916119849083163314612627565b16916bffffffffffffffffffffffff60a01b91838360085416176008551680916009541617600955604051809263313ce56760e01b9081835282600460209687935afa9182156102da5784926119e1918791611a3d575b50612698565b600d5560046040518094819382525afa9081156113bb57611a0a928492611a10575b5050612698565b600e5580f35b611a2f9250803d10611a36575b611a27818361234a565b81019061267f565b3880611a03565b503d611a1d565b611a549150843d8611611a3657611a27818361234a565b386119db565b50346102e557806003193601126102e55760209054604051908152f35b50346102e557806003193601126102e557602060405160128152f35b50346102e557806003193601126102e55760206040517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98152f35b50346102e55760603660031901126102e557611b2a611aeb6122a4565b611af36122ba565b6044359160018060a01b038116808652602095600387526040812033600052875260001960406000205403611b33575b5050612477565b60405160018152f35b80826040925260038852818120336000528852611b5486836000205461245d565b92815260038852203360005286526040600020553880611b23565b50346102e557806003193601126102e5576020600154604051908152f35b50346102e557806003193601126102e5576020600654604051908152f35b50346102e557806003193601126102e5576008546040516001600160a01b039091168152602090f35b50346102e557611be3366123b5565b6010546001600160701b03607082901c8116939116919083611c068615156124e1565b83151580611d20575b611c189061253e565b6009546001600160a01b0392831690831614611d16575b50600754604051631e45990d60e31b8152928216600484015260209183916024918391165afa9081156102da578591611cf8575b5015611ceb57611c748385926125b0565b926127109283850294808604851490151715611cd75790611c949161245d565b908203918211611cc357611cac92916118b0916125b0565b90600182018092116116e957602082604051908152f35b634e487b7160e01b84526011600452602484fd5b634e487b7160e01b86526011600452602486fd5b611c7483600554926125b0565b611d10915060203d8111610c0057610bf2818361234a565b38611c63565b9293506020611c2f565b50841515611c0f565b50346102e55760403660031901126102e557610f8c611d466122a4565b6024359033612406565b50346102e557806003193601126102e5576060611d81601054906001600160701b038083169260701c1690600f5490565b9091604051926001600160701b0380921684521660208301526040820152f35b50346102e557806003193601126102e557610fdc604051611dc181612312565b600d81526c537061726b53776170204c507360981b60208201526040519182918261236c565b50346102e55760803660031901126102e557611e0161228e565b60643567ffffffffffffffff8111610d3e57611e219036906004016122d0565b91611e306001601154146125e3565b60118490556007546040516304ad971560e21b808252600482018790526001600160a01b039492851690602083602481855afa928315610c5b57889361226d575b508215612263575b8215612216575b505015610e3457600435158015809161220b575b15610dd2576010546001600160701b038082169660709290921c16906004358711806121f7575b611ec49061253e565b856008541692866009541694848888161415806121eb575b15610d665789916121d9575b6024356121c7575b82612142575b505050604051916020836024816370a0823160e01b948582523060048301525afa928315610c5b57889361210d575b5060209060246040518096819382523060048301525afa9283156107d85787936120d9575b50611f606004356001600160701b03881661245d565b8211156120d257611f7f610a276004356001600160701b03891661245d565b955b611f966024356001600160701b03841661245d565b8411156120cb57611fb56109eb6024356001600160701b03851661245d565b935b87158015906120c2575b611fca906124e1565b88156120ba57885b8461271081020461271014851517156120a657611ff2610a7e828b6125b0565b9061271083029083820461271014841517156120925761201a9291610ab0610ab6928a6125b0565b612039610adb6001600160701b0386166001600160701b0386166125b0565b11610b4357612047936129f6565b60405193845260208401526004356040840152602435606084015216907fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d82260803392a3600160115580f35b634e487b7160e01b8c52601160045260248cfd5b634e487b7160e01b8a52601160045260248afd5b600554611fd2565b50841515611fc1565b8793611fb7565b8695611f81565b9092506020813d602011612105575b816120f56020938361234a565b8101031261029257519138611f4a565b3d91506120e8565b9092506020813d60201161213a575b816121296020938361234a565b810103126102925751916020611f25565b3d915061211c565b8787163b156107e35760a483916040519485938492630da4f2fd60e21b84523360048501526004356024850152602435604485015260806064850152816084850152848401378181018301859052601f01601f1916810103018183898b165af18015610c5b576121b4575b8781611ef6565b6121c0909791976122fe565b95386121ad565b6121d4602435888861285a565b611ef0565b6121e6600435888761285a565b611ee8565b50858888161415611edc565b506001600160701b03821660243510611ebb565b506024351515611e94565b602091925060246040518094819382523360048301525afa90811561079c578691612244575b503880611e80565b61225d915060203d602011610c0057610bf2818361234a565b3861223c565b3332149250611e79565b61228791935060203d602011610c0057610bf2818361234a565b9138611e71565b604435906001600160a01b038216820361029257565b600435906001600160a01b038216820361029257565b602435906001600160a01b038216820361029257565b9181601f840112156102925782359167ffffffffffffffff8311610292576020838186019501011161029257565b67ffffffffffffffff811161050557604052565b6040810190811067ffffffffffffffff82111761050557604052565b6080810190811067ffffffffffffffff82111761050557604052565b90601f8019910116810190811067ffffffffffffffff82111761050557604052565b6020808252825181830181905290939260005b8281106123a157505060409293506000838284010152601f8019910116010190565b81810186015184820160400152850161237f565b606090600319011261029257600435906001600160a01b03906024358281168103610292579160443590811681036102925790565b3461029257600036600319011261029257602060405160648152f35b909160207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259160018060a01b03809416938460005260038352604060002095169485600052825280604060002055604051908152a3565b919082039182116116d357565b919082018092116116d357565b90916020600080516020612c0a8339815191529160018060a01b0380941693600095858752600284526124ae83604089205461245d565b868852600285526040882055169485815260406124ce838284205461246a565b91878152600285522055604051908152a3565b156124e857565b60405162461bcd60e51b815260206004820152602860248201527f537061726b53776170506169723a20494e53554646494349454e545f494e50556044820152671517d05353d5539560c21b6064820152608490fd5b1561254557565b60405162461bcd60e51b815260206004820152602560248201527f537061726b53776170506169723a20494e53554646494349454e545f4c495155604482015264494449545960d81b6064820152608490fd5b90816020910312610292575180151581036102925790565b818102929181159184041417156116d357565b81156125cd570490565b634e487b7160e01b600052601260045260246000fd5b156125ea57565b60405162461bcd60e51b815260206004820152601560248201527414dc185c9ad4ddd85c14185a5c8e881313d0d2d151605a1b6044820152606490fd5b1561262e57565b60405162461bcd60e51b8152602060048201526024808201527f537061726b53776170506169723a2043616c6c6572206973206e6f7420666163604482015263746f727960e01b6064820152608490fd5b90816020910312610292575160ff811681036102925790565b60ff16604d81116116d357600a0a90565b600080516020612c0a83398151915260206000926126c98560015461246a565b60015560018060a01b031693848452600282526126ea81604086205461246a565b858552600283526040852055604051908152a3565b600754604051622fcfcb60e31b8152919392906001600160a01b03906020908490600490829085165afa92831561284e57600093612810575b50821615159081612804575b600c54919485156127f3578261275b575b50505050565b6115866127789161277e936001600160701b0380911691166125b0565b91612b5b565b80821161278c575b80612755565b61279c600154610ab6838561245d565b916127aa60065480946125b0565b9183606403606481116116d3576127d7946127cb6127d1926118b0956125b0565b926125b0565b9061246a565b806127e3575b80612786565b6127ec916126a9565b38806127dd565b505090506127fd57565b6000600c55565b60065415159150612744565b6020939193813d8211612846575b8161282b6020938361234a565b810103126107e357519083821682036102e557509138612738565b3d915061281e565b6040513d6000823e3d90fd5b906040519261286884612312565b601984527f7472616e7366657228616464726573732c75696e74323536290000000000000060209485015260405163a9059cbb60e01b8186019081526001600160a01b0390931660248201526044808201929092529081526128c98161232e565b600092839283809351925af1903d156129a4573d67ffffffffffffffff81116129905760405190612903601f8201601f191686018361234a565b81528091843d92013e5b81612960575b501561291c5750565b6064906040519062461bcd60e51b82526004820152601e60248201527f537061726b53776170506169723a205452414e534645525f4641494c454400006044820152fd5b80518015925083908315612978575b50505038612913565b6129889350820181019101612598565b38828161296f565b634e487b7160e01b82526041600452602482fd5b50606061290d565b61271060016002815b8082116129cd575050816000190481116116d3570290565b9092806000190481116116d3578184166129ed575b800292811c906129b5565b809202916129e2565b91926001600160701b03908184111580612b51575b15612b0c57816040947f1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad19663ffffffff421693612a4a600f548661245d565b80151580612b01575b80612af6575b612a9d575b505050169283601054916001600160701b0360701b9060701b169163ffffffff60e01b1617179081601055600f55835192835260701c166020820152a1565b612aeb92612ae3926001600160e01b0391612acd9190612ac890612adb612ad3878787858789612bb5565b612beb565b166125b0565b600a5461246a565b600a55612bb5565b600b5461246a565b600b55388080612a5e565b508482161515612a59565b508483161515612a53565b60405162461bcd60e51b815260206004820152601760248201527f537061726b53776170506169723a204f564552464c4f570000000000000000006044820152606490fd5b5081831115612a0b565b9060006003831115612ba857508160019080821c8281018091116116d35791905b848310612b8857505050565b90919350612b9f84612b9a81846125c3565b61246a565b821c9190612b7c565b91612baf57565b60019150565b6dffffffffffffffffffffffffffff60701b607082901b16906001600160701b0316808204600160701b14901517156116d35790565b906001600160701b03169081156125cd576001600160e01b0316049056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa264697066735822122006280758518c180494fe710957cf10bd3f48348fea2a289b77823eb8356759ca64736f6c63430008130033