// File: contracts/interfaces/IERC1155.sol



pragma solidity >=0.5.0;


interface IERC1155 {

  event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _amount);
  event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts);
  event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
  event URI(string _amount, uint256 indexed _id);


  function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount, bytes calldata _data) external;
  function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _amounts, bytes calldata _data) external;
  function balanceOf(address _owner, uint256 _id) external view returns (uint256);
  function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory);
  function setApprovalForAll(address _operator, bool _approved) external;
  function isApprovedForAll(address _owner, address _operator) external view returns (bool isOperator);
  
}
// File: contracts/MonetExchange.sol



pragma solidity =0.5.16;

import './libraries/SafeMath.sol';
import './interfaces/IERC20.sol';
import './interfaces/IERC1155.sol';

contract MonetExchange {
    using SafeMath for uint256;

    address public tokenCard;
    address public tokenMonet;
    
    bytes4 private constant ERC1155_RECEIVED_VALUE = 0xf23a6e61;
    bytes4 private constant ERC1155_BATCH_RECEIVED_VALUE = 0xbc197c81;

    uint256 public fee = 2;  // 0.2%
    address public feeTo;
    address public feeToSetter;
    
    uint256 private _nextId;
    mapping(uint256 => Order) internal _orders;

    struct Order {
        uint256 id;
        uint256 num;
        uint256 price;
        address owner;
        uint256 status;
        uint256 direction;
    }

    constructor(address card, address monet) public {
        tokenCard = card;
        tokenMonet = monet;
        feeTo = msg.sender;
        feeToSetter = msg.sender;
    }

    // VIEW

    // PRIVATE
    function _transferFromCards(address _from, address _to, uint256 _id, uint256 _value) private {
        IERC1155(tokenCard).safeTransferFrom(_from, _to, _id, _value, bytes(''));
    }

    function _transferFromMonet(address _from, address _to, uint256 _value) private {
        require(IERC20(tokenMonet).transferFrom(_from, _to, _value), 'transferFrom fail');
    }

    function _transfer(address _to, uint256 _value) private {
        require(IERC20(tokenMonet).transfer(_to, _value), 'transfer fail');
    }

    // EXTERNAL
    function trade(uint256 orderId, uint256 num) external {
        Order storage order = _orders[orderId];
        require(order.id != 0 && order.status == 0, 'order is empty');
        require(order.owner != msg.sender, 'order owner is caller');
        require(order.num >= num, 'order num is less');
        if (order.direction == 0) {
            uint256 feeAmount = num.mul(order.price).mul(2).div(1000);
            if (feeAmount > 0 && feeTo != address(0)) _transfer(feeTo, feeAmount);
            _transfer(msg.sender, num.mul(order.price));
            _transferFromCards(msg.sender, order.owner, order.id, num);
        } else {
            uint256 feeAmount = num.mul(order.price).mul(2).div(1000);
            if (feeAmount > 0 && feeTo != address(0)) _transferFromMonet(msg.sender, feeTo, feeAmount);
            _transferFromCards(address(this), msg.sender, order.id, num);
            _transferFromMonet(msg.sender, order.owner, num.mul(order.price));
        }
        order.num = order.num.sub(num);
        if (order.num == 0) {
            order.status = 1;
        }
        emit Trade(msg.sender, orderId, num);
    }

    function revoke(uint256 orderId) external {
        Order storage order = _orders[orderId];
        require(order.id != 0 && order.status == 0, 'order is empty');
        require(order.owner == msg.sender, 'caller is not the order owner');
        if (order.direction == 0) {
            uint256 amount = order.num.mul(order.price).mul(1002).div(1000);
            _transfer(msg.sender, amount);
        } else {
            _transferFromCards(address(this), msg.sender, order.id, order.num);
        }
        order.status = 2;
        emit Revoke(msg.sender, orderId);
    }

    function placeOrder(uint256 id, uint256 num, uint256 price, uint256 direction) external generateId {
        require(_nextId > 0, 'order id generate fail');
        require(direction < 2, 'direction is error');
        if (direction == 0) {
            uint256 amount = num.mul(price).mul(1002).div(1000);
            _transferFromMonet(msg.sender, address(this), amount);
        } else {
            _transferFromCards(msg.sender, address(this), id, num);
        }
        _orders[_nextId] = Order(id, num, price, msg.sender, 0, direction);
        emit PlaceOrder(msg.sender, _nextId, id, num, price, direction);
    }

    function setFeeTo(address _feeTo) external {
        require(msg.sender == feeToSetter, 'FORBIDDEN');
        feeTo = _feeTo;
    }

    function setFeeToSetter(address _feeToSetter) external {
        require(msg.sender == feeToSetter, 'FORBIDDEN');
        feeToSetter = _feeToSetter;
    }
    
    // IERC1155TokenReceiver
    function onERC1155Received(address, address, uint256, uint256, bytes calldata) external pure returns(bytes4){
        return ERC1155_RECEIVED_VALUE;
    }

    function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) external pure returns(bytes4){
        return ERC1155_BATCH_RECEIVED_VALUE;
    }

    // MODIFIER
    modifier generateId() {
        _nextId = _nextId.add(1);

        _;
    }

    // EVENT
    event Trade(address indexed sender, uint256 indexed orderId, uint256 num);
    event Revoke(address indexed sender, uint256 indexed orderId);
    event PlaceOrder(address indexed sender, uint256 orderId, uint256 id, uint256 num, uint256 price, uint256 direction);
}

// File: contracts/libraries/SafeMath.sol



