// MoneyStampsERC721.sol -- Interest-bearing NFTs with lexStamps based on the DAI Savings Token
// - Adapted from ChargedParticlesERC721.sol - https://github.com/robsecord/ChargedParticlesEth
//
// MIT License
// Copyright (c) 2020 Open, ESQ LLC
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

pragma solidity ^0.5.13;

// ERROR CODES:
//  100:        ADDRESS, OWNER, OPERATOR
//      101         Invalid Address
//      102         Sender is not owner
//      103         Sender is not operator
//  200:        MATH
//      201         Underflow
//      202         Overflow
//      203         Multiplication overflow
//      204         Division by zero
//  300:        ERC721
//      301         Invalid Recipient
//      302         Invalid on-received message
//      303         Invalid tokenId
//      304         Invalid owner/operator
//      305         Token ID already exists
//  400:        ChargedParticles
//      401         Invalid Method
//      402         Unregistered Type
//      403         Particle has no Charge
//      404         Insufficient DAI Balance
//      405         Transfer Failed

/**
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
 * the optional functions; to access them see {ERC20Detailed}.
 */
interface IERC20 {
    /**
     * @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 `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, 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 `sender` to `recipient` 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 sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @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);
}

/**
 * @title Chai.money interface
 * @dev see https://github.com/dapphub/chai
 */
contract IChai {
    function transfer(address dst, uint wad) external returns (bool);
    // like transferFrom but dai-denominated
    function move(address src, address dst, uint wad) external returns (bool);
    function transferFrom(address src, address dst, uint wad) public returns (bool);
    function approve(address usr, uint wad) external returns (bool);
    function balanceOf(address usr) external returns (uint);

    // Approve by signature
    function permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s) external;

    function dai(address usr) external returns (uint wad);
    function dai(uint chai) external returns (uint wad);

    // wad is denominated in dai
    function join(address dst, uint wad) external;

    // wad is denominated in (1/chi) * dai
    function exit(address src, uint wad) public;

    // wad is denominated in dai
    function draw(address src, uint wad) external returns (uint chai);
}

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
	/**
 	* @dev Returns the addition of two unsigned integers, reverting on
 	* overflow.
 	*
 	* Counterpart to Solidity's `+` operator.
 	*
 	* Requirements:
 	* - Addition cannot overflow.
 	*/
	function add(uint256 a, uint256 b) internal pure returns (uint256) {
    	uint256 c = a + b;
    	require(c >= a, "SafeMath: addition overflow");

    	return c;
	}

	/**
 	* @dev Returns the subtraction of two unsigned integers, reverting on
 	* overflow (when the result is negative).
 	*
 	* Counterpart to Solidity's `-` operator.
 	*
 	* Requirements:
 	* - Subtraction cannot overflow.
 	*/
	function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    	return sub(a, b, "SafeMath: subtraction overflow");
	}

	/**
 	* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
 	* overflow (when the result is negative).
 	*
 	* Counterpart to Solidity's `-` operator.
 	*
 	* Requirements:
 	* - Subtraction cannot overflow.
 	*
 	* _Available since v2.4.0._
 	*/
	function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
    	require(b <= a, errorMessage);
    	uint256 c = a - b;

    	return c;
	}

	/**
 	* @dev Returns the multiplication of two unsigned integers, reverting on
 	* overflow.
 	*
 	* Counterpart to Solidity's `*` operator.
 	*
 	* Requirements:
 	* - Multiplication cannot overflow.
 	*/
	function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    	// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
    	// benefit is lost if 'b' is also tested.
    	// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
    	if (a == 0) {
        	return 0;
    	}

    	uint256 c = a * b;
    	require(c / a == b, "SafeMath: multiplication overflow");

    	return c;
	}

	/**
 	* @dev Returns the integer division of two unsigned integers. Reverts on
 	* division by zero. The result is rounded towards zero.
 	*
 	* Counterpart to Solidity's `/` operator. Note: this function uses a
 	* `revert` opcode (which leaves remaining gas untouched) while Solidity
 	* uses an invalid opcode to revert (consuming all remaining gas).
 	*
 	* Requirements:
 	* - The divisor cannot be zero.
 	*/
	function div(uint256 a, uint256 b) internal pure returns (uint256) {
    	return div(a, b, "SafeMath: division by zero");
	}

	/**
 	* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
 	* division by zero. The result is rounded towards zero.
 	*
 	* Counterpart to Solidity's `/` operator. Note: this function uses a
 	* `revert` opcode (which leaves remaining gas untouched) while Solidity
 	* uses an invalid opcode to revert (consuming all remaining gas).
 	*
 	* Requirements:
 	* - The divisor cannot be zero.
 	*
 	* _Available since v2.4.0._
 	*/
	function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
    	// Solidity only automatically asserts when dividing by 0
    	require(b > 0, errorMessage);
    	uint256 c = a / b;
    	// assert(a == b * c + a % b); // There is no case in which this doesn't hold

    	return c;
	}

	/**
 	* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
 	* Reverts when dividing by zero.
 	*
 	* Counterpart to Solidity's `%` operator. This function uses a `revert`
 	* opcode (which leaves remaining gas untouched) while Solidity uses an
 	* invalid opcode to revert (consuming all remaining gas).
 	*
 	* Requirements:
 	* - The divisor cannot be zero.
 	*/
	function mod(uint256 a, uint256 b) internal pure returns (uint256) {
    	return mod(a, b, "SafeMath: modulo by zero");
	}

	/**
 	* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
 	* Reverts with custom message when dividing by zero.
 	*
 	* Counterpart to Solidity's `%` operator. This function uses a `revert`
 	* opcode (which leaves remaining gas untouched) while Solidity uses an
 	* invalid opcode to revert (consuming all remaining gas).
 	*
 	* Requirements:
 	* - The divisor cannot be zero.
 	*
 	* _Available since v2.4.0._
 	*/
	function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
    	require(b != 0, errorMessage);
    	return a % b;
	}
}

