Repository
https://github.com/igormuba/EthereumSolidityClasses/tree/master/class15
What Will I Learn?
- What is the standard 721
- What is the standard 165 and how to implement it
- Implementing the transfer functions of ERC721
- Implementing the balance functionality of unique tokens
Requirements
- Internet connection
- Code editor
- Browser
Difficulty
- Intermediate
Tutorial Contents
On this tutorial, we will implement the transfer functions from the ERC721 standard. We will do everything on Remix because currently there is a bug on web3.js that when we use a getter function it throws an error. You can read more about this bug in the following post, in there is a fix that temporarily solves the issue, but since that is not the focus of this tutorial, I might cover that on another standalone post about Truffle bugs and how to fix them
https://github.com/ethereum/web3.js/issues/1916
On the previous tutorial we have created a non-fungible token, but it does not fit within any token standard.
It is important, when developing decentralized applications, to be aware of what design patterns you should follow, to allow third-party developers to access and use the application you have developed. If every dapp had an arbitrary design, it would be much harder to a third party to use their product.
In this tutorial, I will cover what a contract for a non-fungible token needs to fit in the ERC721 token standard.
The ERC721 standard
There is a website dedicated to teaching people about the ERC721 standard, on erc721.org you can see what big apps are using this development standard and what are the requirements.
First, the ERC 165
Right off the bat, the first requirement to fit into the ERC721 standard is to fit on another standard, the ERC165
https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md
The ERC165 is the standard that requires contracts to be able to identify their own standard and respond to the front end whether they can or can not handle the caller function requirements.
To comply with the ERC165 is easy, you just need to create a new contract called "ERC165.sol" and within it code the following interface
pragma solidity ^0.5.0;
interface ERC165 {
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
When you inherit from this interface you will have to implement a function that returns the response if the contract implemented has the functions required or not.
Inheriting the ERC165
On the contract we have created on the previous tutorial, we inherit from the interface 165 into our uniqueToken
contract uniqueToken is ERC165
And implement the function that checks if the standard meets the requirements, to avoid errors when compiling
Note: the bytes4 code for the ERC721 is 0x80ac58cd, so:
function supportsInterface(bytes4 interfaceID) external view returns (bool){
if (interfaceID==0x80ac58cd){ //checks if we implement the desired protocol
return true; //returns true if it does
}
}
The ERC721 events
Starting from the easiest and most intuitive thing from the ERC721, the events, they must be implemented so front end applications from other developers can know what has happened, the interfaces will expect these messages
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);
The Transfer
event alerts the network when new tokens are created, destroyed, or transferred.
The Approval
event alerts the network when an address approval is made, reaffirmed or deleted
The ApprovalFroAll
event alerts the network when an operator is enabled or disabled for an owner.
Balance of
The first function from the ERC721 standard we will implement is the balanceOf
, this function counts and returns all tokens owned by someone.
There is more than a way how we could implement this function. For instance, the simplest implementation, but more expensive in terms of gas cost, would be to check every token and see if certain address owns them.
A cheaper way, however, would require us to change functions we have implemented before. For this, first, I am implementing a mapping that keeps track of how many of our unique tokens certain address has
mapping(address=>uint) public uniqueTokens;
and on the function createToken
created on the last tutorial add uniqueTokens[msg.sender]+=1;
So now it looks like
function createToken(string memory _name, uint value) public{
tokens.push(ourToken(currentId, _name, value, msg.sender));
ownership[currentId]=msg.sender;
uniqueTokens[msg.sender]+=1; //increases token count for creator
currentId++;
}
And on the buyToken
function, also implemented on the previous tutorial, add uniqueTokens[tokens[_id].owner]-=1;
and uniqueTokens[msg.sender]+=1;
BEFORE WE CHANGE THE OWNER, so
function buyToken(uint _id) payable public{
require(msg.value>=tokens[_id].value);
balance[tokens[_id].owner]+=msg.value;
uniqueTokens[msg.sender]+=1;//increase token count of buyer
uniqueTokens[tokens[_id].owner]-=1; //reduces token count of seller
tokens[_id].owner = msg.sender;
ownership[_id] = msg.sender;
}
That done, we just need to implement the balanceOf
itself, that fetches and returns the value from the mapping
function balanceOf(address _owner) external view returns (uint256){
return uniqueTokens[_owner]; //returns how many tokens the _owner has
}
Owner of
The next function is the ownerOf
, luckily for us, we already have a very similar function, the tokenOwnership
, we can just rename it!
function ownerOf(uint _id) public view returns(address){
return ownership[_id];
}
Safe Transfer From and Transfer From
The ERC721 standard recommends to implement 2 types of safeTransferFrom
using polymorphism, I have covered polymorphism is another tutorial, one will take the address from, to and the token ID, the other will take the same 3 arguments but with extra data. It also requires a regular transfer from. Our application does not require extra data but we will implement both functions so that external applications can work without errors.
The standard recommends to throw an error unless the caller of the function is the owner, but we can leverage the fact that it is a payable function and implement on this the selling functionality of our tokens.
function safeTransferFrom(address _from, address _to, uint256 _id, bytes data) external payable{
if (msg.sender==tokens[_id].owner){
uniqueTokens[msg.sender]+=1;
uniqueTokens[tokens[_id].owner]-=1;
tokens[_id].owner = msg.sender;
ownership[_id] = msg.sender;
}else{
require(msg.value>=tokens[_id].value);
balance[tokens[_id].owner]+=msg.value;
uniqueTokens[msg.sender]+=1;
uniqueTokens[tokens[_id].owner]-=1;
tokens[_id].owner = msg.sender;
ownership[_id] = msg.sender;
}
}
function safeTransferFrom(address _from, address _to, uint256 _id) external payable{
if (msg.sender==tokens[_id].owner){
uniqueTokens[msg.sender]+=1;
uniqueTokens[tokens[_id].owner]-=1;
tokens[_id].owner = msg.sender;
ownership[_id] = msg.sender;
}else{
require(msg.value>=tokens[_id].value);
balance[tokens[_id].owner]+=msg.value;
uniqueTokens[msg.sender]+=1;
uniqueTokens[tokens[_id].owner]-=1;
tokens[_id].owner = msg.sender;
ownership[_id] = msg.sender;
}
}
function transferFrom(address _from, address _to, uint256 _id) external payable{
if (msg.sender==tokens[_id].owner){
uniqueTokens[msg.sender]+=1;
uniqueTokens[tokens[_id].owner]-=1;
tokens[_id].owner = msg.sender;
ownership[_id] = msg.sender;
}else{
require(msg.value>=tokens[_id].value);
balance[tokens[_id].owner]+=msg.value;
uniqueTokens[msg.sender]+=1;
uniqueTokens[tokens[_id].owner]-=1;
tokens[_id].owner = msg.sender;
ownership[_id] = msg.sender;
}
}
The next tutorial will cover the approvals of token because those features are very security focused and deserve a post for themselves.
Curriculum
- (Part 14) Ethereum Solidity - Token Uniqueness, Non-Fungibility And Transactions With Unique Tokens(PT 14)
- (Part 13) Ethereum Solidity In Truffle - Testnet Environment Function Calls And Truffle Testing(PT 13)
- (Part 12) Ethereum Solidity - Using Truffle, Ganache And Zeppelin To Deploy(PT 12)
- (Part 11) Ethereum Solidity - Multisig Contract As Bank,k Multiple Users, And Where To Implement App Logic(PT 11)
- (Part 10) Ethereum Solidity - Multiple inheritances, Diamond Problem And Function Polymorphism(PT 10)
- (Part 9) Ethereum Solidity Assembly - Return, Memory, Hexadecimal, Pointers, And Memory Addressing(PT 9)
- (Part 8) Ethereum Solidity - Assembly, Reducing Costs And Creation Your Low-Level Expression(PT 8)
- (Part 7) Ethereum Solidity - Fallback, Ethereum Fractions, And Collateral Backed Contract(PT 7)
- (Part 6) Ethereum Solidity - Custom Variable Functionalities, Libraries, Using Libraries For Security(PT 6)
- (Part 5) Ethereum Solidity - Custom Access Modifiers, Security Breach Alerts, Assert And Require(PT 5)
- (Part 4) Ethereum Solidity Development - Inheritance, Working With Multiple Contracts And Simple Mutability(PT 4)
- (Part 3) Ethereum Solidity Development - Contract Mutability, DelegateCall And Calling Functions By Address(PT 3)
- (Part 2) Ethereum Solidity Development - Deploying, Security And ERC20 Compliance(PT 2)
- (Part 1) Ethereum Solidity Development - Getting Started + Lower Level Explanation (PT 1)
Beneficiaries
This post has as beneficiaries
@utopian.pay with 5%
using the SteemPeak beneficiary tool