pragma solidity =0.5.16;

// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)

library SafeMath {
    function add(uint x, uint y) internal pure returns (uint z) {
        require((z = x + y) >= x, 'ds-math-add-overflow');
    }

    function sub(uint x, uint y) internal pure returns (uint z) {
        require((z = x - y) <= x, 'ds-math-sub-underflow');
    }

    function mul(uint x, uint y) internal pure returns (uint z) {
        require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');
    }
    
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0);
        return a / b;
    }
}

// File: contracts/interfaces/IERC20.sol



pragma solidity >=0.5.0;

interface IERC20 {
    event Approval(address indexed owner, address indexed spender, uint value);
    event Transfer(address indexed from, address indexed to, uint 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 (uint);
    function balanceOf(address owner) external view returns (uint);
    function allowance(address owner, address spender) external view returns (uint);

    function approve(address spender, uint value) external returns (bool);
    function transfer(address to, uint value) external returns (bool);
    function transferFrom(address from, address to, uint value) external returns (bool);
}

// File: contracts/StakingRewardsFactory.sol



pragma solidity =0.5.16;

import './Ownable.sol';
import './StakingRewards.sol';
import './interfaces/IERC20.sol';

contract StakingRewardsFactory is Ownable {
    // immutables
    address public rewardsToken;
    uint public stakingRewardsGenesis;

    // the staking tokens for which the rewards contract has been deployed
    address[] public stakingTokens;

    // info about rewards for a particular staking token
    struct StakingRewardsInfo {
        address stakingRewards;
        uint rewardAmount;
    }

    // rewards info by staking token
    mapping(address => StakingRewardsInfo) public stakingRewardsInfoByStakingToken;

    constructor(
        address _rewardsToken,
        uint _stakingRewardsGenesis
    ) Ownable() public {
        require(_stakingRewardsGenesis >= block.timestamp, 'StakingRewardsFactory::constructor: genesis too soon');

        rewardsToken = _rewardsToken;
        stakingRewardsGenesis = _stakingRewardsGenesis;
    }

    ///// permissioned functions

    // deploy a staking reward contract for the staking token, and store the reward amount
    // the reward will be distributed to the staking reward contract no sooner than the genesis
    function deploy(address stakingToken, uint rewardAmount) public onlyOwner {
        StakingRewardsInfo storage info = stakingRewardsInfoByStakingToken[stakingToken];
        require(info.stakingRewards == address(0), 'StakingRewardsFactory::deploy: already deployed');

        info.stakingRewards = address(new StakingRewards(/*_rewardsDistribution=*/ address(this), rewardsToken, stakingToken));
        info.rewardAmount = rewardAmount;
        stakingTokens.push(stakingToken);
    }

    ///// permissionless functions

    // call notifyRewardAmount for all staking tokens.
    function notifyRewardAmounts() public {
        require(stakingTokens.length > 0, 'StakingRewardsFactory::notifyRewardAmounts: called before any deploys');
        for (uint i = 0; i < stakingTokens.length; i++) {
            notifyRewardAmount(stakingTokens[i]);
        }
    }

    // notify reward amount for an individual staking token.
    // this is a fallback in case the notifyRewardAmounts costs too much gas to call for all contracts
    function notifyRewardAmount(address stakingToken) public {
        require(block.timestamp >= stakingRewardsGenesis, 'StakingRewardsFactory::notifyRewardAmount: not ready');

        StakingRewardsInfo storage info = stakingRewardsInfoByStakingToken[stakingToken];
        require(info.stakingRewards != address(0), 'StakingRewardsFactory::notifyRewardAmount: not deployed');

        if (info.rewardAmount > 0) {
            uint rewardAmount = info.rewardAmount;
            info.rewardAmount = 0;

            require(
                IERC20(rewardsToken).transfer(info.stakingRewards, rewardAmount),
                'StakingRewardsFactory::notifyRewardAmount: transfer failed'
            );
            StakingRewards(info.stakingRewards).notifyRewardAmount(rewardAmount);
        }
    }
}
// File: contracts/Ownable.sol



pragma solidity =0.5.16;

contract Ownable {
    address private _owner;

    event OwnershipTransferred(
        address indexed previousOwner,
        address indexed newOwner
    );

    constructor() internal {
        _owner = msg.sender;
        emit OwnershipTransferred(address(0), _owner);
    }

    function owner() public view returns (address) {
        return _owner;
    }

    modifier onlyOwner() {
        require(isOwner(), "Ownable: caller is not the owner");
        _;
    }

    function isOwner() public view returns (bool) {
        return msg.sender == _owner;
    }

    function transferOwnership(address newOwner) public onlyOwner {
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

// File: contracts/StakingRewards.sol



pragma solidity =0.5.16;

// Inheritance
import './libraries/Math.sol';
import './libraries/SafeMath.sol';
import './libraries/SafeERC20.sol';
import "./interfaces/IStakingRewards.sol";
import './ReentrancyGuard.sol';
import "./RewardsDistributionRecipient.sol";

contract StakingRewards is IStakingRewards, RewardsDistributionRecipient, ReentrancyGuard {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    /* ========== STATE VARIABLES ========== */

    IERC20 public rewardsToken;
    IERC20 public stakingToken;
    uint256 public periodFinish = 0;
    uint256 public rewardRate = 0;
    uint256 public rewardsDuration = 60 days;
    uint256 public lastUpdateTime;
    uint256 public rewardPerTokenStored;

    mapping(address => uint256) public userRewardPerTokenPaid;
    mapping(address => uint256) public rewards;

    uint256 private _totalSupply;
    mapping(address => uint256) private _balances;

    /* ========== CONSTRUCTOR ========== */

    constructor(
        address _rewardsDistribution,
        address _rewardsToken,
        address _stakingToken
    ) public {
        rewardsToken = IERC20(_rewardsToken);
        stakingToken = IERC20(_stakingToken);
        rewardsDistribution = _rewardsDistribution;
    }

    /* ========== VIEWS ========== */

    function totalSupply() external view returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) external view returns (uint256) {
        return _balances[account];
    }

    function lastTimeRewardApplicable() public view returns (uint256) {
        return Math.min(block.timestamp, periodFinish);
    }

    function rewardPerToken() public view returns (uint256) {
        if (_totalSupply == 0) {
            return rewardPerTokenStored;
        }
        return
            rewardPerTokenStored.add(
                lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(1e18).div(_totalSupply)
            );
    }

    function earned(address account) public view returns (uint256) {
        return _balances[account].mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]);
    }

