contract SilverToken is Destructible, Pausable, MintableToken, BurnableToken, DetailedERC20("Silvertoken", "SLVT", 8), Whitelist {
  using SafeMath for uint256;
  using ECRecovery for bytes32;

  uint256 public transferFee = 10;//1%
  uint256 public transferDiscountFee = 8;//0.8%
  uint256 public redemptionFee = 40;//4%
  uint256 public convertFee = 10;//1%
  address public feeReturnAddress = 0xE34f13B2dadC938f44eCbC38A8dBe94B8bdB2109;
  uint256 public transferFreeAmount;
  uint256 public transferDiscountAmount;
  address public silverDollarAddress;
  address public SLVTReserve = 0x900122447a2Eaeb1655C99A91E20f506D509711B;
  bool    public canPurchase = true;
  bool    public canConvert = true;

  // Internal features

  uint256 internal multiplier;
  uint256 internal percentage = 1000;

  //ce4385affa8ad2cbec45b1660c6f6afcb691bf0a7a73ebda096ee1dfb670fe6f
  event TokenRedeemed(address from, uint256 amount);
  //3ceffd410054fdaed44f598ff5c1fb450658778e2241892da4aa646979dee617
  event TokenPurchased(address addr, uint256 amount, uint256 tokens);
  //5a56a31cc0c9ebf5d0626c5189b951fe367d953afc1824a8bb82bf168713cc52
  event FeeApplied(string name, address addr, uint256 amount);
  event Converted(address indexed sender, uint256 amountSLVT, uint256 amountSLVD, uint256 amountFee);

  modifier purchasable() {
    require(canPurchase == true, "can't purchase");
    _;
  }

  modifier onlySilverDollar() {
    require(msg.sender == silverDollarAddress, "not silverDollar");
    _;
  }
  
  modifier isConvertible() {
    require(canConvert == true, "SLVT conversion disabled");
    _;
  }


  constructor () public {
    multiplier = 10 ** uint256(decimals);
    transferFreeAmount = 2 * multiplier;
    transferDiscountAmount = 500 * multiplier;
    owner = msg.sender;
    super.mint(msg.sender, 1 * 1000 * 1000 * multiplier);
  }

  // Settings begin

  function setTransferFreeAmount(uint256 value) public onlyOwner      { transferFreeAmount = value; }
  function setTransferDiscountAmount(uint256 value) public onlyOwner  { transferDiscountAmount = value; }
  function setRedemptionFee(uint256 value) public onlyOwner           { redemptionFee = value; }
  function setFeeReturnAddress(address value) public onlyOwner        { feeReturnAddress = value; }
  function setCanPurchase(bool value) public onlyOwner                { canPurchase = value; }
  function setSilverDollarAddress(address value) public onlyOwner     { silverDollarAddress = value; }
  function setCanConvert(bool value) public onlyOwner                 { canConvert = value; }
  function setConvertFee(uint256 value) public onlyOwner              { convertFee = value; }


  function increaseTotalSupply(uint256 value) public onlyOwner returns (uint256) {
    super.mint(owner, value);
    return totalSupply_;
  }

  // Settings end

  // ERC20 re-implementation methods begin

  function transfer(address to, uint256 amount) public whenNotPaused returns (bool) {
    uint256 feesPaid = payFees(address(0), to, amount);
    require(super.transfer(to, amount.sub(feesPaid)), "failed transfer");

    return true;
  }

  function transferFrom(address from, address to, uint256 amount) public whenNotPaused returns (bool) {
    uint256 feesPaid = payFees(from, to, amount);
    require(super.transferFrom(from, to, amount.sub(feesPaid)), "failed transferFrom");

    return true;
  }

  // ERC20 re-implementation methods end

  // Silvertoken methods end

  function payFees(address from, address to, uint256 amount) private returns (uint256 fees) {
    if (msg.sender == owner || hasRole(from, ROLE_WHITELISTED) || hasRole(msg.sender, ROLE_WHITELISTED) || hasRole(to, ROLE_WHITELISTED))
        return 0;
    fees = getTransferFee(amount);
    if (from == address(0)) {
      require(super.transfer(feeReturnAddress, fees), "transfer fee payment failed");
    }
    else {
      require(super.transferFrom(from, feeReturnAddress, fees), "transferFrom fee payment failed");
    }
    emit FeeApplied("Transfer", to, fees);
  }

  function getTransferFee(uint256 amount) internal view returns(uint256) {
    if (transferFreeAmount > 0 && amount <= transferFreeAmount) return 0;
    if (transferDiscountAmount > 0 && amount >= transferDiscountAmount) return amount.mul(transferDiscountFee).div(percentage);
    return amount.mul(transferFee).div(percentage);
  }

  function transferTokens(address from, address to, uint256 amount) internal returns (bool) {
    require(balances[from] >= amount, "balance insufficient");

    balances[from] = balances[from].sub(amount);
    balances[to] = balances[to].add(amount);

    emit Transfer(from, to, amount);

    return true;
  }

  function purchase(uint256 tokens, uint256 fee, uint256 timestamp, bytes signature) public payable purchasable whenNotPaused {
    require(
      isSignatureValid(
        msg.sender, msg.value, tokens, fee, timestamp, signature
      ),
      "invalid signature"
    );
    require(tokens > 0, "invalid number of tokens");
    
    emit TokenPurchased(msg.sender, msg.value, tokens);
    transferTokens(owner, msg.sender, tokens);

    feeReturnAddress.transfer(msg.value);
    if (fee > 0) {
      emit FeeApplied("Purchase", msg.sender, fee);
    }       
  }

  function purchasedSilverDollar(uint256 amount) public onlySilverDollar purchasable whenNotPaused returns (bool) {
    require(super._mint(SLVTReserve, amount), "minting of slvT failed");
    
    return true;
  }

  function purchaseWithSilverDollar(address to, uint256 amount) public onlySilverDollar purchasable whenNotPaused returns (bool) {
    require(transferTokens(SLVTReserve, to, amount), "failed transfer of slvT from reserve");

    return true;
  }

  function redeem(uint256 tokens) public whenNotPaused {
    require(tokens > 0, "amount of tokens redeemed must be > 0");

    uint256 fee = tokens.mul(redemptionFee).div(percentage);

    _burn(msg.sender, tokens.sub(fee));
    if (fee > 0) {
      require(super.transfer(feeReturnAddress, fee), "token transfer failed");
      emit FeeApplied("Redeem", msg.sender, fee);
    }
    emit TokenRedeemed(msg.sender, tokens);
  }

  function isSignatureValid(
    address sender, uint256 amount, uint256 tokens, 
    uint256 fee, uint256 timestamp, bytes signature
  ) public view returns (bool) 
  {
    if (block.timestamp > timestamp + 10 minutes) return false;
    bytes32 hash = keccak256(
      abi.encodePacked(
        address(this),
        sender, 
        amount, 
        tokens,
        fee,
        timestamp
      )
    );
    return hash.toEthSignedMessageHash().recover(signature) == owner;
  }

  function isConvertSignatureValid(
    address sender, uint256 amountSLVT, uint256 amountSLVD, 
    uint256 timestamp, bytes signature
  ) public view returns (bool) 
  {
    if (block.timestamp > timestamp + 10 minutes) return false;
    bytes32 hash = keccak256(
      abi.encodePacked(
        address(this),
        sender, 
        amountSLVT, 
        amountSLVD,
        timestamp
      )
    );
    return hash.toEthSignedMessageHash().recover(signature) == owner;
  }

  function convertToSLVD(
    uint256 amountSLVT, uint256 amountSLVD,
    uint256 timestamp, bytes signature
  ) public isConvertible whenNotPaused returns (bool) {
    require(
      isConvertSignatureValid(
        msg.sender, amountSLVT, 
        amountSLVD, timestamp, signature
      ), 
      "convert failed, invalid signature"
    );

    uint256 fees = amountSLVT.mul(convertFee).div(percentage);
    if (whitelist(msg.sender) && Whitelist(silverDollarAddress).whitelist(msg.sender))
      fees = 0;

    super.transfer(SLVTReserve, amountSLVT.sub(fees));
    require(super.transfer(feeReturnAddress, fees), "transfer fee payment failed");
    require(
      ASilverDollar(silverDollarAddress).purchaseWithSilverToken(msg.sender, amountSLVD), 
      "failed purchase of silverdollar with silvertoken"
    );
    
    emit Converted(msg.sender, amountSLVD, amountSLVD, fees);
    return true;
  }
}
function payFees(address from, address to, uint256 amount) private returns (uint256 fees) {
  if (msg.sender == owner || hasRole(from, ROLE_WHITELISTED) || hasRole(msg.sender, ROLE_WHITELISTED) || hasRole(to, ROLE_WHITELISTED))
      return 0;
  fees = getTransferFee(amount);
  if (from == address(0)) {
    require(super.transfer(feeReturnAddress, fees), "transfer fee payment failed");
  }
  else {
    require(super.transferFrom(from, feeReturnAddress, fees), "transferFrom fee payment failed");
  }
  emit FeeApplied("Transfer", to, fees);
}

// ERC20 re-implementation methods end
// Silvertoken methods end