WEB3DEV

Cover image for Construir um DApp com Ethereum + Sistema de Arquivos InterPlanetário (IPFS)+ React.js.
Fatima Lima
Fatima Lima

Posted on

Construir um DApp com Ethereum + Sistema de Arquivos InterPlanetário (IPFS)+ React.js.

POR QUE ESTAMOS CONSTRUINDO ISSO?

É proibitivamente caro armazenar uma grande quantidade de dados sobre a blockchain Ethereum. De acordo com o yellow paper da Ethereum, gastam-se aproximadamente 20.0000 gas para 256bit/8 bytes (1 palavra). Com base em 28/02/2018 os preços do gas são 4 gwei/gas. See: https://ethgasstation.info para preços atuais.

20,000 gas por transação de 8 bytes x 4 gwei/gas = 80,000 gwei para 8 bytes.

80,000 gwei por 8 bytes. x 1000bytes/8 = 10,000,000 gwei/kB = .01 Ether

.01 Ether/kB x 1000kB = 10 Ether para armazenar 1Mb a $860/ether = $8600.00! Custaria $8,600,000.00 para armazenar um arquivo de 1 GB na blockchain Ethereum!

Para armazenar um PDF com 38 páginas do yellow paper da Ethereum (520Kb) = $4472 USD. Veja: http://eth-converter.com/ para conversões.

Se pudéssemos armazenar apenas alguns Kb de dados na blockchain, então ainda teríamos que contar com um servidor centralizado para armazenar dados. Felizmente, está disponível uma solução para armazenar dados em rede descentralizada chamada Sistema de Arquivos InterPlanetário ("IPFS"). Visite: https://ipfs.io/ para ler mais. Ao procurar por arquivos no IPFS, você está pedindo à rede para encontrar nós (nodes) que armazenem o conteúdo por trás de um único hash. A partir do próprio site do IPFS:

“IPFS e a Blockchain são uma combinação perfeita! Você pode endereçar grandes quantidades de dados com IPFS, e colocar os links IPFS imutáveis e permanentes em uma transação da blockchain. Este marca o tempo do seu conteúdo e o protege, sem a necessidade de colocar os dados na própria cadeia.”

O QUE ESTAMOS CONSTRUINDO?

Um simples DApp para carregar um documento no IPFS e depois armazenar o hash do IPFS na blockchain Ethereum. Uma vez que o número do hash do IPFS é enviado para a blockchain Ethereum, o usuário receberá um recibo da transação. Usaremos a estrutura Create-React-App para fazer um front-end. Este Dapp funciona com qualquer usuário que tenha a MetaMask instalada em seu navegador.

COMO CONSTRUIR:

Antes de começarmos, estas são as premissas que eu fiz:

Premissas sobre o Usuário: O usuário tem a Metamask instalada para interagir com o DApp.

Premissas sobre você/Desenvolvedor: Você tem alguma familiaridade com JavaScript e/ou React.js, bem como com Node.js/NPM. Nota importante: certifique-se de que você está executando uma versão atual do Node e NPM. Para este tutorial estou executando: Node v8.9.4 e NPM 5.6.0.

Instalar MetaMask. Se ainda não tem a MetaMask instalada, por favor vá para https://metamask.io/ e siga as instruções. Este DApp assumirá que o usuário tem a MetaMask instalada.

**Crie um diretório para armazenar nosso DApp. **Para este tutorial, vou chamá-lo de "eth-ipfs".

Instale Create-React-App e outras dependências usando NPM. Use o NPM e instale o seguinte:

npm i create-react-app
npm install react-bootstrap
npm install fs-extra
npm install ipfs-api
npm install web3@^1.0.0-beta.26
Enter fullscreen mode Exit fullscreen mode

Entre no diretório "eth-ipfs", digite "npm start" e o Create-React-App deverá renderizar automaticamente em http://localhost:3000/.

Nota: se você não utilizou o create-react-app até agora, você pode ter que instalá-lo globalmente primeiro

1. sudo npm install -g create-react-app

ou npm install -g create-react-app

2. create-react-app eth-ipfs

3. cd into eth-ipfs and “npm start”

Implante o seguinte código Solidity usando o Remix na testnet Rinkeby. Veja https://remix.ethereum.org. Você precisará de algum ether de teste Rinkeby, se você ainda não tiver algum, vá à faucet Rinkeby para obter ether de teste gratuitamente. https://www.rinkeby.io/#faucet .

pragma solidity ^0.4.17;
contract Contract {
string ipfsHash;

function sendHash(string x) public {
  ipfsHash = x;
}
function getHash() public view returns (string x) {
  return ipfsHash;
}}
Enter fullscreen mode Exit fullscreen mode

Salve o endereço do contrato onde ele é implantado e a Interface Binária de Aplicação (ABI). Para obter a ABI do contrato, no Remix vá para o seu endereço contratual:

Clique na aba "Compile", depois clique no botão cinza "Details".

Image description

Isto abrirá a janela Detalhes. Copie a "ABI", que é um arquivo JSON.

Image description