/**
 * @dev Collection of functions related to the address type
 */
library Address {
	/**
 	* @dev Returns true if `account` is a contract.
 	*
 	* [IMPORTANT]
 	* ====
 	* It is unsafe to assume that an address for which this function returns
 	* false is an externally-owned account (EOA) and not a contract.
 	*
 	* Among others, `isContract` will return false for the following
 	* types of addresses:
 	*
 	*  - an externally-owned account
 	*  - a contract in construction
 	*  - an address where a contract will be created
 	*  - an address where a contract lived, but was destroyed
 	* ====
 	*/
	function isContract(address account) internal view returns (bool) {
    	// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
    	// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
    	// for accounts without code, i.e. `keccak256('')`
    	bytes32 codehash;
    	bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
    	// solhint-disable-next-line no-inline-assembly
    	assembly { codehash := extcodehash(account) }
    	return (codehash != accountHash && codehash != 0x0);
	}

	/**
 	* @dev Converts an `address` into `address payable`. Note that this is
 	* simply a type cast: the actual underlying value is not changed.
 	*
 	* _Available since v2.4.0._
 	*/
	function toPayable(address account) internal pure returns (address payable) {
    	return address(uint160(account));
	}

	/**
 	* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
 	* `recipient`, forwarding all available gas and reverting on errors.
 	*
 	* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
 	* of certain opcodes, possibly making contracts go over the 2300 gas limit
 	* imposed by `transfer`, making them unable to receive funds via
 	* `transfer`. {sendValue} removes this limitation.
 	*
 	* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
 	*
 	* IMPORTANT: because control is transferred to `recipient`, care must be
 	* taken to not create reentrancy vulnerabilities. Consider using
 	* {ReentrancyGuard} or the
 	* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
 	*
 	* _Available since v2.4.0._
 	*/
	function sendValue(address payable recipient, uint256 amount) internal {
    	require(address(this).balance >= amount, "Address: insufficient balance");

    	// solhint-disable-next-line avoid-call-value
    	(bool success, ) = recipient.call.value(amount)("");
    	require(success, "Address: unable to send value, recipient may have reverted");
	}
}

/**
 * @title Counters
 * @author Matt Condon (@shrugs)
 * @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number
 * of elements in a mapping, issuing ERC721 ids, or counting request ids.
 *
 * Include with `using Counters for Counters.Counter;`
 * Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the {SafeMath}
 * overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never
 * directly accessed.
 */
library Counters {
	using SafeMath for uint256;

	struct Counter {
    	// This variable should never be directly accessed by users of the library: interactions must be restricted to
    	// the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
    	// this feature: see https://github.com/ethereum/solidity/issues/4637
    	uint256 _value; // default: 0
	}

	function current(Counter storage counter) internal view returns (uint256) {
    	return counter._value;
	}

	function increment(Counter storage counter) internal {
    	// The {SafeMath} overflow check can be skipped here, see the comment at the top
    	counter._value += 1;
	}

	function decrement(Counter storage counter) internal {
    	counter._value = counter._value.sub(1);
	}
}

/*
 * @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 GSN 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.
 */
contract Context {
	// Empty internal constructor, to prevent people from mistakenly deploying
	// an instance of this contract, which should be used via inheritance.
	constructor () internal { }
	// solhint-disable-previous-line no-empty-blocks

	function _msgSender() internal view returns (address payable) {
    	return msg.sender;
	}

	function _msgData() internal view returns (bytes memory) {
    	this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
    	return msg.data;
	}
}

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
	/**
 	* @dev Returns true if this contract implements the interface defined by
 	* `interfaceId`. See the corresponding
 	* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
 	* to learn more about how these ids are created.
 	*
 	* This function call must use less than 30 000 gas.
 	*/
	function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts may inherit from this and call {_registerInterface} to declare
 * their support of an interface.
 */
