ERC-721 구현

CHOYEAH·2023년 10월 23일
0
post-thumbnail

인터페이스 기반하여 구현


pragma solidity >=0.4.24 <=0.5.6;

/// @title ERC-721 Non-Fungible Token Standard
/// @dev See https://eips.ethereum.org/EIPS/eip-721
///  Note: the ERC-165 identifier for this interface is 0x80ac58cd.
interface ERC721 /* is ERC165 */ {
   
    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);

    function balanceOf(address _owner) external view returns (uint256);
    function ownerOf(uint256 _tokenId) external view returns (address);

    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external ;
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external ;
    function transferFrom(address _from, address _to, uint256 _tokenId) external ;
    function approve(address _approved, uint256 _tokenId) external ;
    function setApprovalForAll(address _operator, bool _approved) external;
    function getApproved(uint256 _tokenId) external view returns (address);
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

interface ERC721TokenReceiver {
    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4);
}

contract ERC721Implementation is ERC721 {
    
    // 토큰의 아이디를 키값으로 해당 주소를 리턴 
    mapping (uint256 => address) tokenOwner;
    // 계정 주소에 해당하는 토큰 수를 리턴
    mapping (address => uint256) owendTokensCount;
    mapping (uint256 => address) tokenApprovals;
    // 누가 누구에게 권한 부여를 했는가에 대한 맵핑, 1:N으로 권한을 부여할 수 있음.
    mapping (address => mapping (address => bool)) operatorApprovals;

    // 토큰 발행
    function mint(
        address _to, // 발행된 토큰의 소유자
        uint _tokenId // 몇 번째 토큰인지
        ) public {
            tokenOwner[_tokenId] = _to;
            owendTokensCount[_to] += 1; // 토큰 보유 개수 1 증가시킴
    }

    // 특정 계정의 토큰 수를 리턴
    function balanceOf(address _owner) public view returns (uint256) {
        return owendTokensCount[_owner];
    }

    // 토큰의 주인 주소를 리턴
    function ownerOf(uint256 _tokenId) public view returns (address) {
        return tokenOwner[_tokenId];
    }

    // 토큰 전송
    function transferFrom(address _from, address _to, uint256 _tokenId) public {
        // 이 함수를 호출한 계정이 _tokenId의 소유자인지 검사
        address owner = ownerOf(_tokenId);
        // 1.토큰의 소유자 계정이거나 2.특정 토큰에 권한이 있는 계정이 접근했을때 3.소유자의 전체 토큰의 전송 권한을 가진  계정이 호출했을때
        require(msg.sender == owner || getApproved(_tokenId) == msg.sender || isApprovedForAll(owner, msg.sender));
        // 빈 인자 검사 
        require(_from != address(0)); // address(0)는 비어있다는 의미
        require(_to != address(0));
        
        // 보내는 측의 토큰 보유 수량을 1차감
        owendTokensCount[_from] -= 1;
        // 기존 토큰 보유자의 토큰 소유 정보를 제거
        tokenOwner[_tokenId] = address(0);
        // 전달받는 측의 토큰 보유 수량을 1증가
        owendTokensCount[_to] += 1;
        // 전달받는 측에 토큰 소유정보 추가 
        tokenOwner[_tokenId] = _to;     
    }

    // 토큰 안전 정송
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) public {
         transferFrom(_from, _to, _tokenId); 
         if(isContract(_to)){
            bytes4 returnValue = ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, '');
            require(returnValue == 0x150b7a02);
         }
    }

    // 토큰 안전 전송2
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) public {
        transferFrom(_from, _to, _tokenId); 
        if(isContract(_to)){
            bytes4 returnValue = ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, data);
            require(returnValue == 0x150b7a02);
        }

    }

    function approve(address _approved, uint256 _tokenId) public {
        // 권한을 받게되는 계정은 토큰 오너가 아니여야 한다.
        address owner = ownerOf(_tokenId);
        require(_approved != owner);
        // 토큰 주인이 호출해야함 
        require(msg.sender == owner);
        tokenApprovals[_tokenId] = _approved;
    }
    
    function getApproved(uint256 _tokenId) public view returns (address) {
        return tokenApprovals[_tokenId];
    }

    function setApprovalForAll(address _operator, bool _approved) external {
        // _operator: 계정이 소유한 모든 토큰들을 대신 운영할 계정
        // _approved: 권한 부여 여부
        require(_operator != msg.sender);
        operatorApprovals[msg.sender][_operator] = _approved;
    }
    
    function isApprovedForAll(address _owner, address _operator) public view returns (bool) {
        return operatorApprovals[_owner][_operator];
    }

    function isContract(address _addr) private view returns (bool) {
        uint256 size;
        assembly { size:= extcodesize(_addr) }
        return size > 0;
    }
}