    function getRewardForDuration() external view returns (uint256) {
        return rewardRate.mul(rewardsDuration);
    }

    /* ========== MUTATIVE FUNCTIONS ========== */

    function stakeWithPermit(uint256 amount, uint deadline, uint8 v, bytes32 r, bytes32 s) external nonReentrant updateReward(msg.sender) {
        require(amount > 0, "Cannot stake 0");
        _totalSupply = _totalSupply.add(amount);
        _balances[msg.sender] = _balances[msg.sender].add(amount);

        // permit
        IUniswapV2ERC20(address(stakingToken)).permit(msg.sender, address(this), amount, deadline, v, r, s);

        stakingToken.safeTransferFrom(msg.sender, address(this), amount);
        emit Staked(msg.sender, amount);
    }

    function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
        require(amount > 0, "Cannot stake 0");
        _totalSupply = _totalSupply.add(amount);
        _balances[msg.sender] = _balances[msg.sender].add(amount);
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);
        emit Staked(msg.sender, amount);
    }

    function withdraw(uint256 amount) public nonReentrant updateReward(msg.sender) {
        require(amount > 0, "Cannot withdraw 0");
        _totalSupply = _totalSupply.sub(amount);
        _balances[msg.sender] = _balances[msg.sender].sub(amount);
        stakingToken.safeTransfer(msg.sender, amount);
        emit Withdrawn(msg.sender, amount);
    }

    function getReward() public nonReentrant updateReward(msg.sender) {
        uint256 reward = rewards[msg.sender];
        if (reward > 0) {
            rewards[msg.sender] = 0;
            rewardsToken.safeTransfer(msg.sender, reward);
            emit RewardPaid(msg.sender, reward);
        }
    }

    function exit() external {
        withdraw(_balances[msg.sender]);
        getReward();
    }

    /* ========== RESTRICTED FUNCTIONS ========== */

    function notifyRewardAmount(uint256 reward) external onlyRewardsDistribution updateReward(address(0)) {
        if (block.timestamp >= periodFinish) {
            rewardRate = reward.div(rewardsDuration);
        } else {
            uint256 remaining = periodFinish.sub(block.timestamp);
            uint256 leftover = remaining.mul(rewardRate);
            rewardRate = reward.add(leftover).div(rewardsDuration);
        }

        // Ensure the provided reward amount is not more than the balance in the contract.
        // This keeps the reward rate in the right range, preventing overflows due to
        // very high values of rewardRate in the earned and rewardsPerToken functions;
        // Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
        uint balance = rewardsToken.balanceOf(address(this));
        require(rewardRate <= balance.div(rewardsDuration), "Provided reward too high");

        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp.add(rewardsDuration);
        emit RewardAdded(reward);
    }

    /* ========== MODIFIERS ========== */

    modifier updateReward(address account) {
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = lastTimeRewardApplicable();
        if (account != address(0)) {
            rewards[account] = earned(account);
            userRewardPerTokenPaid[account] = rewardPerTokenStored;
        }
        _;
    }

    /* ========== EVENTS ========== */

    event RewardAdded(uint256 reward);
    event Staked(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);
    event RewardPaid(address indexed user, uint256 reward);
}

interface IUniswapV2ERC20 {
    function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
}

// File: contracts/libraries/Math.sol



pragma solidity =0.5.16;

// a library for performing various math operations