contract ERC165 is IERC165 {
	/*
 	* bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7
 	*/
	bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;

	/**
 	* @dev Mapping of interface ids to whether or not it's supported.
 	*/
	mapping(bytes4 => bool) private _supportedInterfaces;

	constructor () internal {
    	// Derived contracts need only register support for their own interfaces,
    	// we register support for ERC165 itself here
    	_registerInterface(_INTERFACE_ID_ERC165);
	}

	/**
 	* @dev See {IERC165-supportsInterface}.
 	*
 	* Time complexity O(1), guaranteed to always use less than 30 000 gas.
 	*/
	function supportsInterface(bytes4 interfaceId) external view returns (bool) {
    	return _supportedInterfaces[interfaceId];
	}

	/**
 	* @dev Registers the contract as an implementer of the interface defined by
 	* `interfaceId`. Support of the actual ERC165 interface is automatic and
 	* registering its interface id is not required.
 	*
 	* See {IERC165-supportsInterface}.
 	*
 	* Requirements:
 	*
 	* - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`).
 	*/
	function _registerInterface(bytes4 interfaceId) internal {
    	require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
    	_supportedInterfaces[interfaceId] = true;
	}
}

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
contract IERC721 is IERC165 {
	event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
	event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
	event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

	/**
 	* @dev Returns the number of NFTs in `owner`'s account.
 	*/
	function balanceOf(address owner) public view returns (uint256 balance);

	/**
 	* @dev Returns the owner of the NFT specified by `tokenId`.
 	*/
	function ownerOf(uint256 tokenId) public view returns (address owner);

	/**
 	* @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to
 	* another (`to`).
 	*
 	*
 	*
 	* Requirements:
 	* - `from`, `to` cannot be zero.
 	* - `tokenId` must be owned by `from`.
 	* - If the caller is not `from`, it must be have been allowed to move this
 	* NFT by either {approve} or {setApprovalForAll}.
 	*/
	function safeTransferFrom(address from, address to, uint256 tokenId) public;
	/**
 	* @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to
 	* another (`to`).
 	*
 	* Requirements:
 	* - If the caller is not `from`, it must be approved to move this NFT by
 	* either {approve} or {setApprovalForAll}.
 	*/
	function transferFrom(address from, address to, uint256 tokenId) public;
	function approve(address to, uint256 tokenId) public;
	function getApproved(uint256 tokenId) public view returns (address operator);

	function setApprovalForAll(address operator, bool _approved) public;
	function isApprovedForAll(address owner, address operator) public view returns (bool);


	function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public;
}

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
contract IERC721Receiver {
	/**
 	* @notice Handle the receipt of an NFT
 	* @dev The ERC721 smart contract calls this function on the recipient
 	* after a {IERC721-safeTransferFrom}. This function MUST return the function selector,
 	* otherwise the caller will revert the transaction. The selector to be
 	* returned can be obtained as `this.onERC721Received.selector`. This
 	* function MAY throw to revert and reject the transfer.
 	* Note: the ERC721 contract address is always the message sender.
 	* @param operator The address which called `safeTransferFrom` function
 	* @param from The address which previously owned the token
 	* @param tokenId The NFT identifier which is being transferred
 	* @param data Additional data with no specified format
 	* @return bytes4 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
 	*/
	function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data)
	public returns (bytes4);
}

/**
 * @title ERC721 Non-Fungible Token Standard basic implementation
 * @dev see https://eips.ethereum.org/EIPS/eip-721
 */