contract Auction is ERC721TokenReceiver {
    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes memory _data) public returns(bytes4) {
        return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
    }
}

그냥 구현


// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

contract ERC721 {

  string private _name; 
  string private _symbol; 

  mapping(uint256 => string) private _tokenInfo;
  mapping(uint256 => address) private _owners;
  mapping(address => uint256) private _balances;
  mapping(uint256 => address) private _tokenApprovals;
  mapping(address => mapping(address=>bool)) private _operatorApprovals;

  uint private _totalSupply;
 
  event Transfer(address indexed from, address indexed to, uint tokenId);
  event Approval(address indexed from, address indexed to, uint tokenId);
  event ApprovalForAll(address from, address operator, bool approval);

  constructor(string memory name_, string memory symbol_) {
    _name = name_;
    _symbol = symbol_;
  }

  function balanceOf(address owner) public view returns (uint256) {
    return _balances[owner];
  }

  function ownerOf(uint256 tokenId) public view returns (address) {
    return _owners[tokenId];
  }

  function name() public view returns (string memory) {
    return _name;
  }

  function symbol() public view returns (string memory) {
    return _symbol;
  }

  function tokenURI(uint tokenId) public view returns (string memory) {
    return _tokenInfo[tokenId];
  }

  function getApproved(uint256 tokenId) public view returns (address) {
    return _tokenApprovals[tokenId];
  }

  function isApprovedForAll(address owner, address operator) public view returns (bool) {
    return _operatorApprovals[owner][operator];
  }

    // 사용자 a가 사용자 b의 계좌에서 나에게 전송해달라고 요청하는 함수, 받는 사람이 호출하는 구조 
  function transferFrom(
    address from,
    address to,
    uint256 tokenId
  ) public {
    address owner = _owners[tokenId];
    require(from == owner || isApprovedForAll(owner, msg.sender) || getApproved(tokenId) == msg.sender, "Not Approved");
    
    _balances[msg.sender] -= 1;
    _balances[to] += 1;
    _owners[tokenId] = to;

    emit Transfer(from, to, tokenId);
  }

  function mint(address to, uint256 tokenId, string memory url ) public {
    _balances[to] += 1;
    _owners[tokenId] = to;
    _tokenInfo[tokenId] = url;
    _totalSupply += 1;
    emit Transfer(address(0), to, tokenId);
  }

  function burn(uint256 tokenId) public {
    address owner = _owners[tokenId];
    delete _tokenApprovals[tokenId];
    _balances[owner] -= 1;
    delete _owners[tokenId];
    emit Transfer(owner, address(0), tokenId);
  }

  function transfer(address to, uint256 tokenId) public {
    require(_owners[tokenId] == msg.sender, "Incorrect Owner");
    delete _tokenApprovals[tokenId];
    _balances[msg.sender] -= 1;
    _balances[to] += 1;
    _owners[tokenId] = to;
    emit Transfer(msg.sender, to, tokenId);
  }

  function approve(address to, uint256 tokenId) public {
     require(_owners[tokenId] == msg.sender, "Incorrect Owner");
    _tokenApprovals[tokenId] = to;
    emit Approval(_owners[tokenId], to, tokenId);
  }

  function setApprovalForAll(address owner, address operator, bool approved) public {
    _operatorApprovals[owner][operator] = approved;
    emit ApprovalForAll(owner, operator, approved);
  }
}

랜덤 NFT


// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