library Math {
    function min(uint x, uint y) internal pure returns (uint z) {
        z = x < y ? x : y;
    }

    // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
    function sqrt(uint y) internal pure returns (uint z) {
        if (y > 3) {
            z = y;
            uint x = y / 2 + 1;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }
}

// File: contracts/libraries/SafeERC20.sol



pragma solidity =0.5.16;

import "../interfaces/IERC20.sol";
import "./SafeMath.sol";
import "./Address.sol";

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

    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        // solhint-disable-next-line max-line-length
        require((value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).add(value);
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).sub(value);
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves.

        // A Solidity high level call has three parts:
        //  1. The target address is checked to verify it contains contract code
        //  2. The call itself is made, and success asserted
        //  3. The return value is decoded, which in turn checks the size of the returned data.
        // solhint-disable-next-line max-line-length
        require(address(token).isContract(), "SafeERC20: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = address(token).call(data);
        require(success, "SafeERC20: low-level call failed");

        if (returndata.length > 0) { // Return data is optional
            // solhint-disable-next-line max-line-length
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}
// File: contracts/interfaces/IStakingRewards.sol



pragma solidity >=0.5.0;


interface IStakingRewards {
    // Views
    function lastTimeRewardApplicable() external view returns (uint256);

    function rewardPerToken() external view returns (uint256);

    function earned(address account) external view returns (uint256);

    function getRewardForDuration() external view returns (uint256);

    function totalSupply() external view returns (uint256);

    function balanceOf(address account) external view returns (uint256);

    // Mutative

    function stake(uint256 amount) external;

    function withdraw(uint256 amount) external;

    function getReward() external;

    function exit() external;
}
// File: contracts/ReentrancyGuard.sol



pragma solidity =0.5.16;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the `nonReentrant` modifier
 * available, which can be aplied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 */
contract ReentrancyGuard {
    /// @dev counter to allow mutex lock with only one SSTORE operation
    uint256 private _guardCounter;

    constructor () internal {
        // The counter starts at one to prevent changing it from zero to a non-zero
        // value, which is a more expensive operation.
        _guardCounter = 1;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _guardCounter += 1;
        uint256 localCounter = _guardCounter;
        _;
        require(localCounter == _guardCounter, "ReentrancyGuard: reentrant call");
    }
}
// File: contracts/RewardsDistributionRecipient.sol



pragma solidity =0.5.16;

contract RewardsDistributionRecipient {
    address public rewardsDistribution;

    function notifyRewardAmount(uint256 reward) external;

    modifier onlyRewardsDistribution() {
        require(msg.sender == rewardsDistribution, "Caller is not RewardsDistribution contract");
        _;
    }
}
// File: contracts/libraries/Address.sol



pragma solidity =0.5.16;

library Address {

    /**
     * Returns whether the target address is a contract
     * @dev This function will return false if invoked during the constructor of a contract,
     * as the code is not actually created until after the constructor finishes.
     * @param account address of the account to check
     * @return whether the target address is a contract
     */
    function isContract(address account) internal view returns (bool) {
        uint256 size;
        // XXX Currently there is no better way to check if there is a contract in an address
        // than to check the size of the code at that address.
        // See https://ethereum.stackexchange.com/a/14016/36603
        // for more details about how this works.
        // TODO Check this again before the Serenity release, because all addresses will be
        // contracts then.
        // solium-disable-next-line security/no-inline-assembly
        assembly { size := extcodesize(account) }
        return size > 0;
    }

}
// File: contracts/MonetCardToken.sol



pragma solidity =0.5.16;

import "./libraries/Address.sol";
import "./libraries/SafeMath.sol";
import "./interfaces/IERC1155TokenReceiver.sol";
import "./Minter.sol";

contract MonetCardToken is Minter {
    using Address for address;
    using SafeMath for uint256;

    bytes4 private constant ERC1155_RECEIVED_VALUE = 0xf23a6e61;
    bytes4 private constant ERC1155_BATCH_RECEIVED_VALUE = 0xbc197c81;
    bytes4 private constant ERC1155_INTERFACE_ID = 0xd9b67a26;

    mapping(address => mapping(uint256 => uint256)) internal _balances;
    mapping(address => mapping(address => bool)) internal _operators;
    mapping(uint256 => uint256) internal _totalSupplies;

    // VIEW

    function isApprovedForAll(address _owner, address _operator) public view returns (bool) {
        return _operators[_owner][_operator];
    }

    function totalSupply(uint256 _id) public view returns (uint256) {
        return _totalSupplies[_id];
    }

    function balanceOf(address _owner, uint256 _id) external view returns (uint256) {
        return _balances[_owner][_id];
    }

    function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory) {
        require(_owners.length == _ids.length, "INVALID_ARRAY_LENGTH");

        uint256[] memory batchBalances = new uint256[](_owners.length);
        for (uint256 i = 0; i < _owners.length; i++) {
            batchBalances[i] = _balances[_owners[i]][_ids[i]];
        }
        return batchBalances;
    }

    function cardsNumOf(address _owner, uint256 _level,uint256 _carry) public view returns (uint256 nums) {
        for (uint256 i = 0; i < 4; i++) {
            uint256 num = _balances[_owner][_level.mul(10).add(i)];
            nums = nums.add((_carry**(3 - i)).mul(num));
        }
        return nums;
    }

    function cardsNumOfAll(address _owner, uint256 _carry) public view returns (uint256[10] memory nums) {
        uint256 levelMax = 10;
        for (uint256 i = 0; i < levelMax; i++) {
            nums[i] = cardsNumOf(_owner, levelMax.sub(i), _carry);
        }
    }

    function cardsTotalSupply() public view returns (uint256[40] memory nums) {
        uint256 idx;
        for (uint256 i = 10; i > 0; i--) {
            for (uint256 j = 0; j < 4; j++) {
                nums[idx++] = _totalSupplies[i.mul(10).add(j)];
            }
        }
    }

    function supportsInterface(bytes4 _interfaceID) external pure returns (bool) {
        return _interfaceID == ERC1155_INTERFACE_ID;
    }

    // PRIVATE
    function _mintBatch(address _to, uint256[] memory _ids, uint256[] memory _values) private {
        require(_to != address(0), "INVALID_RECIPIENT");

        uint256 size = _ids.length;
        for (uint256 i = 0; i < size; i++) {
            _totalSupplies[_ids[i]] = _totalSupplies[_ids[i]].add(_values[i]);
            _balances[_to][_ids[i]] = _balances[_to][_ids[i]].add(_values[i]);
        }
        emit TransferBatch(msg.sender, address(0), _to, _ids, _values);
    }

    function _safeBatchBurnFrom(address _from, uint256[] memory _ids, uint256[] memory _values ) private {
        require(_ids.length == _values.length, "INVALID_ARRAYS_LENGTH");

        uint256 size = _ids.length;
        for (uint256 i = 0; i < size; i++) {
            _balances[_from][_ids[i]] = _balances[_from][_ids[i]].sub(
                _values[i]
            );
            _totalSupplies[_ids[i]] = _totalSupplies[_ids[i]].sub(_values[i]);
        }

        emit TransferBatch(msg.sender, _from, address(0), _ids, _values);
    }

    function _safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount) private {
        _balances[_from][_id] = _balances[_from][_id].sub(_amount); // Subtract amount
        _balances[_to][_id] = _balances[_to][_id].add(_amount); // Add amount

        emit TransferSingle(msg.sender, _from, _to, _id, _amount);
    }

    function _safeBatchTransferFrom(
        address _from,
        address _to,
        uint256[] memory _ids,
        uint256[] memory _amounts
    ) private {
        require(_ids.length == _amounts.length, "INVALID_ARRAYS_LENGTH");

        uint256 size = _ids.length;
        for (uint256 i = 0; i < size; i++) {
            _balances[_from][_ids[i]] = _balances[_from][_ids[i]].sub(
                _amounts[i]
            );
            _balances[_to][_ids[i]] = _balances[_to][_ids[i]].add(_amounts[i]);
        }

        emit TransferBatch(msg.sender, _from, _to, _ids, _amounts);
    }

    function _callonERC1155Received(address _from, address _to, uint256 _id, uint256 _amount, bytes memory _data) private {
        if (_to.isContract()) {
            bytes4 retval = IERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _id, _amount, _data);
            require(retval == ERC1155_RECEIVED_VALUE, "INVALID_ON_RECEIVE_MESSAGE");
        }
    }

    function _callonERC1155BatchReceived(address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data) private {
        if (_to.isContract()) {
            bytes4 retval = IERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _from, _ids, _amounts, _data );
            require(retval == ERC1155_BATCH_RECEIVED_VALUE, "INVALID_ON_RECEIVE_MESSAGE");
        }
    }

    // EXTERNAL
    function safeBatchMint(address _to, uint256[] calldata _ids, uint256[] calldata _values) external onlyMinter {
        require(_ids.length == _values.length, "INVALID_ARRAYS_LENGTH");
        _mintBatch(_to, _ids, _values);
    }

    function safeBatchBurnFrom(address _from, uint256[] calldata _ids, uint256[] calldata _amounts) external {
        require((msg.sender == _from) || isApprovedForAll(_from, msg.sender));

        _safeBatchBurnFrom(_from, _ids, _amounts);
    }

    function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount, bytes calldata _data) external {
        require((msg.sender == _from) || isApprovedForAll(_from, msg.sender));
        require(_to != address(0), "INVALID_RECIPIENT");

        _safeTransferFrom(_from, _to, _id, _amount);
        _callonERC1155Received(_from, _to, _id, _amount, _data);
    }

    function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _amounts, bytes calldata _data) external {
        require((msg.sender == _from) || isApprovedForAll(_from, msg.sender));
        require(_to != address(0), "INVALID_RECIPIENT");

        _safeBatchTransferFrom(_from, _to, _ids, _amounts);
        _callonERC1155BatchReceived(_from, _to, _ids, _amounts, _data);
    }

    function cardsBatchMint(address _to, uint256[] calldata _cards) external onlyMinter {
        uint256[] memory _ids = new uint256[](_cards.length);
        uint256[] memory _values = new uint256[](_cards.length);
        for (uint256 i = 0; i < _cards.length; i++) {
            _ids[i] = _cards[i] % 1000;
            _values[i] = _cards[i] / 1000;
        }
        _mintBatch(_to, _ids, _values);
    }

    function cardsBatchBurnFrom(address _from, uint256[] calldata _cards) external {
        require((msg.sender == _from) || isApprovedForAll(_from, msg.sender));

        uint256[] memory _ids = new uint256[](_cards.length);
        uint256[] memory _values = new uint256[](_cards.length);
        for (uint256 i = 0; i < _cards.length; i++) {
            _ids[i] = _cards[i] % 1000;
            _values[i] = _cards[i] / 1000;
        }

        _safeBatchBurnFrom(_from, _ids, _values);
    }

    function setApprovalForAll(address _operator, bool _approved) external {
        _operators[msg.sender][_operator] = _approved;
        emit ApprovalForAll(msg.sender, _operator, _approved);
    }

    // EVENT
    event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _amount);
    event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts);
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
    event URI(string _amount, uint256 indexed _id);
}