contract ERC721 is Context, ERC165, IERC721 {
	using SafeMath for uint256;
	using Address for address;
	using Counters for Counters.Counter;

	// Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
	// which can be also obtained as `IERC721Receiver(0).onERC721Received.selector`
	bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;

	// Mapping from token ID to owner
	mapping (uint256 => address) private _tokenOwner;

	// Mapping from token ID to approved address
	mapping (uint256 => address) private _tokenApprovals;

	// Mapping from owner to number of owned token
	mapping (address => Counters.Counter) private _ownedTokensCount;

	// Mapping from owner to operator approvals
	mapping (address => mapping (address => bool)) private _operatorApprovals;

	/*
 	* 	bytes4(keccak256('balanceOf(address)')) == 0x70a08231
 	* 	bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e
 	* 	bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3
 	* 	bytes4(keccak256('getApproved(uint256)')) == 0x081812fc
 	* 	bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465
 	* 	bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c5
 	* 	bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd
 	* 	bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e
 	* 	bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde
 	*
 	* 	=> 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^
 	*    	0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd
 	*/
	bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;

	constructor () public {
    	// register the supported interfaces to conform to ERC721 via ERC165
    	_registerInterface(_INTERFACE_ID_ERC721);
	}

	/**
 	* @dev Gets the balance of the specified address.
 	* @param owner address to query the balance of
 	* @return uint256 representing the amount owned by the passed address
 	*/
	function balanceOf(address owner) public view returns (uint256) {
    	require(owner != address(0), "ERC721: balance query for the zero address");

    	return _ownedTokensCount[owner].current();
	}

	/**
 	* @dev Gets the owner of the specified token ID.
 	* @param tokenId uint256 ID of the token to query the owner of
 	* @return address currently marked as the owner of the given token ID
 	*/
	function ownerOf(uint256 tokenId) public view returns (address) {
    	address owner = _tokenOwner[tokenId];
    	require(owner != address(0), "ERC721: owner query for nonexistent token");

    	return owner;
	}

	/**
 	* @dev Approves another address to transfer the given token ID
 	* The zero address indicates there is no approved address.
 	* There can only be one approved address per token at a given time.
 	* Can only be called by the token owner or an approved operator.
 	* @param to address to be approved for the given token ID
 	* @param tokenId uint256 ID of the token to be approved
 	*/
	function approve(address to, uint256 tokenId) public {
    	address owner = ownerOf(tokenId);
    	require(to != owner, "ERC721: approval to current owner");

    	require(_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
        	"ERC721: approve caller is not owner nor approved for all"
    	);

    	_tokenApprovals[tokenId] = to;
    	emit Approval(owner, to, tokenId);
	}

	/**
 	* @dev Gets the approved address for a token ID, or zero if no address set
 	* Reverts if the token ID does not exist.
 	* @param tokenId uint256 ID of the token to query the approval of
 	* @return address currently approved for the given token ID
 	*/
	function getApproved(uint256 tokenId) public view returns (address) {
    	require(_exists(tokenId), "ERC721: approved query for nonexistent token");

    	return _tokenApprovals[tokenId];
	}

	/**
 	* @dev Sets or unsets the approval of a given operator
 	* An operator is allowed to transfer all tokens of the sender on their behalf.
 	* @param to operator address to set the approval
 	* @param approved representing the status of the approval to be set
 	*/
	function setApprovalForAll(address to, bool approved) public {
    	require(to != _msgSender(), "ERC721: approve to caller");

    	_operatorApprovals[_msgSender()][to] = approved;
    	emit ApprovalForAll(_msgSender(), to, approved);
	}

	/**
 	* @dev Tells whether an operator is approved by a given owner.
 	* @param owner owner address which you want to query the approval of
 	* @param operator operator address which you want to query the approval of
 	* @return bool whether the given operator is approved by the given owner
 	*/
	function isApprovedForAll(address owner, address operator) public view returns (bool) {
    	return _operatorApprovals[owner][operator];
	}

	/**
 	* @dev Transfers the ownership of a given token ID to another address.
 	* Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
 	* Requires the msg.sender to be the owner, approved, or operator.
 	* @param from current owner of the token
 	* @param to address to receive the ownership of the given token ID
 	* @param tokenId uint256 ID of the token to be transferred
 	*/
	function transferFrom(address from, address to, uint256 tokenId) public {
    	//solhint-disable-next-line max-line-length
    	require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");

    	_transferFrom(from, to, tokenId);
	}

	/**
 	* @dev Safely transfers the ownership of a given token ID to another address
 	* If the target address is a contract, it must implement {IERC721Receiver-onERC721Received},
 	* which is called upon a safe transfer, and return the magic value
 	* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
 	* the transfer is reverted.
 	* Requires the msg.sender to be the owner, approved, or operator
 	* @param from current owner of the token
 	* @param to address to receive the ownership of the given token ID
 	* @param tokenId uint256 ID of the token to be transferred
 	*/
	function safeTransferFrom(address from, address to, uint256 tokenId) public {
    	safeTransferFrom(from, to, tokenId, "");
	}

	/**
 	* @dev Safely transfers the ownership of a given token ID to another address
 	* If the target address is a contract, it must implement {IERC721Receiver-onERC721Received},
 	* which is called upon a safe transfer, and return the magic value
 	* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
 	* the transfer is reverted.
 	* Requires the _msgSender() to be the owner, approved, or operator
 	* @param from current owner of the token
 	* @param to address to receive the ownership of the given token ID
 	* @param tokenId uint256 ID of the token to be transferred
 	* @param _data bytes data to send along with a safe transfer check
 	*/
	function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public {
    	require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
    	_safeTransferFrom(from, to, tokenId, _data);
	}

	/**
 	* @dev Safely transfers the ownership of a given token ID to another address
 	* If the target address is a contract, it must implement `onERC721Received`,
 	* which is called upon a safe transfer, and return the magic value
 	* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
 	* the transfer is reverted.
 	* Requires the msg.sender to be the owner, approved, or operator
 	* @param from current owner of the token
 	* @param to address to receive the ownership of the given token ID
 	* @param tokenId uint256 ID of the token to be transferred
 	* @param _data bytes data to send along with a safe transfer check
 	*/
	function _safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) internal {
    	_transferFrom(from, to, tokenId);
    	require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
	}

	/**
 	* @dev Returns whether the specified token exists.
 	* @param tokenId uint256 ID of the token to query the existence of
 	* @return bool whether the token exists
 	*/
	function _exists(uint256 tokenId) internal view returns (bool) {
    	address owner = _tokenOwner[tokenId];
    	return owner != address(0);
	}

	/**
 	* @dev Returns whether the given spender can transfer a given token ID.
 	* @param spender address of the spender to query
 	* @param tokenId uint256 ID of the token to be transferred
 	* @return bool whether the msg.sender is approved for the given token ID,
 	* is an operator of the owner, or is the owner of the token
 	*/
	function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
    	require(_exists(tokenId), "ERC721: operator query for nonexistent token");
    	address owner = ownerOf(tokenId);
    	return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
	}

	/**
 	* @dev Internal function to safely mint a new token.
 	* Reverts if the given token ID already exists.
 	* If the target address is a contract, it must implement `onERC721Received`,
 	* which is called upon a safe transfer, and return the magic value
 	* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
 	* the transfer is reverted.
 	* @param to The address that will own the minted token
 	* @param tokenId uint256 ID of the token to be minted
 	*/
	function _safeMint(address to, uint256 tokenId) internal {
    	_safeMint(to, tokenId, "");
	}

	/**
 	* @dev Internal function to safely mint a new token.
 	* Reverts if the given token ID already exists.
 	* If the target address is a contract, it must implement `onERC721Received`,
 	* which is called upon a safe transfer, and return the magic value
 	* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
 	* the transfer is reverted.
 	* @param to The address that will own the minted token
 	* @param tokenId uint256 ID of the token to be minted
 	* @param _data bytes data to send along with a safe transfer check
 	*/
	function _safeMint(address to, uint256 tokenId, bytes memory _data) internal {
    	_mint(to, tokenId);
    	require(_checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
	}

	/**
 	* @dev Internal function to mint a new token.
 	* Reverts if the given token ID already exists.
 	* @param to The address that will own the minted token
 	* @param tokenId uint256 ID of the token to be minted
 	*/
	function _mint(address to, uint256 tokenId) internal {
    	require(to != address(0), "ERC721: mint to the zero address");
    	require(!_exists(tokenId), "ERC721: token already minted");

    	_tokenOwner[tokenId] = to;
    	_ownedTokensCount[to].increment();

    	emit Transfer(address(0), to, tokenId);
	}

	/**
 	* @dev Internal function to burn a specific token.
 	* Reverts if the token does not exist.
 	* Deprecated, use {_burn} instead.
 	* @param owner owner of the token to burn
 	* @param tokenId uint256 ID of the token being burned
 	*/
	function _burn(address owner, uint256 tokenId) internal {
    	require(ownerOf(tokenId) == owner, "ERC721: burn of token that is not own");

    	_clearApproval(tokenId);

    	_ownedTokensCount[owner].decrement();
    	_tokenOwner[tokenId] = address(0);

    	emit Transfer(owner, address(0), tokenId);
	}

	/**
 	* @dev Internal function to burn a specific token.
 	* Reverts if the token does not exist.
 	* @param tokenId uint256 ID of the token being burned
 	*/
	function _burn(uint256 tokenId) internal {
    	_burn(ownerOf(tokenId), tokenId);
	}

	/**
 	* @dev Internal function to transfer ownership of a given token ID to another address.
 	* As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
 	* @param from current owner of the token
 	* @param to address to receive the ownership of the given token ID
 	* @param tokenId uint256 ID of the token to be transferred
 	*/
	function _transferFrom(address from, address to, uint256 tokenId) internal {
    	require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
    	require(to != address(0), "ERC721: transfer to the zero address");

    	_clearApproval(tokenId);

    	_ownedTokensCount[from].decrement();
    	_ownedTokensCount[to].increment();

    	_tokenOwner[tokenId] = to;

    	emit Transfer(from, to, tokenId);
	}

	/**
 	* @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
 	* The call is not executed if the target address is not a contract.
 	*
 	* This is an internal detail of the `ERC721` contract and its use is deprecated.
 	* @param from address representing the previous owner of the given token ID
 	* @param to target address that will receive the tokens
 	* @param tokenId uint256 ID of the token to be transferred
 	* @param _data bytes optional data to send along with the call
 	* @return bool whether the call correctly returned the expected magic value
 	*/
	function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data)
    	internal returns (bool)
	{
    	if (!to.isContract()) {
        	return true;
    	}
    	// solhint-disable-next-line avoid-low-level-calls
    	(bool success, bytes memory returndata) = to.call(abi.encodeWithSelector(
        	IERC721Receiver(to).onERC721Received.selector,
        	_msgSender(),
        	from,
        	tokenId,
        	_data
    	));
    	if (!success) {
        	if (returndata.length > 0) {
            	// solhint-disable-next-line no-inline-assembly
            	assembly {
                	let returndata_size := mload(returndata)
                	revert(add(32, returndata), returndata_size)
            	}
        	} else {
            	revert("ERC721: transfer to non ERC721Receiver implementer");
        	}
    	} else {
        	bytes4 retval = abi.decode(returndata, (bytes4));
        	return (retval == _ERC721_RECEIVED);
    	}
	}

	/**
 	* @dev Private function to clear current approval of a given token ID.
 	* @param tokenId uint256 ID of the token to be transferred
 	*/
	function _clearApproval(uint256 tokenId) private {
    	if (_tokenApprovals[tokenId] != address(0)) {
        	_tokenApprovals[tokenId] = address(0);
    	}
	}
}

