/** * ERC-721 Non-Fungible Token Standard * See https://eips.ethereum.org/EIPS/eip-721 * and https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol * * This implementation is currently incomplete. It lacks the following: * - Token approvals * - Operator approvals * - Emitting events */ export class ERC721 { constructor(name, symbol) { this.name = name; this.symbol = symbol; this.balances = new Map(); // owner address --> token count this.owners = new Map(); // token id --> owner address // this.tokenApprovals = new Map(); // token id --> approved addresses // this.operatorApprovals = new Map(); // owner --> operator approvals this.events = { // Transfer: (_from, _to, _tokenId) => {}, // Approval: (_owner, _approved, _tokenId) => {}, // ApprovalForAll: (_owner, _operator, _approved) => {}, }; } incrementBalance(owner, increment) { const balance = this.balances.get(owner) ?? 0; this.balances.set(owner, balance + increment); } mint(to, tokenId) { console.log('ERC721.mint', { to, tokenId }); if (this.owners.get(tokenId)) { throw new Error('ERC721: token already minted'); } this.incrementBalance(to, 1); this.owners.set(tokenId, to); } burn(tokenId) { const owner = this.owners.get(tokenId); this.incrementBalance(owner, -1); this.owners.delete(tokenId); } balanceOf(owner) { if (!owner) { throw new Error('ERC721: address zero is not a valid owner'); } return this.balances.get(owner) ?? 0; } ownerOf(tokenId) { const owner = this.owners.get(tokenId); if (!owner) { throw new Error(`ERC721: invalid token ID: ${tokenId}`); } return owner; } transfer(from, to, tokenId) { console.log('ERC721.transfer', { from, to, tokenId }); const owner = this.owners.get(tokenId); if (owner !== from) { throw new Error(`ERC721: transfer from incorrect owner ${from}; should be ${owner}`); } this.incrementBalance(from, -1); this.incrementBalance(to, 1); this.owners.set(tokenId, to); } /// @notice Enable or disable approval for a third party ("operator") to manage /// all of `msg.sender`'s assets /// @dev Emits the ApprovalForAll event. The contract MUST allow /// multiple operators per owner. /// @param _operator Address to add to the set of authorized operators /// @param _approved True if the operator is approved, false to revoke approval // setApprovalForAll(_operator, _approved) {} /// @notice Get the approved address for a single NFT /// @dev Throws if `_tokenId` is not a valid NFT. /// @param _tokenId The NFT to find the approved address for /// @return The approved address for this NFT, or the zero address if there is none // getApproved(_tokenId) {} /// @notice Query if an address is an authorized operator for another address /// @param _owner The address that owns the NFTs /// @param _operator The address that acts on behalf of the owner /// @return True if `_operator` is an approved operator for `_owner`, false otherwise // isApprovedForAll(_owner, _operator) {} }