Pessoalmente, prefiro colocar a ABI JSON em um formatador, tal como https://jsonformatter.org e verificar se é válida antes de utilizá-la em meu código javascript. Guarde o endereço do contrato e a ABI para mais tarde.

Dentro de nosso diretório "eth-ipfs/src", crie os seguintes arquivos: web3.js, ipfs.js, e storehash.js. O grosso do nosso código estará em App.js.

Image description

web3.js

Queremos usar a versão 1.0 do web3.js, porque ao contrário da versão 0.20, a 1.0 nos permite usar a async e await em vez de promises em nosso javascript. No momento, o fornecedor padrão da MetaMask do web3.js é a versão 0.20. Portanto, vamos nos certificar de que substituímos a versão padrão da Metamask web3 versão 0.20, e usar nossa 1.0. Aqui está o código:

    //substitui a metamask v0.2 por nossa versão 1.0.
    //1.0 nos permite usar async e await ao invés de promisesimport Web3 from 'web3';
    const web3 = new Web3(window.web3.currentProvider);export default web3;
Enter fullscreen mode Exit fullscreen mode

storehash.js

Para que o web3.js tenha acesso ao contrato que enviamos à testnet Rinkeby da Ethereum anteriormente, você precisará do seguinte: 1) endereço do contrato e 2) a ABI do contrato. Certifique-se de importar seu arquivo web3.js de seu diretório /src também. Aqui está o código:

    import web3 from './web3';//acessar nossa cópia local para o contrato implantado na testnet rinkeby
    //usar o seu prórpio endereço de contrato addressconst = '0xb84b12e953f5bcf01b05f926728e855f2d4a67a9';//usar a ABI da sua contractconst abi = [
     {
       "constant": true,
       "inputs": [],
       "name": "getHash",
       "outputs": [
         {
           "name": "x",
           "type": "string"
         }
       ],
       "payable": false,
       "stateMutability": "view",
       "type": "function"
     },
     {
       "constant": false,
       "inputs": [
         {
           "name": "x",
           "type": "string"
         }
       ],
       "name": "sendHash",
       "outputs": [],
       "payable": false,
       "stateMutability": "nonpayable",
       "type": "function"
     }
    ]export default new web3.eth.Contract(abi, address);
Enter fullscreen mode Exit fullscreen mode

ipfs.js