// File: contracts/interfaces/IERC1155TokenReceiver.sol



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



pragma solidity =0.5.16;

import './Ownable.sol';

contract Minter is Ownable {
    
    mapping(address => bool) private _minters;
    
    event MinterChanged(address indexed minter, bool approved);


    modifier onlyMinter {
        require(isMinter(), "Minter: caller is not the minter");
        _;
    }

    function isMinter() public view returns (bool){
        return _minters[msg.sender];
    }
    
    function setMinter(address _minter,bool _approved) external onlyOwner {
        _minters[_minter] = _approved;
        emit MinterChanged(_minter,_approved);
    }

}
// File: contracts/MonetLuckyToken.sol



pragma solidity =0.5.16;

import './Minter.sol';
import './MonetTokenERC20.sol';

contract MonetLuckyToken is MonetTokenERC20 , Minter{
    string public constant name = 'Monet Lucky';
    string public constant symbol = 'L-MNT';
    uint8 public constant decimals = 18;

    
    function mint(uint value) external onlyMinter returns (bool){
        _mint(msg.sender,value);
        return true;
    }
    
    function mintFrom(address from,uint value) external returns(bool){
        _mint(from,value);
        return true;
    }

    function burn(uint value) external returns (bool) {
        _burn(msg.sender, value);
        return true;
    }

    function burnFrom(address from, uint value) external returns (bool) {
        if (allowance[from][msg.sender] != uint256(-1)) {
            allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
        }
        _burn(from, value);
        return true;
    }
}