contract ERC721 {

  string private _name; 
  string private _symbol; 

  mapping(uint256 => uint) private _tokenInfo;
  mapping(uint256 => address) private _owners;
  mapping(address => uint256) private _balances;
  mapping(uint256 => address) private _tokenApprovals;
  mapping(address => mapping(address=>bool)) private _operatorApprovals;

  uint private _totalSupply = 10000;
 
  event Transfer(address indexed from, address indexed to, uint tokenId);
  event Approval(address indexed from, address indexed to, uint tokenId);
  event ApprovalForAll(address from, address operator, bool approval);

  constructor(string memory name_, string memory symbol_) {
    _name = name_;
    _symbol = symbol_;
  }

  function balanceOf(address owner) public view returns (uint256) {
    return _balances[owner];
  }

  function ownerOf(uint256 tokenId) public view returns (address) {
    return _owners[tokenId];
  }

  function name() public view returns (string memory) {
    return _name;
  }

  function symbol() public view returns (string memory) {
    return _symbol;
  }

  function tokenURI(uint tokenId) public view returns (uint) {
    return _tokenInfo[tokenId];
  }

  function getApproved(uint256 tokenId) public view returns (address) {
    return _tokenApprovals[tokenId];
  }

  function isApprovedForAll(address owner, address operator) public view returns (bool) {
    return _operatorApprovals[owner][operator];
  }

    // 사용자 a가 사용자 b의 계좌에서 나에게 전송해달라고 요청하는 함수, 받는 사람이 호출하는 구조 
  function transferFrom(
    address from,
    address to,
    uint256 tokenId
  ) public {
    address owner = _owners[tokenId];
    require(from == owner, "Not Approved");
    require(isApprovedForAll(owner, msg.sender), "Not Approved");
    require(getApproved(tokenId) == msg.sender, "Not Approved");
    
    _balances[msg.sender] -= 1;
    _balances[to] += 1;
    _owners[tokenId] = to;

    emit Transfer(from, to, tokenId);
  }

  function mint(address to, uint256 tokenId) public {
    _balances[to] += 1;
    _owners[tokenId] = to;
    _tokenInfo[tokenId] = randomNft(_totalSupply);
    emit Transfer(address(0), to, tokenId);
  }

  function randomNft(uint maxValue) internal view returns(uint) {
    return uint(keccak256(abi.encodePacked(msg.sender, block.timestamp, _totalSupply))) % maxValue;
  }

  function burn(uint256 tokenId) public {
    address owner = _owners[tokenId];
    delete _tokenApprovals[tokenId];
    _balances[owner] -= 1;
    delete _owners[tokenId];
    emit Transfer(owner, address(0), tokenId);
  }

  function transfer(address to, uint256 tokenId) public {
    require(_owners[tokenId] == msg.sender, "Incorrect Owner");
    delete _tokenApprovals[tokenId];
    _balances[msg.sender] -= 1;
    _balances[to] += 1;
    _owners[tokenId] = to;
    emit Transfer(msg.sender, to, tokenId);
  }

  function approve(address to, uint256 tokenId) public {
     require(_owners[tokenId] == msg.sender, "Incorrect Owner");
    _tokenApprovals[tokenId] = to;
    emit Approval(_owners[tokenId], to, tokenId);
  }

  function setApprovalForAll(address owner, address operator, bool approved) public {
    _operatorApprovals[owner][operator] = approved;
    emit ApprovalForAll(owner, operator, approved);
  }
}

ERC721Enumerable


The enumeration extension is OPTIONAL for ERC-721 smart contracts (see "caveats", below). This allows your contract to publish its full list of NFTs and make them discoverable.

/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
///  Note: the ERC-165 identifier for this interface is 0x780e9d63.
interface ERC721Enumerable /* is ERC721 */ {
    /// @notice Count NFTs tracked by this contract
    /// @return A count of valid NFTs tracked by this contract, where each one of
    ///  them has an assigned and queryable owner not equal to the zero address
    function totalSupply() external view returns (uint256);

    /// @notice Enumerate valid NFTs
    /// @dev Throws if `_index` >= `totalSupply()`.
    /// @param _index A counter less than `totalSupply()`
    /// @return The token identifier for the `_index`th NFT,
    ///  (sort order not specified)
    function tokenByIndex(uint256 _index) external view returns (uint256);

    /// @notice Enumerate NFTs assigned to an owner
    /// @dev Throws if `_index` >= `balanceOf(_owner)` or if
    ///  `_owner` is the zero address, representing invalid NFTs.
    /// @param _owner An address where we are interested in NFTs owned by them
    /// @param _index A counter less than `balanceOf(_owner)`
    /// @return The token identifier for the `_index`th NFT assigned to `_owner`,
    ///   (sort order not specified)
    function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
}

계정마다 소유한 토큰을 불러오거나 계정들을 통해 발행된 모든 토큰들을 불러온다.

ERC721Metadata


The metadata extension
is OPTIONAL for ERC-721 smart contracts (see "caveats", below). This allows your smart contract to be interrogated for its name and for details about the assets which your NFTs represent.

/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
///  Note: the ERC-165 identifier for this interface is 0x5b5e139f.
interface ERC721Metadata /* is ERC721 */ {
    /// @notice A descriptive name for a collection of NFTs in this contract
    function name() external view returns (string _name);

    /// @notice An abbreviated name for NFTs in this contract
    function symbol() external view returns (string _symbol);

    /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
    /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
    ///  3986. The URI may point to a JSON file that conforms to the "ERC721
    ///  Metadata JSON Schema".
    function tokenURI(uint256 _tokenId) external view returns (string);
}

tokenURI()는 토큰의 정보를 저장한 IPFS 주소를 의미함.
IPFS에 저장된 토큰 정보의 주소를 불러오는데 쓰인다.

profile
Move fast & break things

0개의 댓글