Fungible e Non Fungible Token in pratica

di Andrea Zani, in Blockchain,

Ecco l'ultima parte della serie dedicata alla Blockchain. E' il momento di scrivere qualche nota riguardante i Token, anzi, per essere più precisi, sulle due categorie di Token utilizzabili nelle Blockchain: Fungible Token e Non Fungible Token (NFT). I Fungible Token sono equiparabili alla criptovaluta come il Bitcoin o l'Ethereum, i Non Fungible Token rappresentano invece un oggetto unico digitale (quindi una qualsiasi sequenza di byte) il cui riferimento è salvato nella Blockchain. Sequenza di byte è esagerata come definizione? No, perché un qualsiasi file che può essere un'immagine, un mp3, un file di testo, una sequenza di bytes casuale può essere salvato cone un NFT nella Blockchain.

Per poter salvare queste tipologie di Token è necessario ovviamente utilizzare reti/criptovalute che lo permettano. Per il momento la regina della criptovalute che lo permette è l'Ethereum - come già scritto più volte ci sono molte altre che lo permettono: Solana, l'italiana Algorand etc... E per il loro utilizzo sono necessari gli Smart Contract. Nessuno vieta di creare come Fungible Token la propria criptovaluta da utilizzare come moneta per un proprio servizio. Alcuni videogiochi hanno già cominciato a farne uso come valuta interna al gioco, così come i Non Fungible Token possono essere utilizzati per l'assegnazione di equipaggiamento/skin per il proprio personaggio come ricompensa.

Ethereum ha creato un suo standard interno per la creazione di applicativi - Smart Contract - per i Fungigle Token e i Non Fungile Token: Ethereum Request for Comment (ERC). A questo link la lista degli attuali standard utilizzati di cui tratterò solo quelli di mio interesse:

  • ERC-20: questo standard definisce l'interfaccia per la creazione dei Fungible Token. Il proprio Smart Contract dovrà implementare tutti i metodi qui definiti.
  • ERC-721: questo standard definisce l'interfaccia per i Non Fungible Token. Qui sono i metodi da implementare nel proprio Smart Contract.
  • ERC-1155: questo standard è nato come esigenza di unire di due standard qui sopra. Infatti utilizzando questa interfaccia si potranno utilizzare nel proprio Smart Contract sia i Fungigle Token sia i Non Fungible Token. Inoltre mette a disposizioni metodi appositi per trasferire anche più token a più destinatari allo stesso momento per risparmiare il costo di gas per la transazione. E sarà anche quello che utilizzerò per la demo che mostrerò più avanti.

Ma qual è lo scopo finale di utilizzare questi standard? Inserire questi standard nei propri Smart Contract permette ad esso di essere utilizzato anche da piattaforme diverse. Creando uno Smart Contract che implementa l'interfaccia ERC-721 mi permetterà di inserire i miei Non Fungible Token in siti che permettono la vendita di questi oggetti. Ovviamente nessuno vieta la creazione di uno Smart Contract per la trattazione dei Token senza l'uso di queste interfacce se lo scopo finale è il loro utilizzo, come visto nel mio post precedente, in una custom web application.

Siccome lo scopo finale della demo che mostrerò è l'inserimento dei miei token in un portale pubblico, io utilizzerò questi standard, e volendo implementare entrambi le tipologie di Token utilizzerò l'ERC-1155.

Tipologie di Token della demo

La demo includerà le due tipologie di Token. Innanzitutto creerò il mio coin di esempio per il Fungible Token. Per rappresentarlo userò la seguente semplice immagine:

Per i Non Fungible Token, utilizzerò delle immagini di esempio fatte al volo che non passeranno mai alla storia per l'arte espressa:

Omino in bianco e nero Omino a colori