// File: contracts/MonetTokenERC20.sol



pragma solidity =0.5.16;

import './interfaces/IERC20.sol';
import './libraries/SafeMath.sol';

contract MonetTokenERC20 is IERC20 {
    using SafeMath for uint;

    uint public totalSupply;
    mapping(address => uint) public balanceOf;
    mapping(address => mapping(address => uint)) public allowance;


    event Approval(address indexed owner, address indexed spender, uint value);
    event Transfer(address indexed from, address indexed to, uint value);

    function _mint(address to, uint value) internal {
        totalSupply = totalSupply.add(value);
        balanceOf[to] = balanceOf[to].add(value);
        emit Transfer(address(0), to, value);
    }

    function _burn(address from, uint256 value) internal {
        balanceOf[from] = balanceOf[from].sub(value);
        totalSupply = totalSupply.sub(value);
        emit Transfer(from, address(0), value);
    }

    function _approve(address owner, address spender, uint value) private {
        allowance[owner][spender] = value;
        emit Approval(owner, spender, value);
    }

    function _transfer(address from, address to, uint value) private {
        balanceOf[from] = balanceOf[from].sub(value);
        balanceOf[to] = balanceOf[to].add(value);
        emit Transfer(from, to, value);
    }

    function approve(address spender, uint value) external returns (bool) {
        _approve(msg.sender, spender, value);
        return true;
    }

    function transfer(address to, uint value) external returns (bool) {
        _transfer(msg.sender, to, value);
        return true;
    }

    function transferFrom(address from, address to, uint value) external returns (bool) {
        if (allowance[from][msg.sender] != uint(-1)) {
            allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
        }
        _transfer(from, to, value);
        return true;
    }
}

// File: contracts/test/ERC20.sol



pragma solidity =0.5.16;

import '../MonetTokenERC20.sol';

contract ERC20 is MonetTokenERC20 {
    constructor(uint _totalSupply) public {
        _mint(msg.sender, _totalSupply);
    }
}

// File: contracts/MonetToken.sol



pragma solidity =0.5.16;

import './MonetTokenERC20.sol';

contract MonetToken is MonetTokenERC20 {
    
    string public constant name = "Monet";
    string public constant symbol = "MNT";
    uint8 public constant decimals = 18;

    constructor() public {
        _mint(msg.sender,21000e18);
    }
}

// File: contracts/MonetV1Router.sol



pragma solidity =0.5.16;

import "./libraries/SafeMath.sol";
import "./libraries/Math.sol";
import "./libraries/Card.sol";