/**
 * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
 */
contract IERC721Enumerable is IERC721 {
	function totalSupply() public view returns (uint256);
	function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256 tokenId);

	function tokenByIndex(uint256 index) public view returns (uint256);
}

/**
 * @title ERC-721 Non-Fungible Token with optional enumeration extension logic
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
contract ERC721Enumerable is Context, ERC165, ERC721, IERC721Enumerable {
	// Mapping from owner to list of owned token IDs
	mapping(address => uint256[]) private _ownedTokens;

	// Mapping from token ID to index of the owner tokens list
	mapping(uint256 => uint256) private _ownedTokensIndex;

	// Array with all token ids, used for enumeration
	uint256[] private _allTokens;

	// Mapping from token id to position in the allTokens array
	mapping(uint256 => uint256) private _allTokensIndex;

	/*
 	* 	bytes4(keccak256('totalSupply()')) == 0x18160ddd
 	* 	bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59
 	* 	bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7
 	*
 	* 	=> 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63
 	*/
	bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63;

	/**
 	* @dev Constructor function.
 	*/
	constructor () public {
    	// register the supported interface to conform to ERC721Enumerable via ERC165
    	_registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE);
	}

	/**
 	* @dev Gets the token ID at a given index of the tokens list of the requested owner.
 	* @param owner address owning the tokens list to be accessed
 	* @param index uint256 representing the index to be accessed of the requested tokens list
 	* @return uint256 token ID at the given index of the tokens list owned by the requested address
 	*/
	function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) {
    	require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
    	return _ownedTokens[owner][index];
	}

	/**
 	* @dev Gets the total amount of tokens stored by the contract.
 	* @return uint256 representing the total amount of tokens
 	*/
	function totalSupply() public view returns (uint256) {
    	return _allTokens.length;
	}

	/**
 	* @dev Gets the token ID at a given index of all the tokens in this contract
 	* Reverts if the index is greater or equal to the total number of tokens.
 	* @param index uint256 representing the index to be accessed of the tokens list
 	* @return uint256 token ID at the given index of the tokens list
 	*/
	function tokenByIndex(uint256 index) public view returns (uint256) {
    	require(index < totalSupply(), "ERC721Enumerable: global index out of bounds");
    	return _allTokens[index];
	}

	/**
 	* @dev Internal function to transfer ownership of a given token ID to another address.
 	* As opposed to transferFrom, this imposes no restrictions on msg.sender.
 	* @param from current owner of the token
 	* @param to address to receive the ownership of the given token ID
 	* @param tokenId uint256 ID of the token to be transferred
 	*/
	function _transferFrom(address from, address to, uint256 tokenId) internal {
    	super._transferFrom(from, to, tokenId);

    	_removeTokenFromOwnerEnumeration(from, tokenId);

    	_addTokenToOwnerEnumeration(to, tokenId);
	}

	/**
 	* @dev Internal function to mint a new token.
 	* Reverts if the given token ID already exists.
 	* @param to address the beneficiary that will own the minted token
 	* @param tokenId uint256 ID of the token to be minted
 	*/
	function _mint(address to, uint256 tokenId) internal {
    	super._mint(to, tokenId);

    	_addTokenToOwnerEnumeration(to, tokenId);

    	_addTokenToAllTokensEnumeration(tokenId);
	}

	/**
 	* @dev Internal function to burn a specific token.
 	* Reverts if the token does not exist.
 	* Deprecated, use {ERC721-_burn} instead.
 	* @param owner owner of the token to burn
 	* @param tokenId uint256 ID of the token being burned
 	*/
	function _burn(address owner, uint256 tokenId) internal {
    	super._burn(owner, tokenId);

    	_removeTokenFromOwnerEnumeration(owner, tokenId);
    	// Since tokenId will be deleted, we can clear its slot in _ownedTokensIndex to trigger a gas refund
    	_ownedTokensIndex[tokenId] = 0;

    	_removeTokenFromAllTokensEnumeration(tokenId);
	}

	/**
 	* @dev Gets the list of token IDs of the requested owner.
 	* @param owner address owning the tokens
 	* @return uint256[] List of token IDs owned by the requested address
 	*/
	function _tokensOfOwner(address owner) internal view returns (uint256[] storage) {
    	return _ownedTokens[owner];
	}

	/**
 	* @dev Private function to add a token to this extension's ownership-tracking data structures.
 	* @param to address representing the new owner of the given token ID
 	* @param tokenId uint256 ID of the token to be added to the tokens list of the given address
 	*/
	function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
    	_ownedTokensIndex[tokenId] = _ownedTokens[to].length;
    	_ownedTokens[to].push(tokenId);
	}

	/**
 	* @dev Private function to add a token to this extension's token tracking data structures.
 	* @param tokenId uint256 ID of the token to be added to the tokens list
 	*/
	function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
    	_allTokensIndex[tokenId] = _allTokens.length;
    	_allTokens.push(tokenId);
	}

	/**
 	* @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
 	* while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
 	* gas optimizations e.g. when performing a transfer operation (avoiding double writes).
 	* This has O(1) time complexity, but alters the order of the _ownedTokens array.
 	* @param from address representing the previous owner of the given token ID
 	* @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
 	*/
	function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
    	// To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
    	// then delete the last slot (swap and pop).

    	uint256 lastTokenIndex = _ownedTokens[from].length.sub(1);
    	uint256 tokenIndex = _ownedTokensIndex[tokenId];

    	// When the token to delete is the last token, the swap operation is unnecessary
    	if (tokenIndex != lastTokenIndex) {
        	uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];

        	_ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
        	_ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
    	}

    	// This also deletes the contents at the last position of the array
    	_ownedTokens[from].length--;

    	// Note that _ownedTokensIndex[tokenId] hasn't been cleared: it still points to the old slot (now occupied by
    	// lastTokenId, or just over the end of the array if the token was the last one).
	}

	/**
 	* @dev Private function to remove a token from this extension's token tracking data structures.
 	* This has O(1) time complexity, but alters the order of the _allTokens array.
 	* @param tokenId uint256 ID of the token to be removed from the tokens list
 	*/
	function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
    	// To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
    	// then delete the last slot (swap and pop).

    	uint256 lastTokenIndex = _allTokens.length.sub(1);
    	uint256 tokenIndex = _allTokensIndex[tokenId];

    	// When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
    	// rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
    	// an 'if' statement (like in _removeTokenFromOwnerEnumeration)
    	uint256 lastTokenId = _allTokens[lastTokenIndex];

    	_allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
    	_allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index

    	// This also deletes the contents at the last position of the array
    	_allTokens.length--;
    	_allTokensIndex[tokenId] = 0;
	}
}

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
 */
