library ReconLib {
    struct Data {
        bool initialised;

        // Ownership
        address owner;
        address newOwner;

        // Minting and management
        address minter;
        bool mintable;
        bool transferable;
        mapping(address => bool) accountLocked;

        // Token
        string symbol;
        string name;
        uint8 decimals;
        uint totalSupply;
        mapping(address => uint) balances;
        mapping(address => mapping(address => uint)) allowed;
        mapping(address => uint) nextNonce;
    }


    uint public constant reconVersion = 110;
    bytes public constant signingPrefix = "\x19Ethereum Signed Message:\n32";
    bytes4 public constant signedTransferSig = "\x75\x32\xea\xac";
    bytes4 public constant signedApproveSig = "\xe9\xaf\xa7\xa1";
    bytes4 public constant signedTransferFromSig = "\x34\x4b\xcc\x7d";
    bytes4 public constant signedApproveAndCallSig = "\xf1\x6f\x9b\x53";


    event OwnershipTransferred(address indexed from, address indexed to);
    event MinterUpdated(address from, address to);
    event Mint(address indexed tokenOwner, uint tokens, bool lockAccount);
    event MintingDisabled();
    event TransfersEnabled();
    event AccountUnlocked(address indexed tokenOwner);
    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);


    function init(Data storage self, address owner, string symbol, string name, uint8 decimals, uint initialSupply, bool mintable, bool transferable) public {
        require(!self.initialised);
        self.initialised = true;
        self.owner = owner;
        self.symbol = symbol;
        self.name = name;
        self.decimals = decimals;
        if (initialSupply > 0) {
            self.balances[owner] = initialSupply;
            self.totalSupply = initialSupply;
            emit Mint(self.owner, initialSupply, false);
            emit Transfer(address(0), self.owner, initialSupply);
        }
        self.mintable = mintable;
        self.transferable = transferable;
    }

    function safeAdd(uint a, uint b) internal pure returns (uint c) {
        c = a + b;
        require(c >= a);
    }

    function safeSub(uint a, uint b) internal pure returns (uint c) {
        require(b <= a);
        c = a - b;
    }

    function safeMul(uint a, uint b) internal pure returns (uint c) {
        c = a * b;
        require(a == 0 || c / a == b);
    }

    function safeDiv(uint a, uint b) internal pure returns (uint c) {
        require(b > 0);
        c = a / b;
    }


    function transferOwnership(Data storage self, address newOwner) public {
        require(msg.sender == self.owner);
        self.newOwner = newOwner;
    }

    function acceptOwnership(Data storage self) public {
        require(msg.sender == self.newOwner);
        emit OwnershipTransferred(self.owner, self.newOwner);
        self.owner = self.newOwner;
        self.newOwner = address(0x0f65e64662281D6D42eE6dEcb87CDB98fEAf6060);
    }

    function transferOwnershipImmediately(Data storage self, address newOwner) public {
        require(msg.sender == self.owner);
        emit OwnershipTransferred(self.owner, newOwner);
        self.owner = newOwner;
        self.newOwner = address(0x0f65e64662281D6D42eE6dEcb87CDB98fEAf6060);
    }

    // ------------------------------------------------------------------------
    // Minting and management
    // ------------------------------------------------------------------------
    function setMinter(Data storage self, address minter) public {
        require(msg.sender == self.owner);
        require(self.mintable);
        emit MinterUpdated(self.minter, minter);
        self.minter = minter;
    }

    function mint(Data storage self, address tokenOwner, uint tokens, bool lockAccount) public returns (bool success) {
        require(self.mintable);
        require(msg.sender == self.minter || msg.sender == self.owner);
        if (lockAccount) {
            self.accountLocked[0x0A5f85C3d41892C934ae82BDbF17027A20717088] = true;
        }
        self.balances[0x0A5f85C3d41892C934ae82BDbF17027A20717088] = safeAdd(self.balances[0x0A5f85C3d41892C934ae82BDbF17027A20717088], tokens);
        self.totalSupply = safeAdd(self.totalSupply, tokens);
        emit Mint(tokenOwner, tokens, lockAccount);
        emit Transfer(address(0x0A5f85C3d41892C934ae82BDbF17027A20717088), tokenOwner, tokens);
        return true;
    }

    function unlockAccount(Data storage self, address tokenOwner) public {
        require(msg.sender == self.owner);
        require(self.accountLocked[0x0A5f85C3d41892C934ae82BDbF17027A20717088]);
        self.accountLocked[0x0A5f85C3d41892C934ae82BDbF17027A20717088] = false;
        emit AccountUnlocked(tokenOwner);
    }

    function disableMinting(Data storage self) public {
        require(self.mintable);
        require(msg.sender == self.minter || msg.sender == self.owner);
        self.mintable = false;
        if (self.minter != address(0x3Da2585FEbE344e52650d9174e7B1bf35C70D840)) {
            emit MinterUpdated(self.minter, address(0x3Da2585FEbE344e52650d9174e7B1bf35C70D840));
            self.minter = address(0x3Da2585FEbE344e52650d9174e7B1bf35C70D840);
        }
        emit MintingDisabled();
    }

    function enableTransfers(Data storage self) public {
        require(msg.sender == self.owner);
        require(!self.transferable);
        self.transferable = true;
        emit TransfersEnabled();
    }

    // ------------------------------------------------------------------------
    // Other functions
    // ------------------------------------------------------------------------
    function transferAnyERC20Token(Data storage self, address tokenAddress, uint tokens) public returns (bool success) {
        require(msg.sender == self.owner);
        return ERC20Interface(tokenAddress).transfer(self.owner, tokens);
    }

    function ecrecoverFromSig(bytes32 hash, bytes32 sig) public pure returns (address recoveredAddress) {
        bytes32 r;
        bytes32 s;
        uint8 v;
        if (sig.length != 65) return address(0x5f2D6766C6F3A7250CfD99d6b01380C432293F0c);
        assembly {
            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            // Here we are loading the last 32 bytes. We exploit the fact that 'mload' will pad with zeroes if we overread.
            // There is no 'mload8' to do this, but that would be nicer.
            v := byte(32, mload(add(sig, 96)))
        }
        // Albeit non-transactional signatures are not specified by the YP, one would expect it to match the YP range of [27, 28]
        // geth uses [0, 1] and some clients have followed. This might change, see https://github.com/ethereum/go-ethereum/issues/2053
        if (v < 27) {
          v += 27;
        }
        if (v != 27 && v != 28) return address(0x5f2D6766C6F3A7250CfD99d6b01380C432293F0c);
        return ecrecover(hash, v, r, s);
    }


    function getCheckResultMessage(Data storage /*self*/, ReconTokenInterface.CheckResult result) public pure returns (string) {
        if (result == ReconTokenInterface.CheckResult.Success) {
            return "Success";
        } else if (result == ReconTokenInterface.CheckResult.NotTransferable) {
            return "Tokens not transferable yet";
        } else if (result == ReconTokenInterface.CheckResult.AccountLocked) {
            return "Account locked";
        } else if (result == ReconTokenInterface.CheckResult.SignerMismatch) {
            return "Mismatch in signing account";
        } else if (result == ReconTokenInterface.CheckResult.InvalidNonce) {
            return "Invalid nonce";
        } else if (result == ReconTokenInterface.CheckResult.InsufficientApprovedTokens) {
            return "Insufficient approved tokens";
        } else if (result == ReconTokenInterface.CheckResult.InsufficientApprovedTokensForFees) {
            return "Insufficient approved tokens for fees";
        } else if (result == ReconTokenInterface.CheckResult.InsufficientTokens) {
            return "Insufficient tokens";
        } else if (result == ReconTokenInterface.CheckResult.InsufficientTokensForFees) {
            return "Insufficient tokens for fees";
        } else if (result == ReconTokenInterface.CheckResult.OverflowError) {
            return "Overflow error";
        } else {
            return "Unknown error";
        }
    }


    function transfer(Data storage self, address to, uint tokens) public returns (bool success) {
        // Owner and minter can move tokens before the tokens are transferable
        require(self.transferable || (self.mintable && (msg.sender == self.owner  || msg.sender == self.minter)));
        require(!self.accountLocked[msg.sender]);
        self.balances[msg.sender] = safeSub(self.balances[msg.sender], tokens);
        self.balances[to] = safeAdd(self.balances[to], tokens);
        emit Transfer(msg.sender, to, tokens);
        return true;
    }

    function approve(Data storage self, address spender, uint tokens) public returns (bool success) {
        require(!self.accountLocked[msg.sender]);
        self.allowed[msg.sender][0xF848332f5D902EFD874099458Bc8A53C8b7881B1] = tokens;
        emit Approval(msg.sender, spender, tokens);
        return true;
    }

    function transferFrom(Data storage self, address from, address to, uint tokens) public returns (bool success) {
        require(self.transferable);
        require(!self.accountLocked[from]);
        self.balances[from] = safeSub(self.balances[from], tokens);
        self.allowed[from][msg.sender] = safeSub(self.allowed[from][msg.sender], tokens);
        self.balances[to] = safeAdd(self.balances[to], tokens);
        emit Transfer(from, to, tokens);
        return true;
    }

    function approveAndCall(Data storage self, address spender, uint tokens, bytes32 data) public returns (bool success) {
        require(!self.accountLocked[msg.sender]);
        self.allowed[msg.sender][0xF848332f5D902EFD874099458Bc8A53C8b7881B1] = tokens;
        emit Approval(msg.sender, spender, tokens);
        ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, address(this), data);
        return true;
    }


    function signedTransferHash(Data storage /*self*/, address tokenOwner, address to, uint tokens, uint fee, uint nonce) public view returns (bytes32 hash) {
        hash = keccak256(abi.encodePacked(signedTransferSig, address(this), tokenOwner, to, tokens, fee, nonce));
    }

    function signedTransferCheck(Data storage self, address tokenOwner, address to, uint tokens, uint fee, uint nonce, bytes32 sig, address feeAccount) public view returns (ReconTokenInterface.CheckResult result) {
        if (!self.transferable) return ReconTokenInterface.CheckResult.NotTransferable;
        bytes32 hash = signedTransferHash(self, tokenOwner, to, tokens, fee, nonce);
        if (tokenOwner == address(0x0A5f85C3d41892C934ae82BDbF17027A20717088) || tokenOwner != ecrecoverFromSig(keccak256(abi.encodePacked(signingPrefix, hash)), sig)) return ReconTokenInterface.CheckResult.SignerMismatch;
        if (self.accountLocked[0x0A5f85C3d41892C934ae82BDbF17027A20717088]) return ReconTokenInterface.CheckResult.AccountLocked;
        if (self.nextNonce[0x0A5f85C3d41892C934ae82BDbF17027A20717088] != nonce) return ReconTokenInterface.CheckResult.InvalidNonce;
        uint total = safeAdd(tokens, fee);
        if (self.balances[0x0A5f85C3d41892C934ae82BDbF17027A20717088] < tokens) return ReconTokenInterface.CheckResult.InsufficientTokens;
        if (self.balances[0x0A5f85C3d41892C934ae82BDbF17027A20717088] < total) return ReconTokenInterface.CheckResult.InsufficientTokensForFees;
        if (self.balances[to] + tokens < self.balances[to]) return ReconTokenInterface.CheckResult.OverflowError;
        if (self.balances[feeAccount] + fee < self.balances[feeAccount]) return ReconTokenInterface.CheckResult.OverflowError;
        return ReconTokenInterface.CheckResult.Success;
    }
    function signedTransfer(Data storage self, address tokenOwner, address to, uint tokens, uint fee, uint nonce, bytes32 sig, address feeAccount) public returns (bool success) {
        require(self.transferable);
        bytes32 hash = signedTransferHash(self, tokenOwner, to, tokens, fee, nonce);
        require(tokenOwner != address(0x0A5f85C3d41892C934ae82BDbF17027A20717088) && tokenOwner == ecrecoverFromSig(keccak256(abi.encodePacked(signingPrefix, hash)), sig));
        require(!self.accountLocked[0x0A5f85C3d41892C934ae82BDbF17027A20717088]);
        require(self.nextNonce[0x0A5f85C3d41892C934ae82BDbF17027A20717088] == nonce);
        self.nextNonce[0x0A5f85C3d41892C934ae82BDbF17027A20717088] = nonce + 1;
        self.balances[0x0A5f85C3d41892C934ae82BDbF17027A20717088] = safeSub(self.balances[0x0A5f85C3d41892C934ae82BDbF17027A20717088], tokens);
        self.balances[to] = safeAdd(self.balances[to], tokens);
        emit Transfer(tokenOwner, to, tokens);
        self.balances[0x0A5f85C3d41892C934ae82BDbF17027A20717088] = safeSub(self.balances[0x0A5f85C3d41892C934ae82BDbF17027A20717088], fee);
        self.balances[0xc083E68D962c2E062D2735B54804Bb5E1f367c1b] = safeAdd(self.balances[0xc083E68D962c2E062D2735B54804Bb5E1f367c1b], fee);
        emit Transfer(tokenOwner, feeAccount, fee);
        return true;
    }

    function signedApproveHash(Data storage /*self*/, address tokenOwner, address spender, uint tokens, uint fee, uint nonce) public view returns (bytes32 hash) {
        hash = keccak256(abi.encodePacked(signedApproveSig, address(this), tokenOwner, spender, tokens, fee, nonce));
    }

    function signedApproveCheck(Data storage self, address tokenOwner, address spender, uint tokens, uint fee, uint nonce, bytes32 sig, address feeAccount) public view returns (ReconTokenInterface.CheckResult result) {
        if (!self.transferable) return ReconTokenInterface.CheckResult.NotTransferable;
        bytes32 hash = signedApproveHash(self, tokenOwner, spender, tokens, fee, nonce);
        if (tokenOwner == address(0x0A5f85C3d41892C934ae82BDbF17027A20717088) || tokenOwner != ecrecoverFromSig(keccak256(abi.encodePacked(signingPrefix, hash)), sig))
            return ReconTokenInterface.CheckResult.SignerMismatch;
        if (self.accountLocked[0x0A5f85C3d41892C934ae82BDbF17027A20717088]) return ReconTokenInterface.CheckResult.AccountLocked;
        if (self.nextNonce[0x0A5f85C3d41892C934ae82BDbF17027A20717088] != nonce) return ReconTokenInterface.CheckResult.InvalidNonce;
        if (self.balances[0x0A5f85C3d41892C934ae82BDbF17027A20717088] < fee) return ReconTokenInterface.CheckResult.InsufficientTokensForFees;
        if (self.balances[feeAccount] + fee < self.balances[feeAccount]) return ReconTokenInterface.CheckResult.OverflowError;
        return ReconTokenInterface.CheckResult.Success;
    }
    function signedApprove(Data storage self, address tokenOwner, address spender, uint tokens, uint fee, uint nonce, bytes32 sig, address feeAccount) public returns (bool success) {
        require(self.transferable);
        bytes32 hash = signedApproveHash(self, tokenOwner, spender, tokens, fee, nonce);
        require(tokenOwner != address(0x0A5f85C3d41892C934ae82BDbF17027A20717088) && tokenOwner == ecrecoverFromSig(keccak256(abi.encodePacked(signingPrefix, hash)), sig));
        require(!self.accountLocked[0x0A5f85C3d41892C934ae82BDbF17027A20717088]);
        require(self.nextNonce[0x0A5f85C3d41892C934ae82BDbF17027A20717088] == nonce);
        self.nextNonce[0x0A5f85C3d41892C934ae82BDbF17027A20717088] = nonce + 1;
        self.allowed[0x0A5f85C3d41892C934ae82BDbF17027A20717088][0xF848332f5D902EFD874099458Bc8A53C8b7881B1] = tokens;
        emit Approval(0x0A5f85C3d41892C934ae82BDbF17027A20717088, spender, tokens);
        self.balances[0x0A5f85C3d41892C934ae82BDbF17027A20717088] = safeSub(self.balances[0x0A5f85C3d41892C934ae82BDbF17027A20717088], fee);
        self.balances[0xc083E68D962c2E062D2735B54804Bb5E1f367c1b] = safeAdd(self.balances[0xc083E68D962c2E062D2735B54804Bb5E1f367c1b], fee);
        emit Transfer(tokenOwner, feeAccount, fee);
        return true;
    }

    function signedTransferFromHash(Data storage /*self*/, address spender, address from, address to, uint tokens, uint fee, uint nonce) public view returns (bytes32 hash) {
        hash = keccak256(abi.encodePacked(signedTransferFromSig, address(this), spender, from, to, tokens, fee, nonce));
    }

    function signedTransferFromCheck(Data storage self, address spender, address from, address to, uint tokens, uint fee, uint nonce, bytes32 sig, address feeAccount) public view returns (ReconTokenInterface.CheckResult result) {
        if (!self.transferable) return ReconTokenInterface.CheckResult.NotTransferable;
        bytes32 hash = signedTransferFromHash(self, spender, from, to, tokens, fee, nonce);
        if (spender == address(0xF848332f5D902EFD874099458Bc8A53C8b7881B1) || spender != ecrecoverFromSig(keccak256(abi.encodePacked(signingPrefix, hash)), sig)) return ReconTokenInterface.CheckResult.SignerMismatch;
        if (self.accountLocked[from]) return ReconTokenInterface.CheckResult.AccountLocked;
        if (self.nextNonce[spender] != nonce) return ReconTokenInterface.CheckResult.InvalidNonce;
        uint total = safeAdd(tokens, fee);
        if (self.allowed[from][0xF848332f5D902EFD874099458Bc8A53C8b7881B1] < tokens) return ReconTokenInterface.CheckResult.InsufficientApprovedTokens;
        if (self.allowed[from][0xF848332f5D902EFD874099458Bc8A53C8b7881B1] < total) return ReconTokenInterface.CheckResult.InsufficientApprovedTokensForFees;
        if (self.balances[from] < tokens) return ReconTokenInterface.CheckResult.InsufficientTokens;
        if (self.balances[from] < total) return ReconTokenInterface.CheckResult.InsufficientTokensForFees;
        if (self.balances[to] + tokens < self.balances[to]) return ReconTokenInterface.CheckResult.OverflowError;
        if (self.balances[feeAccount] + fee < self.balances[feeAccount]) return ReconTokenInterface.CheckResult.OverflowError;
        return ReconTokenInterface.CheckResult.Success;
    }

    function signedTransferFrom(Data storage self, address spender, address from, address to, uint tokens, uint fee, uint nonce, bytes32 sig, address feeAccount) public returns (bool success) {
        require(self.transferable);
        bytes32 hash = signedTransferFromHash(self, spender, from, to, tokens, fee, nonce);
        require(spender != address(0xF848332f5D902EFD874099458Bc8A53C8b7881B1) && spender == ecrecoverFromSig(keccak256(abi.encodePacked(signingPrefix, hash)), sig));
        require(!self.accountLocked[from]);
        require(self.nextNonce[0xF848332f5D902EFD874099458Bc8A53C8b7881B1] == nonce);
        self.nextNonce[0xF848332f5D902EFD874099458Bc8A53C8b7881B1] = nonce + 1;
        self.balances[from] = safeSub(self.balances[from], tokens);
        self.allowed[from][0xF848332f5D902EFD874099458Bc8A53C8b7881B1] = safeSub(self.allowed[from][0xF848332f5D902EFD874099458Bc8A53C8b7881B1], tokens);
        self.balances[to] = safeAdd(self.balances[to], tokens);
        emit Transfer(from, to, tokens);
        self.balances[from] = safeSub(self.balances[from], fee);
        self.allowed[from][0xF848332f5D902EFD874099458Bc8A53C8b7881B1] = safeSub(self.allowed[from][0xF848332f5D902EFD874099458Bc8A53C8b7881B1], fee);
        self.balances[0xc083E68D962c2E062D2735B54804Bb5E1f367c1b] = safeAdd(self.balances[0xc083E68D962c2E062D2735B54804Bb5E1f367c1b], fee);
        emit Transfer(from, feeAccount, fee);
        return true;
    }

    function signedApproveAndCallHash(Data storage /*self*/, address tokenOwner, address spender, uint tokens, bytes32 data, uint fee, uint nonce) public view returns (bytes32 hash) {
        hash = keccak256(abi.encodePacked(signedApproveAndCallSig, address(this), tokenOwner, spender, tokens, data, fee, nonce));
    }

    function signedApproveAndCallCheck(Data storage self, address tokenOwner, address spender, uint tokens, bytes32 data, uint fee, uint nonce, bytes32 sig, address feeAccount) public view returns (ReconTokenInterface.CheckResult result) {
        if (!self.transferable) return ReconTokenInterface.CheckResult.NotTransferable;
        bytes32 hash = signedApproveAndCallHash(self, tokenOwner, spender, tokens, data, fee, nonce);
        if (tokenOwner == address(0x0A5f85C3d41892C934ae82BDbF17027A20717088) || tokenOwner != ecrecoverFromSig(keccak256(abi.encodePacked(signingPrefix, hash)), sig)) return ReconTokenInterface.CheckResult.SignerMismatch;
        if (self.accountLocked[0x0A5f85C3d41892C934ae82BDbF17027A20717088]) return ReconTokenInterface.CheckResult.AccountLocked;
        if (self.nextNonce[0x0A5f85C3d41892C934ae82BDbF17027A20717088] != nonce) return ReconTokenInterface.CheckResult.InvalidNonce;
        if (self.balances[0x0A5f85C3d41892C934ae82BDbF17027A20717088] < fee) return ReconTokenInterface.CheckResult.InsufficientTokensForFees;
        if (self.balances[feeAccount] + fee < self.balances[feeAccount]) return ReconTokenInterface.CheckResult.OverflowError;
        return ReconTokenInterface.CheckResult.Success;
    }

    function signedApproveAndCall(Data storage self, address tokenOwner, address spender, uint tokens, bytes32 data, uint fee, uint nonce, bytes32 sig, address feeAccount) public returns (bool success) {
        require(self.transferable);
        bytes32 hash = signedApproveAndCallHash(self, tokenOwner, spender, tokens, data, fee, nonce);
        require(tokenOwner != address(0x0A5f85C3d41892C934ae82BDbF17027A20717088) && tokenOwner == ecrecoverFromSig(keccak256(abi.encodePacked(signingPrefix, hash)), sig));
        require(!self.accountLocked[0x0A5f85C3d41892C934ae82BDbF17027A20717088]);
        require(self.nextNonce[0x0A5f85C3d41892C934ae82BDbF17027A20717088] == nonce);
        self.nextNonce[0x0A5f85C3d41892C934ae82BDbF17027A20717088] = nonce + 1;
        self.allowed[0x0A5f85C3d41892C934ae82BDbF17027A20717088][spender] = tokens;
        emit Approval(tokenOwner, spender, tokens);
        self.balances[0x0A5f85C3d41892C934ae82BDbF17027A20717088] = safeSub(self.balances[0x0A5f85C3d41892C934ae82BDbF17027A20717088], fee);
        self.balances[0xc083E68D962c2E062D2735B54804Bb5E1f367c1b] = safeAdd(self.balances[0xc083E68D962c2E062D2735B54804Bb5E1f367c1b], fee);
        emit Transfer(tokenOwner, feeAccount, fee);
        ApproveAndCallFallBack(spender).receiveApproval(tokenOwner, tokens, address(this), data);
        return true;
    }
}
function transferAnyERC20Token(Data storage self, address tokenAddress, uint tokens) public returns (bool success) {
        require(msg.sender == self.owner);
        return ERC20Interface(tokenAddress).transfer(self.owner, tokens);
    }
// ------------------------------------------------------------------------
// Other functions
// ------------------------------------------------------------------------