React + IPFS + Ethereum で簡単なイメージアップローダーを作成する

#はじめに

React + IPFS + Ethereumで簡単なイメージアップローダーを作成します。
基本的なことは省いて要所だけを書いてるのでつずいたところがあればコメントください！

# 1. 環境構築

簡単に環境設定をしてくれる、truffleが提供する[react unbox](https://www.trufflesuite.com/boxes/react)を利用します。

```
$ mkdir ipfs_image_uploader
$ cd ipfs_image_uploader
$ truffle unbox react
```

Ganacheをインストール（ https://www.trufflesuite.com/ganache ）して起動します。
そしてローカルのブロックチェーンにコントラクトをデプロイします

```
$ truffle compile
$ truffle migrate　--reset 
```

`react unbox`ではフロントエンドは`client`ディレクトリにあります。

```
$ cd client
$ npm start
```

このような画面が開いたら`react unbox`での環境設定は成功です。

![スクリーンショット 2019-06-25 16.16.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342028/5c1cdf92-af2e-6c10-ec13-dfe5e8f8ebf2.png)

#2. コントラクト

`SimpleStorage.sol`のコントラクトを下記のように書き換えます。
簡単な説明をコメントアウトで書きました

```js
//SimpleStore.sol
pragma solidity ^0.5.0;

contract SimpleStorage {
  string ipfsHash;

　//Ipfsいう構造体を作り、その中にはipfsのHash値が入ります
  struct Ipfs {
    string _ipfsHash;
  }

  //上記で作成したIpfs構造体を配列として格納できるIpfsHashを作ります。
  Ipfs[] public IpfsHash;　

　// set関数でフロントからipfsのハッシュ値が渡されます
  function set(string memory _ipfsHash) public {
    ipfsHash = _ipfsHash;
　　 //Ipfs(ipfsHash)で渡されたHash値こ構造体に格納し、構造体の配列であるIpfsHashにpushします
    IpfsHash.push(Ipfs(ipfsHash));
  }
　
　//ipfsのHash値を取り出す関数
  function get() public view returns (string memory) {
    return ipfsHash;
  }

　//構造体の配列の大きさを取得する関数。フロントで使用します
  function arraylength() public view returns (uint) {
    return IpfsHash.length;
  }
}
```

コントラクトを書き終えたらサイドデプロイします。

```
$ truffle migrate　--reset 
```

# 3. IPFS

インスタンスを作成、ipfsファイルを読めるようにするJSライブラリをインストールします。

```
$ npm install ipfs-api
```

インストールが終了したら`ipfs.js`をApp.jsと同じディレクトリに作成し、このようなコードを書きます。

```ipfs.js
const IPFS = require('ipfs-api');
const ipfs = new IPFS({host:'ipfs.infura.io', port: 5001, protocol: 'https'});

export default ipfs;
```

INFURAを利用することによりIPFSのノードに自動的に繋げてくれるようになるそうです。
これでIPFSに繋がるようになりました。

# 4.フロント
`App.js`を以下のように書き換えます。

```App.js
import React, { Component } from "react";
import SimpleStorageContract from "./contracts/SimpleStorage.json";
import getWeb3 from "./utils/getWeb3";
import ipfs from "./ipfs.js";
import "./App.css";

class App extends Component {

  constructor(props) {
    super(props) 
    this.state= {
      web3: null, 
      accounts: null, 
      contract: null,
      buffer: null,
      ipfsHash: [],
      hoge: '',
    }
    //関数を使えるようにする
    this.captureFile = this.captureFile.bind(this);
    this.onSubmit = this.onSubmit.bind(this); 
    this.loadIpfsHash = this.loadIpfsHash.bind(this)
  } 

  componentWillMount = async () => {
    try {
      // Get network provider and web3 instance.
      const web3 = await getWeb3();
      // Web3でユーザーのアドレスを取得
      const accounts = await web3.eth.getAccounts();
      // コントラクト情報をブロックチェーン上から取得
      const networkId = await web3.eth.net.getId();
      const deployedNetwork = SimpleStorageContract.networks[networkId];
　　　 //コントラクトを定義する
      const instance = new web3.eth.Contract(
        SimpleStorageContract.abi,
        deployedNetwork && deployedNetwork.address,
      );
      //Stateにweb3, accounts, contractをセットする
      this.setState({ web3, accounts, contract: instance }, this.runExample);
    } catch (error) {
      alert(`Failed to load web3, accounts, or contract. Check console for details.`,);
      console.error(error);
    }
    await this.loadIpfsHash();
  };

  loadIpfsHash = async () => {
    const length = await this.state.contract.methods.arraylength().call()
    for (var i = 0; i <= length; i++) {
      const ipfsHashs = await this.state.contract.methods.IpfsHash(i).call()
      this.setState({
        ipfsHash: [...this.state.ipfsHash, ipfsHashs]
      })
    }
  }

  captureFile(event) {
    console.log('File loader ...')
    event.preventDefault()
    //fileにアクセスする
    const file = event.target.files[0]
    //fileを読み込む
    const reader = new window.FileReader()
    //fileをipfsにアクセスできるArrayに追加する
    reader.readAsArrayBuffer(file)
    reader.onloadend = () => {
      //結果をBufferに入れ,ipfsにアクセスできるようにする
      this.setState({ buffer: Buffer(reader.result) })
      console.log('buffer', this.state.buffer)
    }
  }

  onSubmit = async (event) => {
    console.log('on Submit ...')
    //Submit時にリロードしなくなる
    event.preventDefault()
    //ipfsにファイルを追加
    ipfs.files.add(this.state.buffer, async (error, result) => {
      if(error) {
        console.error(error)
        return
      }
      //ブロックチェーンにipfsHashを書きこむ
      this.state.contract.methods.set(result[0].hash).send({ from: this.state.accounts[0] })
      //iphsHashの値をアップデートする
      return this.loadIpfsHash();
    }) 
  }

  render() {
    if (!this.state.web3) {
      return <div>Loading Web3, accounts, and contract...</div>;
    }
    return (
      <div className="App">
        <h1>Your Image</h1>
        <p>This image is stored on IPFS & The Ethereum Blockchain!</p>
        {this.state.ipfsHash.map((hash, key) => {
          return(
            <div key={key}>
              <img src= {`https://ipfs.io/ipfs/${hash}`} alt=""></img>
            </div>
          )
        })}
        <h2>Upload image</h2>
        <form onSubmit={this.onSubmit} >
          <input type="file" onChange={this.captureFile} />
          <input type="submit" />
        </form>
      </div>
    );
  }
}

export default App;

```

## captureFile()
`captureFile()`の関数で、アップロードしたデータをIPFSに追加できるようにしています。

## onSubmit()
`onSubmit()`の関数でIPFSとブロックチェーンにそれぞれ書き込んでいます。
`ipfs.files.add（）`でIPFSにアップロードしたファイルを追加しています。
そして`this.state.contract.methods.set(result[0].hash)`にてIPFSのハッシュ値をブロックチェーンに書き込んでいます。

# 5. 終わりに

意外と簡単にIPFSを使うことができました。
何かわからないこと、また間違い等ありましたらコメントいただけると幸いです。

GitHubは[こちら](https://github.com/Yosuke-Aramaki/ipfs_image_uploader.git)です

#参考サイト
[Dapp University](https://www.youtube.com/watch?v=SkMH0WeRYtg)
基本的にはこのサイトもとにして、少し付け加える形で作成しました。

[bind](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)
[IPFS Github](https://github.com/ipfs/ipfs)
[Node.js Buffer Module](https://www.w3schools.com/nodejs/ref_buffer.asp)
[File Reader](https://developer.mozilla.org/en-US/docs/Web/API/FileReader)