Ma dove devono essere salvate queste immagini? Nella Blockchain? No. Anche perché l'inserimento di un quantitativo elevato di byte nella Blockchain porterebbe ad un salasso notevole (inoltre non si potrebbe avere un url da richiamare per poi rivedere queste immagini). Questi file devono essere salvati su qualche server sempre accessibile. Questo punto è più importante di quanto si potrebbe inizialmente pensare, perché il salvataggio di file poi inclusi in (N)FT dovranno essere poi per sempre essere disponibili soprattutto se sono già stati utilizzati per una transazione per una vendita o altro - lo so, con le immagini qui sopra non corro questi rischi. Conseguenza: vendere un NFT che un giorno ritornerà l'Http error 404 non è proprio una cosa piacevole per l'acquirente.

Come molte demo già presenti in rete ho deciso di seguire la regola della Blockchain riguardante la decentralizzazione, ed ho salvato queste immagini nel servizio IPFS che permette lo share di file. Senza dover collegare un server alla rete di IPFS per la condivisione dei file qui sopra per la demo, ho utilizzato servizi appositi che permettono la condivisione dei file.

Scrivere lo Smart Contract implementando l'ERC-1155

Se l'aver visto l'interfaccia da implementare per gli standard può spaventare, in verità la loro implementazione è banale, perché in rete si trovano migliaia di esempi a riguardo, ed anzi, è possibile utilizzare wizard online che permettono di creare la struttura di base perfettamente funzionante. Per esempio, a questo url:

E' possibile selezionare una delle interfacce prima esposte ed è possibile aggiungere alcune opzioni per la creazione dei metodi appositi, come Mintable, che permette la creazione di nuovi Token anche dopo la pubblicazione del Smart Contract. Qui si ha un'ottima base su cui lavorerò per la personalizzazione che mi serve.

Innanzitutto la gestione dell'url delle immagini contiene un bug e si deve riscrivere. Quindi volendo utilizzare tipologie di immagini diverse con url non consecutivi (id numerico) anche la gestione della funzione mint (per la creazione dei Token) dovrò riscriverla. Niente di complicato. Modifico la funzione di mint, perché nell'esempio creato dal wizard non sono inserite tutte le opzioni di cui ho bisogno. Ecco la mia versione con altre modifiche:

event mintEvent(address account, uint256 id, uint256 amount, string url);

constructor() ERC1155("") {}
mapping(uint256 => string) private _idUrls;

function setURI(uint256 id, string memory newuri) public onlyOwner {
    require(bytes(_idUrls[id]).length != 0, "Id not present");
    _idUrls[id] = newuri;
    emit URI(newuri, id);
}

mintEvent lo utilizzo solo per la gestione degli eventi per l'utilizzo della funzione mint, che include anche il parametro url. In questa funzione Address è il proprietario del Token creato, l'id è un numero che identifica il token, l'amount è la quantità che può essere tratta per quel token: questo diversifica un Fungible (amount = 0) da un Non Fungible (amount > 1). data può contenere qualsiasi dato e infine in url sarà inserito il link al file JSON in cui saranno inserite le informazioni sul Token (mostrerò poi). Ho creato anche un oggetto mapping (dictionary) per memorizzare gli url di ogni specifico Token.

L'interfaccia ERC1155 espone anche la funzione uri che, dato l'id di un Token, ne ritorna l'url. Avendo io modificato la gestione degli url, ecco l'override della funzione:

function uri(uint256 tokenId) override public view returns(string memory) {
    require(bytes(_idUrls[tokenId]).length != 0, "Id not present");
    return _idUrls[tokenId];
}

Ho inserito per dei miei test anche la funzione per modificare l'url di un Token, ma non è obbligatorio. Ecco il codice completo del mio Smart Contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyToken is ERC1155, Ownable {

    event mintEvent(address account, uint256 id, uint256 amount, string url);

    constructor() ERC1155("") {}
    mapping(uint256 => string) private _idUrls;

    function setURI(uint256 id, string memory newuri) public onlyOwner {
        require(bytes(_idUrls[id]).length != 0, "Id not present");
        _idUrls[id] = newuri;
        emit URI(newuri, id);
    }

    function mint(address account, uint256 id, uint256 amount, bytes memory data, string memory url)
        public
        onlyOwner
    {
        _mint(account, id, amount, data);
        _idUrls[id] = url;
        emit mintEvent(account, id, amount, url);
    }

    function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data, string[] memory urls)
        public
        onlyOwner
    {
        _mintBatch(to, ids, amounts, data);

        for(uint i = 0; i < urls.length; i++) {
            _idUrls[ids[i]] = urls[i];
            emit mintEvent(to, ids[i], amounts[i], urls[i]);
        }
    }

    function uri(uint256 tokenId) override public view returns(string memory) {
        require(bytes(_idUrls[tokenId]).length != 0, "Id not present");
        return _idUrls[tokenId];
    }
}

