(Part 14) Ethereum Solidity - Token Uniqueness, Non-Fungibility And Transacions With Unique Tokens(PT 14)

in #utopian-io5 years ago

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
image.png

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
image.png
image.png

Withdrawing from the sellers' contract balance

image.png

Curriculum

Beneficiaries

This post has as beneficiaries
@utopian.pay with 5%
using the SteemPeak beneficiary tool
image.png

Sort:  

Thank you for your contribution @igormuba.
After analyzing your tutorial we suggest the following:

  • It still has some problems with punctuation in the text.

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!

Coin Marketplace

STEEM 0.25
TRX 0.11
JST 0.032
BTC 63706.21
ETH 3073.80
USDT 1.00
SBD 3.76