(Part 14) Ethereum Solidity - Token Uniqueness, Non-Fungibility And Transacions With Unique Tokens(PT 14)
Repository
https://github.com/igormuba/EthereumSolidityClasses/tree/master/class14
What Will I Learn?
- What is fungibility
- How to make unique tokens
- Unique tokens transaction
Requirements
- Internet connection
- Code editor
- Browser
Difficulty
- Intermediate
Tutorial Contents
An Ethereum development pattern that is getting more common is the ERC721. The ERC721 compliant contracts allow you to build unique non-fungible tokens.
We won't cover the ERC721 standard yet, but I will show you how you can create unique tokens with just a few lines of Solidity code, this way you can understand how uniqueness an non-fungibility works before you really get started on fitting your contract on the ERC721 pattern.
Our token
The token we will build in not fungible. A fungible token is a token that can be mixed with other and is no different from other tokens. A not fungible token is a token that is unique and you can identify it.
An example of non-fungible currency is gold. If you steal gold from a bank, sure, it comes with some kind of print or marks to show from what bank it came from, but once you melt it you can sell as if you found it. A non-fungible currency, on the other hand, can be the paper dollar. Each dollar has a unique ID, though every dollar has the same value, if you steal from a bank and you want to deposit the stolen dollar, theoretically they can track you by the id of the bill!! Differently, from gold, you can't melt dollars!
So our token will have an "id" and an indicator of who owns it.
We will also implement a very simple and rudimentary internal market on the contract, which will allow you to set the price of a token, sell, and withdraw into your wallet.
Unique token structure
The code below creates the structure for the tokens we will mint. As you can see each token is unique.
pragma solidity ^0.5.0; //solidity version
contract uniqueToken{ //contract declaration
struct ourToken{ //token structure
uint id; //unique ID
string name; //token name
uint value; //token value (price, we will see it shortly)
address owner; //owner of the token
}
}
Here is what each variable of the structure stands for and how we will work with it
id
each token will have a unique ID that will automatically be assigned to it upon creation
name
the name we give to the token, this variable, differently from the id, is not unique and will be passed into it as an argument upon creation of the token
value
when creating the token we will be able to set a price to it, so if someone wants to buy it from the contract the person will be able to buy it at the price we have set, the price will be set upon token creation but we will create a function to change its value later
owner
wallet that owns the token
Token id
Before the struct code(or right after) declare a private variable, that will hold the value of the current ID, this will allow us to, later, assign a unique ID to each token
pragma solidity ^0.5.0;
contract uniqueToken{
uint private currentId=0; //current ID that will automatically be assigned to the token
struct ourToken{
uint id;
string name;
uint value;
address owner;
}
}
Storage for created tokens and owners
I am using the following variables to store the created tokens and the owners
ourToken[] tokens; //list of created tokens
mapping(uint => address) public ownership; //mapping token to owner
here is how each variable will work
tokens
this is an array that will, in order of creation, store the generated tokens
ownership
is a mapping that, given the ID of a token, it gives us who owns. We could use the list above to get the owner but a mapping would save us computing time and provide a backup proof of ownership
Token market
The buying and selling of tokens will happen internally on the contract. For this, we need to store the balance of users, so when they sell a token their balance increases, they will, later, be able to withdraw their balance.
mapping(address=>uint) public balance; //maps user balance
Token creation
The way this contract is designed, anyone can create its own token and set the name and price. Upon creation, the token is assigned to the creator.
function createToken(string memory _name, uint value) public{//receives name and price of the token
tokens.push(ourToken(currentId, _name, value, msg.sender)); //creates token and adds to the array
ownership[currentId]=msg.sender; //sets creator as owner
currentId++; //sets the ID for the next token
}
By saying ourToken(currenctId, _name, value, msg.sender)
we are, effectively, setting the variables defined in the struct
previously, so
ourToken(1, "igor", 10, 0xca35b7d915458ef540ade6068dfe2f44e8fa733c)
would generate the struct
ourToken{
uint id=1; //ID 1 given automatically from the global variable
string name="igor"; //name given on the builder
uint value=10; //price set to 10wei
address owner=0xca35b7d915458ef540ade6068dfe2f44e8fa733c; //random wallet I have used as example
}
Token getters
It is important that we can see 2 things from the token
- price
- owner
For this, we can use the following functions
function tokenValue(uint _id) public view returns (uint){ //receives a token ID
return tokens[_id].value; //returns the price of given token
}
function tokenOwnership(uint _id) public view returns(address){ //receives token ID
return ownership[_id]; //returns owner of given token
}
The ownership
mapping is useful in this case.
Our contract so far looks like
pragma solidity ^0.5.0;
contract uniqueToken{
uint private currentId=0;
struct ourToken{
uint id;
string name;
uint value;
address owner;
}
ourToken[] tokens;
mapping(uint => address) public ownership;
mapping(address=>uint) public balance;
function tokenValue(uint _id) public view returns (uint){
return tokens[_id].value;
}
function tokenOwnership(uint _id) public view returns(address){
return ownership[_id];
}
function createToken(string memory _name, uint value) public{
tokens.push(ourToken(currentId, _name, value, msg.sender));
ownership[currentId]=msg.sender;
currentId++;
}
}
We still need to implement the functions to be able for someone to buy a token from someone else
Token buy function
The following function will receive a payment from the message sender, store the payment received on the token contract and save the new balance of the seller. It is important, first, to check if the sent value is equal to or greater than the price the owner asks for the token
function buyToken(uint _id) payable public{ //receives the ID of the wanted token
require(msg.value>=tokens[_id].value); //throws error if buyer sends less than asked price
balance[tokens[_id].owner]+=msg.value; //adds credit to seller balance
tokens[_id].owner = msg.sender; //changes ownership of the token itself
ownership[_id] = msg.sender; //changes ownership on the "backup" mapping
}
Changing token price
The contract must allow the owner of the token, and only the owner, to change the price he wants for a said token. The function I designed for it is
function changeValue(uint _id, uint newValue) public{ //receives token ID and new price
require(ownership[_id]==msg.sender); //throws error if the caller is not the owner of the given token
tokens[_id].value=newValue; //sets the new value for the token
}
The require(ownership[_id]==msg.sender);
ensures that only the real owner of the token can change the asked price, else anyone would be able to get any token for free
Check seller balance and withdraw
Once a token is sold the user will receive credit on its balance inside the contract. The ETH (wei) paid for the token will be stored on the contract. We must allow users to check their balances and withdraw the ETH from their sales.
function myBalance() public view returns (uint){
return balance[msg.sender];//shows balance inside the contract from caller
}
function withdraw() public{
uint toSend = balance[msg.sender];
balance[msg.sender]=0;
msg.sender.transfer(toSend); //sends the balance to the caller
}
You could improve the security, but I believe the implemented redundant security measures from the sell function are enough as the withdraw function withdraws all the balance from the user and before it sends it ensures that it cleans the balance first to avoid async exploits.
How the contract looks in the end
pragma solidity ^0.5.0;
contract uniqueToken{
uint private currentId=0;
struct ourToken{
uint id;
string name;
uint value;
address owner;
}
ourToken[] tokens;
mapping(uint => address) public ownership;
mapping(address=>uint) public balance;
function tokenValue(uint _id) public view returns (uint){
return tokens[_id].value;
}
function tokenOwnership(uint _id) public view returns(address){
return ownership[_id];
}
function createToken(string memory _name, uint value) public{
tokens.push(ourToken(currentId, _name, value, msg.sender));
ownership[currentId]=msg.sender;
currentId++;
}
function buyToken(uint _id) payable public{
require(msg.value>=tokens[_id].value);
balance[tokens[_id].owner]+=msg.value;
tokens[_id].owner = msg.sender;
ownership[_id] = msg.sender;
}
function changeValue(uint _id, uint newValue) public{
require(ownership[_id]==msg.sender);
tokens[_id].value=newValue;
}
function myBalance() public view returns (uint){
return balance[msg.sender];
}
function withdraw() public{
uint toSend = balance[msg.sender];
balance[msg.sender]=0;
msg.sender.transfer(balance[msg.sender]);
}
}
Testing in practice
Finally, I am using Remix browser IDE to test how the token works. The prices are set in Wei. For reference
1 ETH = 1000000000000000000 Wei (there are 18 zeroes there)
I have created the token "Igor" and it's ID automatically was set to zero and price of 1000000000000000000 Wei
The account that owns the token is 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c and you can see on the screenshot that it shows the correct ownership
I have switched to the account 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c and if I buy the token with ID 0
The ownership has changed
And the account 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c balance is up on the contract
Withdrawing from the sellers' contract balance
Curriculum
- (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
Thank you for your contribution @igormuba.
After analyzing your tutorial we suggest the following:
We are waiting for more tutorials! Good Work!
Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.
To view those questions and the relevant answers related to your post, click here.
Need help? Chat with us on Discord.
[utopian-moderator]
Hi, thank you for the time spent into reviewing my work. I am sorry that my grammar is not as consistent as I wish it were, I am actively trying to improve.
Posted using Partiko Android
Thank you for your review, @portugalcoin! Keep up the good work!
Hi @igormuba!
Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server
Hey, @igormuba!
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!
Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).
Want to chat? Join us on Discord https://discord.gg/h52nFrV.
Vote for Utopian Witness!