contract IERC721Metadata is IERC721 {
	function name() external view returns (string memory);
	function symbol() external view returns (string memory);
	function tokenURI(uint256 tokenId) external view returns (string memory);
}

contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata {
	// Token name
	string private _name;

	// Token symbol
	string private _symbol;

	// Base URI
	string private _baseURI;

	// Optional mapping for token URIs
	mapping(uint256 => string) private _tokenURIs;

	/*
 	* 	bytes4(keccak256('name()')) == 0x06fdde03
 	* 	bytes4(keccak256('symbol()')) == 0x95d89b41
 	* 	bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd
 	*
 	* 	=> 0x06fdde03 ^ 0x95d89b41 ^ 0xc87b56dd == 0x5b5e139f
 	*/
	bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f;

	/**
 	* @dev Constructor function
 	*/
	constructor (string memory name, string memory symbol) public {
    	_name = name;
    	_symbol = symbol;

    	// register the supported interfaces to conform to ERC721 via ERC165
    	_registerInterface(_INTERFACE_ID_ERC721_METADATA);
	}

	/**
 	* @dev Gets the token name.
 	* @return string representing the token name
 	*/
	function name() external view returns (string memory) {
    	return _name;
	}

	/**
 	* @dev Gets the token symbol.
 	* @return string representing the token symbol
 	*/
	function symbol() external view returns (string memory) {
    	return _symbol;
	}

	/**
 	* @dev Returns the URI for a given token ID. May return an empty string.
 	*
 	* If the token's URI is non-empty and a base URI was set (via
 	* {_setBaseURI}), it will be added to the token ID's URI as a prefix.
 	*
 	* Reverts if the token ID does not exist.
 	*/
	function tokenURI(uint256 tokenId) external view returns (string memory) {
    	require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

    	string memory _tokenURI = _tokenURIs[tokenId];

    	// Even if there is a base URI, it is only appended to non-empty token-specific URIs
    	if (bytes(_tokenURI).length == 0) {
        	return "";
    	} else {
        	// abi.encodePacked is being used to concatenate strings
        	return string(abi.encodePacked(_baseURI, _tokenURI));
    	}
	}

	/**
 	* @dev Internal function to set the token URI for a given token.
 	*
 	* Reverts if the token ID does not exist.
 	*
 	* TIP: if all token IDs share a prefix (e.g. if your URIs look like
 	* `http://api.myproject.com/token/<id>`), use {_setBaseURI} to store
 	* it and save gas.
 	*/
	function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal {
    	require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token");
    	_tokenURIs[tokenId] = _tokenURI;
	}

	/**
 	* @dev Internal function to set the base URI for all token IDs. It is
 	* automatically added as a prefix to the value returned in {tokenURI}.
 	*
 	* _Available since v2.5.0._
 	*/
	function _setBaseURI(string memory baseURI) internal {
    	_baseURI = baseURI;
	}

	/**
	* @dev Returns the base URI set via {_setBaseURI}. This will be
	* automatically added as a preffix in {tokenURI} to each token's URI, when
	* they are non-empty.
	*
	* _Available since v2.5.0._
	*/
	function baseURI() external view returns (string memory) {
    	return _baseURI;
	}

	/**
 	* @dev Internal function to burn a specific token.
 	* Reverts if the token does not exist.
 	* Deprecated, use _burn(uint256) instead.
 	* @param owner owner of the token to burn
 	* @param tokenId uint256 ID of the token being burned by the msg.sender
 	*/
	function _burn(address owner, uint256 tokenId) internal {
    	super._burn(owner, tokenId);

    	// Clear metadata (if any)
    	if (bytes(_tokenURIs[tokenId]).length != 0) {
        	delete _tokenURIs[tokenId];
    	}
	}
}