contract MonetV1Router {
    using SafeMath for uint256;

    address public tokenCard;
    address public tokenLucky;
    address public tokenMonet;

    uint256 public cardsReward = 1575e18;

    uint256 public startTime;
    uint256 public lotteryPrice = 1e18;

    uint256 public rate = 8;
    uint256 public ratePeriodFinish;

    uint256 public laveCards = 8e3;
    uint256 public lavePeriodFinish;
    
    uint256 public unit = 1e4;

    constructor(address lucky, address card, address monet) public {
        tokenCard = card;
        tokenLucky = lucky;
        tokenMonet = monet;
        startTime = block.timestamp;
        lavePeriodFinish = block.timestamp.add(1 days);
        ratePeriodFinish = block.timestamp.add(30 days);
    }


    // VIEW
    function cardsNumOf(address from, uint256 level) public view returns (uint256) {
        return ICardERC(tokenCard).cardsNumOf(from, level, unit);
    }

    function cardsNumOfAll(address from) public view returns (uint256[10] memory) {
        return ICardERC(tokenCard).cardsNumOfAll(from, unit);
    }

    // PRIVATE
    function _pkgCards( uint256 umax, uint256 mmax, uint256[] memory unums, uint256[] memory mnums, uint256 level ) private view returns (uint256[] memory ucards, uint256[] memory mcards) {
        require( unums.length == mnums.length, "nnums size not equal to mnums");
        ucards = new uint256[](umax);
        mcards = new uint256[](mmax);
        uint256 upos;
        uint256 mpos;
        for (uint256 i = 0; i < unums.length; i++) {
            if (unums[i] > 0) (upos, ) = _makeCards(level.sub(i), unums[i], upos, ucards);
            if (mnums[i] > 0) (mpos, ) = _makeCards(level.sub(i).sub(1), mnums[i], mpos, mcards);
        }
    }

    function _calcLevelCardsMerge( address from, uint256 level, uint256 num ) private view returns (uint256[] memory, uint256[] memory) {
        uint256 nums = cardsNumOf(from, level);
        uint256 min = Card.min(nums, unit);
        require(num <= min, "insufficient card resources");

        uint256[] memory unums = new uint256[](1);
        uint256[] memory mnums = new uint256[](1);
        (uint256 mmax, uint256 merge) = _randCards(0, num);
        unums[0] = Card.make(num, num, num, num, unit);
        mnums[0] = merge;
        return _pkgCards(4, mmax, unums, mnums, level);
    }

    function _calcCardsMerge(address from) private view returns (uint256[] memory, uint256[] memory) {
        uint256 colorMax = 4;
        uint256[10] memory nums = cardsNumOfAll(from);
        uint256[] memory mnums = new uint256[](10);
        uint256[] memory unums = new uint256[](10);
        uint256 umax;
        uint256 mmax;
        uint256 seed = uint256(keccak256(abi.encodePacked(block.difficulty, now)));
        for (uint256 i = 0; i < 9; i++) {
            uint256 _num = nums[i];
            if (i > 0 && mnums[i.sub(1)] > 0) _num = Card.merge(_num, mnums[i.sub(1)], unit);
            uint256 min = Card.min(_num, unit);
            if (min == 0) continue;
            (uint256 num, uint256 merge) = _randCards((seed = seed >> 1), min);
            mnums[i] = merge;
            mmax = mmax.add(num);
            umax = umax.add(colorMax);

            uint256 ucard = Card.make(min, min, min, min, unit);
            if (i > 0 && mnums[i.sub(1)] > 0) {
                uint256 cards = mnums[i.sub(1)];
                if (cards == 0) {
                    continue;
                }
                for (uint256 j = 0; j < colorMax; j++) {
                    uint256 cnum = Card.num(cards, j, unit);
                    uint256 mnum = Math.min(cnum, min);
                    if (mnum > 0) {
                        cards = Card.sub(cards, j, mnum, unit);
                        ucard = Card.sub(ucard, j, mnum, unit);
                        if (mnum == min) umax = umax.sub(1);
                        if (mnum == cnum) mmax = mmax.sub(1);
                    }
                }
                mnums[i.sub(1)] = cards;
            }
            unums[i] = ucard;
        }
        return _pkgCards(umax, mmax, unums, mnums, 10);
    }

    function _makeCards( uint256 level, uint256 nums, uint256 pos, uint256[] memory cards ) private view returns (uint256, uint256[] memory) {
        for (uint256 i = 0; i < 4; i++) {
            uint256 num = Card.num(nums, i, unit);
            if (num > 0) {
                require(pos < cards.length, "pos gt array size");
                cards[pos] = num.mul(1000).add(level.mul(10)).add(i);
                pos = pos.add(1);
            }
        }
        return (pos, cards);
    }

    function _randCards(uint256 _seed, uint256 _num) private view returns (uint256 num, uint256) {
        uint256[4] memory nums = [uint256(0), 0, 0, 0];
        if (_seed == 0)
            _seed = uint256(keccak256(abi.encodePacked(block.difficulty, now)));
        for (uint256 i = 0; i < _num; i++) {
            uint256 color = (_seed = _seed / 10 - _seed) % 4;
            nums[color] = nums[color].add(1);
            if (nums[color] == 1) num = num.add(1);
        }
        return (num, Card.make(nums[0], nums[1], nums[2], nums[3], unit));
    }

    function _transferCards( address from, uint256[] memory ucards, uint256[] memory mcards ) private {
        require(ucards.length > 0 && mcards.length > 0, "merge error");

        ICardERC(tokenCard).cardsBatchBurnFrom(from, ucards);
        ICardERC(tokenCard).cardsBatchMint(from, mcards);
        emit TransferCards(from, ucards, mcards);
    }

    function merge(uint256 level, uint256 num) external validateSender(msg.sender) {
        require(level > 1 && level <= 10, "illegal level");

        uint256[] memory ucards;
        uint256[] memory mcards;
        (ucards, mcards) = _calcLevelCardsMerge(msg.sender, level, num);
        _transferCards(msg.sender, ucards, mcards);
    }

    function oneKeyMerge() external validateSender(msg.sender) {
        uint256[] memory ucards;
        uint256[] memory mcards;
        (ucards, mcards) = _calcCardsMerge(msg.sender);
        _transferCards(msg.sender, ucards, mcards);
    }

    function lottery(uint256 num) external validateSender(msg.sender) rateRevise laveCardsReset {
        require(num > 0, "num equals zero");
        require(IERC20(tokenLucky).burnFrom(msg.sender, num.mul(lotteryPrice)), "lucky burn fail");     

        uint256 _rate = rate;
        uint256 _laveCards = laveCards;
        uint256 obtain;
        uint256 seed = uint256(keccak256(abi.encodePacked(block.difficulty, now)));
        for (uint256 i = 0; i < num; i++) {
            uint256 random = (seed = seed / 1000 - seed) % 1000;
            if (random < _rate && _laveCards > 0) {
                obtain = obtain.add(1);
                _laveCards = _laveCards.sub(1);
            }
        }
        laveCards = _laveCards;

        uint256[] memory cards;
        if (obtain > 0) {
            (uint256 cnum, uint256 mnums) = _randCards(seed, obtain);
            cards = new uint256[](cnum);
            _makeCards(10, mnums, 0, cards);

            ICardERC(tokenCard).cardsBatchMint(msg.sender, cards);
        }
        emit Lottery(msg.sender, cards);
    }

    function reward(uint256 color, uint256 num) external validateSender(msg.sender) {
        require(color < 4 && num > 0, "color gt 4");

        uint256[] memory cards = new uint256[](1);
        cards[0] = color.add(10).add(num.mul(1000));

        ICardERC(tokenCard).cardsBatchBurnFrom(msg.sender, cards);
        IERC20(tokenMonet).transfer(msg.sender, num.mul(cardsReward));

        emit Reward(msg.sender, cards);
    }

    // MODIFIER
    modifier rateRevise(){
        if (rate != 1 && block.timestamp > ratePeriodFinish) {
            rate = rate.div(2);
            ratePeriodFinish = ratePeriodFinish.add(30 days);
        }

        _;
    }

    modifier laveCardsReset() {
        if (lavePeriodFinish <= block.timestamp) {
            laveCards = rate.mul(1e3);
            lavePeriodFinish = lavePeriodFinish.add(1 days);
        }  

        _;
    }

    modifier validateSender(address account) {
        uint256 size;
        assembly { size := extcodesize(account) }
        require(size == 0, "account is contract");

        _;
    }

    // EVENT
    event Reward(address indexed sender, uint256[] cards);
    event Lottery(address indexed sender, uint256[] cards);
    event TransferCards(address indexed sender, uint256[] burnCards, uint256[] issueCards);
}