Usare una vera Blockchain

Finora tutti i miei esempi utilizzavano Blockchain locali di test. Per questa demo dovrà uscire dalle sicure mura di casa e utilizzare una rete Ethernet vera. Fino ad un certo punto... perché continuerò a utilizzare una rete di test ma pubblica: Rinkeby. Il primo passo è configurare questa rete in Metamask. Tralasciando il passaggio dell'installazione di Metamask e della creazione del primo Wallet, questa configurazione non è niente di complicato: dopo aver abilitato le reti di test come spiegato nello scorso post, è sufficiente selezionare questa rete:

Il problema successivo è che qualsiasi operazione nella rete necessita di Ethereum che dovrebbe essere a zero come da immagine. Essendo una rete di test è possibile aggiungere gratuitamente valuta andando in link appositi. Nel caso di Rinkeby al momento della scrittura di questo post un link funzionante è il seguente:

https://faucets.chain.link/rinkeby

Inserito il proprio Address in pochi secondi saranno caricati 0.1 ETH, più che sufficienti per dei test. Se questo link non fosse più disponibile una ricerca in rete dovrebbe portare a nuove pagine dove è possibile richiedere valuta di test.

Ora lo Smart Contract devo inserirlo in questa Blockchain. Lo posso fare da Remix. Avviato questo IDE dallo stesso browser dove si è configurato Metamask dal tab Deploy & run transactions, seleziona dal dropdownlist dell'Environment la voce Inject Web3:

Selezionata questa voce si aprirà la schermata di Metamask ed effettuato il collegamento si dovrà avere un risultato simile a questo:

A questo punto Remix sta usando la rete Rinkeby, e il deploy del Smart Contract dovrebbe andare proprio su quella rete. Cliccando su Deploy ora si aprirà la schermata di Metamask con la richiesta della conferma dell'operazione:

Cliccato su Confirm lo Smart Contract sarà inserito in imperitura memoria nella Blockchain -  l'operazione non è immediata (in Rinkeby i tempi di attesa variano tra i cinque e in 15 secondi). Remix mostrerà anche in link sul sito Etherscan dove sono visibili tutte le operazioni sia nella rete principale di Ethereum (mainnet) sia in quelle di test:

https://rinkeby.etherscan.io/tx/0x0a5df8a1269fbae8c800249ce4d7850fff23595b14b56a4379887dc41a94d332

Ed ecco l'Address del mio Smart Contract:

0x84f3745a9529bab3f6023c56eaa7dfd33da2f0ad

Caricare il Token nella Blockchain

Uno dei più importanti NFT marketplace è OpenSea - famosa anche perché proprio nella sua piattaforma c'è stato un furto milionario di NFT causata da email di phishing. Senza scomodare Smart Contract e altro è possibile inserire le proprie immagini, ma non è lo scopo di questo post. OpenSea mette a disposizione anche l'interfaccia completa verso uno Smart Contract esterno e l'utilizzo di una versione di test di OpenSea dove fare tutti i propri test, e tra le reti supportate c'è proprio Rinkeby.

Nel servizio IPFS ho già caricato le immagini, ma per essere riconosciute devo creare dei file che ne definiscono il contenuto per poterle caricare su OpenSea. Ora creo i file JSON, questo è l'immagine del Token:

{
  "name": "AzToken",
  "description": "Coin AzToken",
  "image": "https://gateway.pinata.cloud/ipfs/QmapzQnLrweNWr3mnqByvs3izAspf5jkNJWYKLa8ASnuRS"
}