/**
 * @notice Money Stamps Contract - Interest-Bearing NFTs with lexical stamps
 * <presented by OpenESQ || lexDAO>
 */
contract MoneyStampsERC721 is ERC721Metadata, ERC721Enumerable {
    using SafeMath for uint256;

    /***********************************|
    |        Variables and Events       |
    |__________________________________*/

    IERC20 internal dai;
    IChai internal chai;

    mapping(uint256 => uint256) internal chaiBalanceByTokenId; // Amount of Chai minted from Dai deposited

    uint256 internal totalMintedTokens;
    uint256 internal mintFee;
    uint256 internal collectedFees;
    uint256 internal requiredFunding; // Amount of Dai to deposit when minting

    address private owner; // To be assigned to a DAO

    bytes16 public version = "v0.0.1";

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /***********************************|
    |             Modifiers             |
    |__________________________________*/

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(msg.sender == owner, "E102");
        _;
    }

    /***********************************|
    |          Initialization           |
    |__________________________________*/

    constructor() ERC721Metadata("$TAMP", "$TAMP") public {
        // requiredFunding = 1e18;
        // mintFee = 50;    //  0.5% of Chai from deposited Dai

        owner = msg.sender;
        emit OwnershipTransferred(address(0), owner);
    }

    /***********************************|
    |            Public Read            |
    |__________________________________*/

    /**
     * @notice Gets the address of the contract owner.
     * @return The address of the owner
     */
    function getContractOwner() public view returns (address) {
        return owner;
    }

    /***********************************|
    |            Stamp Glue             |
    |__________________________________*/

    /**
     * @notice Gets the Amount of Base DAI held in the Token (amount token was minted with)
     * @return The Amount of DAI held in the Token
     */
    function baseStampWeight() public view returns (uint256) {
        return requiredFunding;
    }

    /**
     * @notice Gets the amount of interest the Token has generated (its accumulated stamp-loot)
     * @param _tokenId      The ID of the Token
     * @return  The amount of interest the Token has generated
     */
    function currentStampLoot(uint256 _tokenId) public returns (uint256) {
        require(_exists(_tokenId), "E402");

        uint256 currentLoot = chai.dai(chaiBalanceByTokenId[_tokenId]);
        if (requiredFunding >= currentLoot) { return 0; }
        return currentLoot.sub(requiredFunding);
    }

    /**
     * @notice Allows the owner of the Token to collect the interest generated from the token
     *  without removing the underlying DAI that is held in the token
     * @param _tokenId      The ID of the Token
     * @return The amount of interest released from the token
     */
    function peelStamp(uint256 _tokenId) public returns (uint256) {
        require(_isApprovedOrOwner(msg.sender, _tokenId), "E103");

        uint256 _currentLootInDai = currentStampLoot(_tokenId);
        require(_currentLootInDai > 0, "E403");

        uint256 _paidChai = _payoutStampedDai(msg.sender, _currentLootInDai);
        chaiBalanceByTokenId[_tokenId] = chaiBalanceByTokenId[_tokenId].sub(_paidChai);

        return _currentLootInDai;
    }

    /***********************************|
    |            Public Mint            |
    |__________________________________*/

    /**
     * @notice Mints multiple new Stamps
     * @param _to       The owner address to assign the new tokens to
     * @param _amount   The amount of tokens to mint
     * @param _stamp     Custom lexical stamp used to establish authenticity 
     * @return  The IDs of the newly minted tokens
     */
    function mintStamps(address _to, uint256 _amount, string memory _stamp) public returns (uint256[] memory) {
        address _self = address(this);
        uint256 i;
        uint256 _tokenId;
        uint256 _totalDai;
        uint256[] memory _tokenIds = new uint256[](_amount);

        for (i = 0; i < _amount; ++i) {
            _totalDai = requiredFunding.add(_totalDai);

            _tokenId = (totalMintedTokens.add(i+1));
            _tokenIds[i] = _tokenId;
            _mint(_to, _tokenId);
    	    _setTokenURI(_tokenId, _stamp);
        }
        totalMintedTokens = totalMintedTokens.add(_amount);

        if (_totalDai > 0) {
            // Transfer DAI from User to Contract
            _collectRequiredDai(msg.sender, _totalDai);

            uint256 _balance = chai.balanceOf(_self);
            for (i = 0; i < _amount; ++i) {
                _tokenId = _tokenIds[i];

                // Tokenize Interest
                chai.join(_self, requiredFunding);

                // Track Chai in each Token
                 chaiBalanceByTokenId[_tokenId] = _totalChaiForToken(chai.balanceOf(_self).sub(_balance));
                _balance = chai.balanceOf(_self);
            }
        }
        return _tokenIds;
    }

    /***********************************|
    |            Public Burn            |
    |__________________________________*/

    /**
     * @notice Destroys a Stamp and releases the underlying DAI + Interest (Weight + Loot)
     * @param _tokenId      The ID of the token to burn
     */
    function burnStamp(uint256 _tokenId) public {
        // Burn Token
        _burn(msg.sender, _tokenId);

        // Payout Dai + Interest
        uint256 _tokenChai = chaiBalanceByTokenId[_tokenId];
        chaiBalanceByTokenId[_tokenId] = 0;
        _payoutFundedDai(msg.sender, _tokenChai);
    }

    /**
     * @notice Destroys multiple Stamps and releases the underlying DAI + Interest (Weight + Loot)
     * @param _tokenIds     The IDs of the tokens to burn
     */
    function burnStamps(uint256[] memory _tokenIds) public {
        uint256 _tokenId;
        uint256 _totalChai;
        for (uint256 i = 0; i < _tokenIds.length; ++i) {
            _tokenId = _tokenIds[i];

            // Burn Token
            _burn(msg.sender, _tokenId);

            // Payout Dai + Interest
            _totalChai = chaiBalanceByTokenId[_tokenId].add(_totalChai);
            chaiBalanceByTokenId[_tokenId] = 0;
        }
        _payoutFundedDai(msg.sender, _totalChai);
    }

    /***********************************|
    |            Only Owner             |
    |__________________________________*/

    /**
     * @dev Setup the DAI/CHAI contracts and configure the $TAMP contract
     */
    function setup(address _daiAddress, address _chaiAddress, uint256 _mintFee, uint256 _requiredFunding) public onlyOwner {
        // Set DAI as Funding Token
        dai = IERC20(_daiAddress);
        chai = IChai(_chaiAddress);

        // Setup Chai to Tokenize DAI Interest
        dai.approve(_chaiAddress, uint(-1));

        mintFee = _mintFee;
        requiredFunding = _requiredFunding;
    }

    /**
     * @dev Allows contract owner to withdraw any fees earned
     */
    function withdrawFees() public onlyOwner {
        uint256 _balance = address(this).balance;
        if (_balance > 0) {
            msg.sender.transfer(_balance);
        }
        if (collectedFees > 0) {
            _payoutFundedDai(msg.sender, collectedFees);
            collectedFees = 0;
        }
    }

    /**
     * @notice Transfers the ownership of the contract to new address
     * @param _newOwner Address of the new owner
     */
    function transferOwnership(address _newOwner) public onlyOwner {
        require(_newOwner != address(0), "E101");
        emit OwnershipTransferred(owner, _newOwner);
        owner = _newOwner;
    }

    /***********************************|
    |         Private Functions         |
    |__________________________________*/

    /**
     * @dev Collects the Required DAI from the users wallet during Minting
     * @param _from         The owner address to collect the DAI from
     * @param _requiredDai  The amount of DAI to collect from the user
     */
    function _collectRequiredDai(address _from, uint256 _requiredDai) internal {
        // Transfer DAI from User to Contract
        uint256 _userDaiBalance = dai.balanceOf(_from);
        require(_requiredDai <= _userDaiBalance, "E404");
        require(dai.transferFrom(_from, address(this), _requiredDai), "E405");
    }

    /**
     * @dev Pays out a specified amount of CHAI
     * @param _to           The owner address to pay out to
     * @param _totalChai    The total amount of CHAI to pay out
     */
    function _payoutFundedDai(address _to, uint256 _totalChai) internal {
        address _self = address(this);

        // Exit Chai and collect Dai + Interest
        chai.exit(_self, _totalChai);

        // Transfer Dai + Interest
        uint256 _receivedDai = dai.balanceOf(_self);
        require(dai.transferFrom(_self, _to, _receivedDai), "E405");
    }

    /**
     * @dev Pays out a specified amount of DAI
     * @param _to           The owner address to pay out to
     * @param _totalDai     The total amount of DAI to pay out
     */
    function _payoutStampedDai(address _to, uint256 _totalDai) internal returns (uint256) {
        address _self = address(this);

        // Collect Interest
        uint256 _chai = chai.draw(_self, _totalDai);

        // Transfer Interest
        uint256 _receivedDai = dai.balanceOf(_self);
        require(dai.transferFrom(_self, _to, _receivedDai), "E405");
        return _chai;
    }

    /**
     * @dev Calculates the amount of DAI held within a token during minting
     *      Note: Accounts for any contract fees
     * @param _tokenChai    The total amount of DAI used to fund the token
     * @return  The actual amount of DAI to fund the token - fees
     */
    function _totalChaiForToken(uint256 _tokenChai) internal returns (uint256) {
        if (mintFee == 0) { return _tokenChai; }
        uint256 _mintFee = _tokenChai.mul(mintFee).div(1e4);
        collectedFees = collectedFees.add(_mintFee);
        return _tokenChai.sub(_mintFee);
    }
}