Neste tutorial, nós executaremos o nó ipfs.infura.io para conectar com o IPFS ao invés de executar um daemon IPFS no nosso próprio computador. Nos comentários de código, você também pode optar por executar seu próprio daemon IPFS se você instalar o IPFS como uma dependência global. Visite https://infura.io/para mais informações sobre o uso dos nós deles. Eis aqui o código:

    //usar o nó infura.io, caso contrário, o ipfs exige que você execute um //daemon no seu computador/servidor.const IPFS = require('ipfs-api');
    const ipfs = new IPFS({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' });//execute com o  daemon local
    // const ipfsApi = require('ipfs-api');
    // const ipfs = new ipfsApi('localhost', '5001', {protocol:'http'});export default ipfs;
Enter fullscreen mode Exit fullscreen mode

App.js

Esta é a ordem de operações no App.js

  1. Configurar as variáveis de estado.
  2. Obter o arquivo do usuário.
  3. Converter o arquivo em um buffer.
  4. Enviar o arquivo que está no buffer para o IPFS
  5. IPFS retorna um hash.
  6. Obter o endereço Ethereum da Metamask do usuário
  7. Enviar o IPFS para armazenagem na Ethereum.
  8. Usando a MetaMask, o usuário confirmará a transação à Ethereum.
  9. O contrato Ethereum vai retornar um número hash da transação.
  10. O número hash da transação pode ser usado para gerar o recebimento da transação com informações como a quantia de gas utilizada e o número do bloco.
  11. As informações do IPFS e da Ethereum se tornarão disponíveis em uma tabela usando o Bootstrap para CSS. NOTA: Eu não criei uma variável do tipo isLoading para o estado de renderização automática para as variáveis blockNumber e gasUsed. Portanto, por enquanto, você terá que clicar novamente ou implementar seu próprio ícone de carregamento. Uma tabela descrevendo as variáveis e funções, seguida do próprio código, está abaixo:

Image description

Image description

Image description

Finalmente, aqui está o código App.js:

import React, { Component } from 'react';
//importe logo de './logo.svg';
import './App.css';
import web3 from './web3';
import ipfs from './ipfs';
import storehash from './storehash';class App extends Component {

   state = {
     ipfsHash:null,
     buffer:'',
     ethAddress:'',
     blockNumber:'',
     transactionHash:'',
     gasUsed:'',
     txReceipt: ''  
   };captureFile =(event) => {
       event.stopPropagation()
       event.preventDefault()
       const file = event.target.files[0]
       let reader = new window.FileReader()
       reader.readAsArrayBuffer(file)
       reader.onloadend = () => this.convertToBuffer(reader)   
     };
convertToBuffer = async(reader) => {
     //O arquivo é convertido em um buffer para ser carregado no IPFS 
       const buffer = await Buffer.from(reader.result);
     //defina o buffer -usando a sintaxe es6
       this.setState({buffer});
   };onClick = async () => {try{
       this.setState({blockNumber:"waiting.."});
       this.setState({gasUsed:"waiting..."});//obter o recibo de transação no console ao clicar
//Veja: https://web3js.readthedocs.io/en/1.0/web3-eth.html#gettransactionreceiptawait web3.eth.getTransactionReceipt(this.state.transactionHash, (err, txReceipt)=>{
         console.log(err,txReceipt);
         this.setState({txReceipt});
       }); //esperar por getTransactionReceiptawait this.setState({blockNumber: this.state.txReceipt.blockNumber});
       await this.setState({gasUsed: this.state.txReceipt.gasUsed});   
     } //try
   catch(error){
       console.log(error);
     } //catch
 } //onClickonSubmit = async (event) => {
     event.preventDefault();     //trazer o endereço da conta da metamask do usuário
     const accounts = await web3.eth.getAccounts();


     console.log('Enviando da conta Metamask: ' + accounts[0]);    //obter o endereço do contrato de storehash.js
     const ethAddress= await storehash.options.address;
     this.setState({ethAddress});    //salve o documento no IPFS, retorne seu hash# e defina o hash# para state
   //https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#add       await ipfs.add(this.state.buffer, (err, ipfsHash) => {
       console.log(err,ipfsHash);
       //setState definindo ipfsHash para ipfsHash[0].hash
       this.setState({ ipfsHash:ipfsHash[0].hash });   // chame o método de contrato Ethereum "sendHash" e envie o hash IPFS para o contrato ethereum
 //retorne o hash da transação do contrato ethereum contract
//veja isto https://web3js.readthedocs.io/en/1.0/web3-eth-contract.html#methods-mymethod-send


       storehash.methods.sendHash(this.state.ipfsHash).send({
         from: accounts[0]
       }, (error, transactionHash) => {
         console.log(transactionHash);
         this.setState({transactionHash});
       }); //storehash
     }) //await ipfs.add
   }; //onSubmitrender() {


     return (
       <div className="App">
         <header className="App-header">
           <h1> Ethereum e IPFS com Create React App</h1>
         </header>


         <hr /><Grid>
         <h3> Escolha o arquivo para enviar ao IPFS </h3>
         <Form onSubmit={this.onSubmit}>
           <input
             type = "file"
             onChange = {this.captureFile}
           />
            <Button
            bsStyle="primary"
            type="submit">
            Send it
            </Button>
         </Form><hr/>
<Button onClick = {this.onClick}> Obter recibo de transação </Button>  <Table bordered responsive>
               <thead>
                 <tr>
                   <th>Categoria de Recibo Tx</th>
                   <th>Valores</th>
                 </tr>
               </thead>


               <tbody>
                 <tr>
                   <td>Hash # IPFS armazenado no contrato Eth</td>
                   <td>{this.state.ipfsHash}</td>
                 </tr>
                 <tr>
                   <td>Endereço do Contrato Address</td>
                   <td>{this.state.ethAddress}</td>
                 </tr>                  <tr>
                   <td>Tx Hash # </td>
                   <td>{this.state.transactionHash}</td>
                 </tr>                  <tr>
                   <td> Número do bloco # </td>
                   <td>{this.state.blockNumber}</td>
                 </tr>                  <tr>
                   <td>Gas Utilizado</td>
                   <td>{this.state.gasUsed}</td>
                 </tr>


               </tbody>
           </Table>
       </Grid>
    </div>
     );
   } //render
} //Appexport default App;
Enter fullscreen mode Exit fullscreen mode

Adicionei um pouco de CSS em src/App.css para parecer mais fácil aos olhos:

/*algum css que adicionei*/
input[type="file"] {
display: inline-block;
}.table {
max-width: 90%;
margin: 10px;
}
.table th {
text-align: center;
}
/*end of my css*/
Enter fullscreen mode Exit fullscreen mode

E acrescente algumas importações para src/index.js:

/*https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-a-stylesheet*/
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/css/bootstrap-theme.css';
Enter fullscreen mode Exit fullscreen mode

É isso aí! O seu DApp deve estar pronto. Portanto, tudo o que você precisa fazer é escolher um arquivo, enviá-lo e obter um recibo da transação. Se você estiver conectado a um nó IPFS através de seu host local:3000, então você deve ser capaz de ver seu arquivo em uma das portas do IPFS. https://gateway.ipfs.io/ipfs/ + seu hash IPFS#.

Por exemplo: https://gateway.ipfs.io/ipfs/QmYjh5NsDc6LwU3394NbB42WpQbGVsueVSBmod5WACvpte

Uma observação sobre IPFS, a menos que seu arquivo seja recolhido por um outro nó ou que você o fixe, o IPFS eventualmente coletará seu arquivo como lixo. Há muito mais sobre isso no site deles.

Links de Referência:

Este artigo foi escrito por Sagar Barapatre e traduzido por Fátima Lima. Seu original pode ser lido aqui.

Top comments (0)