Questa file è minimale perché sono disponibili molti altri attributi. Quindi creo lo stesso tipo di file anche per le altre due immagini e carico il tutto ancora nul servizio IPFS. Alla fine ho:

  • AzToken. Link JSON. Link immagine.
  • Omino bianconero. Link JSON. Link immagine.
  • Omino a colori. Link JSON. Link immagine.

Ritornando allo Smart Contract posso inserire l'AzCoint come Token. Seleziono il metodo mint e inserisco i parametri:

in account ho inserito l'Address del mio Wallet (lo stesso usato per la creazione dello Smart Contract. id il valore numerico 1. Volendo poter distribuire 1.000 Token di questo tipo inserisco questo valore in amount. In data lascio il valore vuoto "0x00", e in url inserisco l'url del file JSON contenente le informazioni del mio Token. Cliccato su transact apparirà la richiesta di Metamask:

Dopo qualche secondo apparirà la conferma e posso tornare in OpenSea per creare la mia prima Collection dove inserirò inizialmente l'AzToken grazie al mio Smart Contract.

Aperto nel browser la versione di test di OpenSea sarà possibile eseguire la login utilizzando Metamask. Cliccando sull'icona di Metamask nella toolbar del Browser (o premendo i tasti ALT+SHIFT+M) si potrà eseguire il Sign-In e si avrà a disposizione un proprio profilo e la possibilità di creare una propria collection (per qualsiasi operazione che dovrebbe passare per Metamask, fare sempre molta attenzione eventualmente alla sua icona nella toolbar del browser, che dovrebbe poter richiedere eventuali conferme per proseguire):

Nella schermata successiva selezionare Import existing smart contract:

Quindi qui inserisco l'Address del mio Smart Contract:

Se tutto è stato fatto correttamente e i file prima creati sono visibili pubblicamente, dovrebbe apparire la nuova Collection con un nome casuale, nel mio caso: Unidentified contract - WEyY5weTFf. E sotto il mio token/coin che, cliccato sopra, mostrerà le info:

Con la disponibilità di 1.000 pezzi. Ora i Token potranno essere ceduti dall'owner ma non acquistabili direttamente. Come da immagine, è possibile fare un'offerta - Make offer - che solo l'owner potrà accettare oppure no. Oppure sempre l'owner potrà impostare la possibilità di venderli cliccando su Sell:

In Fees sono inserite le tasse per la vendita di questo Token. Ad OpenSea va il 2.5% mentre io ho impostato il 10% all'autore, percentuale che sarà sempre versata al creatore del Token anche in caso di vendita a terzi. Affidandosi a OpenSea, l'assegnazione dei Token passerà sempre per il mio Smart Contract, ma per la vendita effettiva è bene sapere che saranno i Smart Contract di OpenSea a fare il tutto (visto anche l'assegnazione dei Fee). Per modificare il Fee e personalizzare la pagina e il nome della collection, è sufficiente andare nelle opzioni dove è possibile inserire anche loghi e altre informazioni, compreso il Fee e l'Address su cui si sarà versata questa percentuale:

Caricare il Non Fungible Token nella blockchain

E' il momento di inserire le due opere d'arte nel Smart Contract come NFT. In Remix, sempre nel metodo mint inserisco i dati per l'Omino in bianco e nero:

In Account ho inserito ancora l'Address dell'Owner, in Id il numero consecutivo (2) ed essendo un NFT come Amount ho inserito il valore 1, in modo che il Token sia univoco. Quindi rifaccio lo stesso per l'Omino a colori.

Ora riapro la mia collection in OpenSea, ed ecco sia il Token sia i due NFT (link diretto):

Così come prima, io posso metterli in vendita o, con un account esterno, fare una proposta di acquisto. Sicuramente andranno a ruba.

E se non volessi usare OpenSea?

Forse era meglio farla come premessa. Io non ho alcun interesse a pubblicizzare OpenSea, ma è la piattaforma che permette dei test facilmente in un ambiente uguale a quello reale sulla mainnet di Ethereum. Innanzitutto, per chi non si fosse mai trovato di fronte al tipo di autenticazione con Wallet può farsi un'idea del funzionamento. Come ho cercato di spiegare nel post precedente l'implementazione nelle proprie web application non presenta nulla di complicato. Inoltre come spiegato in quel post e in quello precedente, interfacciandosi direttamente con la Blockchain e avendo creato dei propri Token è possibile assegnarli ai propri utenti con il proprio codice sia client che server side. Si pensi al mondo dei videogiochi dove si possono creare Skin (Token) esclusive per i propri giocatori con l'assegnazione come premio o dopo la vendita. Inoltre con questa metodologia si può anche dimenticare la configurazione di un gateway per il pagamento con carte di credito e permettere ai propri utenti l'acquisto di determinati prodotti con cryptovaluta (ovviamente per un mirato tipo di mercato).

La comunicazione tra OpenSea e il mio Smart Contract funziona correttamente perché ho utilizzato l'interfaccia preposta. Nel mondo degli Smart Contract l'uso delle interfacce permette anche di fixare il problema noto sull'impossibilità di modificare il codice di uno Smart Contract una volta inserito nella Blockchain. Di seguito un semplice esempio a riguardo riprendendo quello presente in Remix. Innanzitutto definisco l'interfaccia, se si conosce altri linguaggi come il C# è di facile comprensione:

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.7;

interface IStorage {
    function store(uint num) external;
    function retrieve() external view returns (uint);
}

E riscrivo il Contract Storage implementando questa interface:

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.7;
import "./IStorage.sol";

contract Storage is IStorage {

    uint private number;

    function store(uint num) override external {
        number = num;
    }

    function retrieve() override external view returns (uint){
        return number;
    }
}

Invece di richiamare direttamente questo Contract, creo una Contract Proxy che lo farà per me:

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.7;
import "./IStorage.sol";

contract ProxyContract {

    IStorage private _storage;
    address private _owner = msg.sender;

    function setContract(address from) external {
        require(msg.sender == _owner, "Only Contract Owner!!!");
        _storage = IStorage(from);
    }

    function retrieveFromContract() external view returns(uint) {
        return _storage.retrieve();
    }

    function storeFromContract(uint num) external {
        _storage.store(num);
    }
}

E' presente il metodo setContract nel quale solo l'owner potrà inserire l'Address di un altro Contract. Nel mio esempio inserirò quello che sarà creato nella Blockchain per il Contract Storage. Ora utilizzando ProxyContract potrò interagire con il Contract Storage richiamadone le funzioni External (nelle interfacce è possibile inserire solo questo tipo di visibilità per i membri del contratto). Il vantaggio a questo punto è evidente nel caso di upgrade/fix di Storage. Una volta pubblicata una nuova versione del Smart Contract, potrò inserire qui il nuovo Address e senza modificare altro, potrò usare la nuova versione. Attenzione, perché gli oggetti memorizzati in Storage saranno ovviamente persi, qui subentra un altro trucco dove il salvataggio di oggetti può essere fatto da un terzo contratto accessibile anch'esso con interfaccia.

Con Etherscan è possibile vedere le chiamate da uno Smart Contract ad un altro nel tab Internal Txns, come visibile qui per la vendita simulata che ho testato del Token creato sopra (in questo caso OpenSea ha richiamato una funzione del mio Smart Contract per l'assegnazione del Token ad un altro utente).

Pubblicare il codice sorgente del Smart Contract

E' possibile inserire il codice sorgente in modo che sia visibile a tutti su Etherscan. Questa prassi è consigliata perché permette la visione del codice e delle operazioni fatte dallo Smart Contract. La procedura è abbastanza semplice, è sufficiente andare nella pagina sul sito di Etherscan dove è presente lo Smart Contract, nel mio caso a questo url:

https://rinkeby.etherscan.io/address/0x84F3745a9529BAb3F6023C56EAa7dfd33da2f0Ad

E nella parte inferiore della pagina al tab Contract, sarà possibile inserirlo senza alcun tipo di autenticazione, ma inserendo poche informazioni e il codice sorgente:

Cliccando su Verify and Publish, sarà aperto un form dove inserire le informazioni:

Importante è utilizzare la corretta versione del compilatore utilizzato. Al passo successivo è necessario inserire il codice sorgente. Qui si deve fare attenzione nel caso il codice del Smart Contract includa con gli import altri file come per il mio esempio. In questo caso si deve inserire tutto il codice sorgente comprensivo di quei file. Il modo più semplice. sempre con l'editor Remix, è abilitare il plugin Flattener che visualizzerà nella barra a sinistra di Remix una nuova icona. Selezionata si potrà cliccare sul pulsante Flatten contracts/nome contract.sol per avere il codice sorgente da copiare in Etherscan:

Se tutto è stato inserito correttamente ora nel tab in Etherscan si potrà vedere il codice sorgente e ulteriori due tab permetteranno di richiamare le funzioni del Smart Contract:

https://rinkeby.etherscan.io/address/0x84F3745a9529BAb3F6023C56EAa7dfd33da2f0Ad#code

Per l'ultima volta: ma quanto mi costi?

E' il momento di pagare il conto. Quando mi è costerebbe pubblicare questo Smart Contract se avessi utilizzato la mainnet di Ethereum?

  • Gas usato per la pubblicazione: 3.159.361
  • Prezzo per unità di Gas: 0.000000059 Ether
  • Prodotto Gas x unita di Gas: 0.186402299 Ether
  • Convertito in EUR: ¤ 575.00

Inserimento di un Token direttamente nel Smart Contract:

  • Gas usato per la pubblicazione: 124.539
  • Prezzo per unità di Gas: 0.000000059 Ether
  • Prodotto Gas x unita di Gas: 0.007347801 Ether
  • Convertito in EUR: ¤ 22.67

Il costo della vendita direttamente da OpenSea dipende dal prezzo di vendita ed è molto più economico. Tralascio commenti personali. Il range del prezzo delle unità di Gas è molto variabile, ed è meglio attendere che sia inferiore all'esempio qui sopra (59 Gwei).

Conclusioni

Alla fine di questo post, dunque, gli NFT sono fuffa oppure no? Allo stato attuale oltre al puro collezionismo per speculazione non sembra esserci molto. Possono essere utilizzati per scopi più nobili come in certi siti o giochi per l'assegnazione controllata di gadget e simili. Inoltre il possesso di un NFT non garantisce alcun diritto reale sui byte acquistati. Non c'è tuttora nessuna legge, o futuro piano, perché questi possano diventare un legale certificato di possesso. Attualmente la gestione dei diritti d'autore per contenuti digitali potrebbe avere una svolta importante se dovessero adottare gli NFT e diventassero quindi lo strumento principale per quel tipo di prodotto. Per ora sembra che non se ne parli nemmeno di questa cosa.

In questa serie di post dedicati alla Blockchain, al fantomatico Web3 e Smart Contract ho solo voluto scrivere mie annotazioni e pensieri del tutto personali. Ho tralasciato tematiche e altri aspetti interessanti di questo mondo come gli Oracoli (Oracles) per gli Smart Contract, che permettono la ricezione dei dati in tempo reale di qualsiasi tipo. Inoltre Opera, il noto browser, ha creato una propria versione di browser per la Blockchain. Per rispondere alla domanda di una persona che mi ha chiesto se ne valeva la pena di imparare il linguaggio Solidity per gli Smart Contract quando a breve, a Giugno, la rete di Ethereum dovrebbe passare alla versione 2, rispondo che al momento non c'è alternativa: era stato promesso un nuovo linguaggio di programmazione più evoluto, Ewasm (Ethereum WebAssembly) che prometteva prestazioni molto più elevate ed un maggior risparmio di Gas, ma dalle ultime news sembra che sia stato rinviato per varie problematiche non ancora risolte e tempi di sviluppo più lunghi del previsto.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Nella stessa categoria
I più letti del mese