interface IERC20 {
    function allowance(address owner, address spender) external view returns (uint256);
    function transfer(address to, uint256 value) external returns (bool);
    function burnFrom(address from, uint256 value) external returns (bool);
}

interface ICardERC {
    function cardsBatchMint(address _to, uint256[] calldata _cards) external;
    function cardsBatchBurnFrom(address _from, uint256[] calldata _cards) external;
    function cardsNumOf(address _owner, uint256 _level, uint256 _carry) external view returns (uint256);
    function cardsNumOfAll(address _owner, uint256 _carry) external view returns (uint256[10] memory);
    function balanceOf(address _owner, uint256 _id) external view returns (uint256);
    function isApprovedForAll(address _owner, address _operator) external view returns (bool isOperator);
}
// File: contracts/libraries/Card.sol



pragma solidity =0.5.16;

import "./SafeMath.sol";

library Card {
    using SafeMath for uint256;

    function make(uint256 x, uint256 y, uint256 z, uint256 u, uint256 unit) internal pure returns (uint256) {
        return x.mul(unit**3).add(y.mul(unit**2)).add(z.mul(unit)).add(u);
    }

    function num(uint256 x, uint256 y, uint256 unit) internal pure returns (uint256) {
        return x.div(unit**(uint256(3).sub(y))) % unit;
    }

    function sub(uint256 x, uint256 y, uint256 z, uint256 unit) internal pure returns (uint256) {
        return x.sub(z.mul(unit**(uint256(3).sub(y))));
    }

    function merge(uint256 x, uint256 y, uint256 unit) internal pure returns (uint256) {
        uint256 a = num(x, 0, unit).add(num(y, 0, unit));
        uint256 b = num(x, 1, unit).add(num(y, 1, unit));
        uint256 c = num(x, 2, unit).add(num(y, 2, unit));
        uint256 d = num(x, 3, unit).add(num(y, 3, unit));
        return make(a, b, c, d, unit);
    }

    function min(uint256 x, uint256 unit) internal pure returns (uint256) {
        uint256 _min = num(x, 0, unit);
        for (uint256 i = 1; i < 4; i++) {
            uint256 _num = num(x, i, unit);
            if (_num < _min) _min = _num;
        }
        return _min;
    }
}

// File: contracts/MonetAirdrop.sol



pragma solidity =0.5.16;

import "./Ownable.sol";
import "./libraries/SafeMath.sol";

contract MonetAirdrop is Ownable {
    using SafeMath for uint256;

    address public tokenCards;
    uint256 public laveCards;
    mapping(uint256 => uint256) cardNums;
    mapping(address => bool) public partner;

    constructor(address cards) public {
        tokenCards = cards;
    }

    // EXTERNAL
    function notify(address[] calldata accounts, uint256[] calldata cards) external onlyOwner {
        setCards(cards);
        setPartner(accounts);
    }

    function setPartner(address[] memory accounts) public onlyOwner {
        for (uint256 i = 0; i < accounts.length; i++) {
            partner[accounts[i]] = true;
        }
    }

    function setCards(uint256[] memory cards) public onlyOwner {
        require(cards.length > 0, "cards is empty");

        uint256 levelMax = 10;
        uint256 _laveCards = laveCards;
        for (uint256 i = 0; i < cards.length; i++) {
            cardNums[levelMax.sub(i)] = cards[i];
            _laveCards = _laveCards.add(cards[i]);
        }
        laveCards = _laveCards;
    }

    function airdrop() external onlyCaller(msg.sender) {
        require(laveCards > 0, "lave cards is zero");
        require(partner[msg.sender], "Caller is not partner");

        uint256 seed = uint256(
            keccak256(abi.encodePacked(block.difficulty, now))
        );
        uint256 num = 0;
        uint256 random = seed % laveCards;
        for (uint256 i = 10; i > 4; i--) {
            if (cardNums[i] == 0) continue;
            num = num.add(cardNums[i]);
            if (random <= num) {
                partner[msg.sender] = false;
                laveCards = laveCards.sub(1);
                cardNums[i] = cardNums[i].sub(1);
                uint256 color = (seed / 10 - seed) % 4;
                uint256[] memory cards = new uint256[](1);
                cards[0] = i.mul(10).add(color).add(1000);
                ICardERC(tokenCards).cardsBatchMint(msg.sender, cards);
                emit Airdrop(msg.sender, cards[0]);
                return;
            }
        }
    }

    // MODIFIER
    modifier onlyCaller(address account) {
        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        require(size == 0, "account is contract");

        _;
    }

    // EVENT
    event Airdrop(address indexed sender, uint256 card);
}

interface ICardERC {
    function cardsBatchMint(address _to, uint256[] calldata _cards) external;
}