Skip to content

Instantly share code, notes, and snippets.

@ShariqKhan2012
Created July 12, 2025 05:56
Show Gist options
  • Select an option

  • Save ShariqKhan2012/d06b0eb08e712b4164259aa444c34222 to your computer and use it in GitHub Desktop.

Select an option

Save ShariqKhan2012/d06b0eb08e712b4164259aa444c34222 to your computer and use it in GitHub Desktop.
This gist contains an implementation of EIP-ERC721 (NFT) standard. It also implements the required ERC165 (supportsInterface) standard and the optional ERC721Enumerable, and ERC721Metadata extensions.
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external pure returns (bool);
}
/* ERC721 MUST implement ERC165 */
interface IERC721 is IERC165 {
event Transfer(address indexed from, address indexed to, uint256 tokenId);
event Approval(address indexed owner, address indexed spender, uint256 tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
//SIGBOATS
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) external payable;
function safeTransferFrom(address from, address to, uint256 tokenId) external payable;
function transferFrom(address from, address to, uint256 tokenId) external payable;
function isApprovedForAll(address owner, address operator) external returns (bool);
function getApproved(uint256 tokenId) external returns (address);
function balanceOf(address owner) external returns (uint256);
function ownerOf(uint256 tokenId) external returns (address);
function approve(address spender, uint256 tokenId) external payable;
function setApprovalForAll(address operator, bool approved) external;
}
/**
* ERC721Metadata SHALL automatically implement ERC721
* because it does not stand alone without the ERC721
*/
interface 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);
}
/**
* IERC721Enumerable SHALL automatically implement ERC721
* because it does not stand alone without the ERC721
*/
interface IERC721Enumerable is IERC721 {
function totalSupply() external view returns (uint256);
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
function tokenByIndex(uint256 index) external view returns (uint256);
}
/**
* IERC721Receiver does NOT need to be implemented
* on the NFT contract. Instead, it is meant to be
* implemented on a contract that wants to indicate
* that it can safely receive NFTs.
*/
interface IERC721Receiver {
function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data) external returns (bytes4);
}
/**
* This contract implements the IERC721Receiver interface.
* So, it is here just for a SUCCESSFUL demo of
* safeTransferFrom.
* The 'to' argument in
* safeTransferFrom(from, to, tokenId, data)
* should be the address of this contract
*/
contract ERC721ReceiverMinimalExample is IERC721Receiver {
function doSomethingWithReceivedNFT(address operator, address from, uint256 tokenId, bytes memory data) public {
//Do something with received NFT
}
function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data) public returns (bytes4) {
doSomethingWithReceivedNFT(operator, from, tokenId, data);
return IERC721Receiver.onERC721Received.selector; // returns 0x150b7a02
}
}
/**
* This contract DOES NOT implement the IERC721Receiver interface.
* So, it is here just for a FAILING demo of
* safeTransferFrom.
* The 'to' argument in
* safeTransferFrom(from, to, tokenId, data)
* should be the address of this contract
*/
contract NonERC721ReceiverMinimalExample {
function doSomethingWithReceivedNFT() public pure returns (string memory){
return "Hello";
}
}
/**
* The actual contract implementing the NFT standard
* i.e. ERC721.
* Additionally, it also implements the optional
* IERC721Metadata and IERC721Enumerable extensions
*
*/
contract TigertNFT is IERC165, IERC721, IERC721Metadata, IERC721Enumerable {
// ERC165 interface IDs
bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
bytes4 private constant _INTERFACE_ID_ERC721Metadata = 0x5b5e139f;
bytes4 private constant _INTERFACE_ID_ERC721Enumerable = 0x780e9d63;
string public constant name = "TigerNFT";
string public constant symbol = "TNFT";
uint256 private _currentTokenId = 1;
mapping(uint256 tokenId => address owner) private _owners;
mapping(address owner => uint256 balance) private _balances;
mapping(uint256 tokenId => string url) private _tokenURIs;
mapping(uint256 tokenId => address spender) private _tokenApprovals;
mapping(address owner => mapping(address operator => bool approved)) private _operatorApprovals;
mapping(address owner => uint256[] tokens) private _ownedTokens;
mapping(uint256 tokenId => uint256 index) private _ownedTokensIndex;
uint256[] private _allTokens;
mapping(uint256 tokenId => uint256 index) private _allTokensIndex;
function getTokensOfOwner(address owner) public view returns(uint256[] memory) {
return _ownedTokens[owner];
}
function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
return interfaceId == _INTERFACE_ID_ERC165 ||
interfaceId == _INTERFACE_ID_ERC721 ||
interfaceId == _INTERFACE_ID_ERC721Metadata ||
interfaceId == _INTERFACE_ID_ERC721Enumerable;
}
function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
_ownedTokens[to].push(tokenId);
_ownedTokensIndex[tokenId] = _ownedTokens[to].length - 1;
}
function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
uint256 tokenIndex = _ownedTokensIndex[tokenId];
uint256 lastIndex = _ownedTokens[from].length - 1;
/**
* Need to do a bit of moving around, if the
* token being removed is not the last one
*/
if(tokenIndex < lastIndex) {
uint256 lastToken = _ownedTokens[from][lastIndex];
_ownedTokens[from][tokenIndex] = lastToken;
_ownedTokensIndex[lastToken] = tokenIndex;
}
_ownedTokens[from].pop();
delete _ownedTokensIndex[tokenId];
}
function _updateTokenOwnerEnumeration(address from, address to, uint256 tokenId) private {
if(from != address(0)) {
_removeTokenFromOwnerEnumeration(from, tokenId);
}
if(to != address(0)) {
_addTokenToOwnerEnumeration(to, tokenId);
}
}
function transferFrom(address from, address to, uint256 tokenId) public payable {
require(to != address(0), "Zero address");
address owner = ownerOf(tokenId);
require(owner == from, "Token not owned by sender");
// Caller should either be owner, or approved or operator
require(owner == msg.sender ||
getApproved(tokenId) == msg.sender ||
isApprovedForAll(from, msg.sender),
"Not authorized"
);
_updateTokenOwnerEnumeration(from, to, tokenId);
_owners[tokenId] = to;
_balances[from] -= 1;
_balances[to] += 1;
delete _tokenApprovals[tokenId];
emit Transfer(from, to, tokenId);
}
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public payable {
transferFrom(from, to, tokenId);
require(_checkOnERC721Received(msg.sender, from, to, tokenId, data), "Not safe");
}
function safeTransferFrom(address from, address to, uint256 tokenId) public payable {
transferFrom(from, to, tokenId);
require(_checkOnERC721Received(msg.sender, from, to, tokenId, ""), "Not safe");
}
function _checkOnERC721Received(address operator, address from, address to, uint256 tokenId, bytes memory data) private returns (bool) {
if(_isContract(to)) {
try IERC721Receiver(to).onERC721Received(operator, from, tokenId, data) returns (bytes4 retval) {
return retval == IERC721Receiver.onERC721Received.selector;
}
catch {
return false;
}
}
return true;
}
function _isContract(address account) private view returns (bool) {
return account.code.length > 0;
}
function isApprovedForAll(address owner, address operator) public view returns (bool) {
return _operatorApprovals[owner][operator];
}
function getApproved(uint256 tokenId) public view returns (address) {
return _tokenApprovals[tokenId];
}
function balanceOf(address owner) public view returns (uint256) {
return _balances[owner];
}
function ownerOf(uint256 tokenId) public view returns (address) {
return _owners[tokenId];
}
function approve(address spender, uint256 tokenId) public payable{
require(ownerOf(tokenId) == msg.sender || isApprovedForAll(msg.sender, spender), "Not authorized");
_tokenApprovals[tokenId] = spender;
emit Approval(msg.sender, spender, tokenId);
}
function setApprovalForAll(address operator, bool approved) public {
require(operator != address(0), "Zero address");
_operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
//Metadata functions
function tokenURI(uint256 tokenId) public view returns (string memory) {
return _tokenURIs[tokenId];
}
/* Enumeration functions */
function totalSupply() public view returns (uint256) {
return _allTokens.length;
}
function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) {
return _ownedTokens[owner][index];
}
function tokenByIndex(uint256 index) public view returns (uint256) {
return _allTokens[index];
}
function mint(string memory ipfsUrl) public {
uint256 tokenId = _currentTokenId++;
_owners[tokenId] = msg.sender;
_balances[msg.sender] += 1;
_allTokens.push(tokenId);
_allTokensIndex[tokenId] = _allTokens.length - 1;
_tokenURIs[tokenId] = ipfsUrl;
_addTokenToOwnerEnumeration(msg.sender, tokenId);
emit Transfer(address(0), msg.sender, tokenId);
}
function burn(uint256 tokenId) public {
address owner = ownerOf(tokenId);
require(owner != address(0), "Not minted");
require(msg.sender == owner, "Not authorized");
_balances[owner] -= 1;
delete _owners[tokenId];
delete _tokenURIs[tokenId];
delete _tokenApprovals[tokenId];
_removeTokenFromOwnerEnumeration(owner, tokenId);
uint256 tokenIndex = _allTokensIndex[tokenId];
uint256 lastTokenIndex = totalSupply() - 1;
if(tokenIndex < lastTokenIndex) {
uint256 lastTokenId = _allTokens[lastTokenIndex];
_allTokens[tokenIndex] = _allTokens[lastTokenIndex];
_allTokensIndex[lastTokenId] = tokenIndex;
}
_allTokens.pop();
delete _allTokensIndex[tokenId];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment