以太坊和ipfs结合使用的小demo

思路很简单,文件存在ipfs,然后对应的QmHash存在合约里。主要试试ipfs的api。
操作系统:centos 7.6

  1. 创建项目目录,并初始化
mkdir eth-ipfs
cd eth-ipfs
truffle unbox react
  1. 修改合约
vim contracts/SimpleStorage.sol
pragma solidity ^0.5.0; 

contract SimpleStorage {
        string storedData;

        function set(string memory x) public {
                storedData = x;
        }   

        function get() public view returns(string memory) {
                return storedData;
        }   
}
  1. 修改truffle-config.js
vim truffle-config.js
const path = require("path");
const HDWalletProvider = require("truffle-hdwallet-provider")

module.exports = { 
        // See <http://truffleframework.com/docs/advanced/configuration>
        // to customize your Truffle configuration!
        contracts_build_directory: path.join(__dirname, "client/src/contracts"),

        networks: {
                ropsten: {
                        provider: function() {
                                const mnemonic = 'inject stock easy learn repair fringe damage crawl cruise junior enable remember';
                                const endpoint = 'https://ropsten.infura.io/v3/ba7794faf4cd4b27894283a1ded74631';
                                return new HDWalletProvider(mnemonic, endpoint);
                        },  
                        network_id: '3',
                },  
                test: {
                        provider: function() {
                                const mnemonic = 'improve ripple network feature legal holiday always awkward seek enforce quick drip';
                                const endpoint = 'http://127.0.0.1:8545/';
                                return new HDWalletProvider(mnemonic, endpoint);
                        },  
                        network_id: '*',
                },  
        }   
};
  1. 安装 truffle-hdwallet-provider
npm init
npm install truffle-hdwallet-provider --save
  1. 编译合约
truffle compile
  1. 部署合约
truffle migrate --network ropsten

输出结果

⚠️  Important ⚠️
If you're using an HDWalletProvider, it must be Web3 1.0 enabled or your migration will hang.


Starting migrations...
======================
> Network name:    'ropsten'
> Network id:      3
> Block gas limit: 8000029


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > transaction hash:    0x23e9ffa98251462a76add9449bc63ca497eca827e68d31d665df3b85cbc92d27
   > Blocks: 2            Seconds: 13
   > contract address:    0x5Cb1ea6D50E55eb62D7B121817321b5B5B7c8ca2
   > account:             0xcf961578df24ECB997929dbA39F6a05bA14c6bAd
   > balance:             14.23019043199999
   > gas used:            284908
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00569816 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00569816 ETH


2_deploy_contracts.js
=====================

   Deploying 'SimpleStorage'
   -------------------------
   > transaction hash:    0xa2b7fffbb9b18f4df0ef9f0da6eb87a4fe90312d1712a78db7b552545112f570
   > Blocks: 2            Seconds: 53
   > contract address:    0x5aDAB0baee0b5d4e22401F7c75eBACB7CED4ed71
   > account:             0xcf961578df24ECB997929dbA39F6a05bA14c6bAd
   > balance:             14.22392959199999
   > gas used:            271008
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00542016 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00542016 ETH


Summary
=======
> Total deployments:   2
> Final cost:          0.01111832 ETH
  1. 修改App.js
cd client
vim src/App.js

App.js

import React, { Component } from "react";                                                                                                                   
import SimpleStorageContract from "./contracts/SimpleStorage.json";
import getWeb3 from "./utils/getWeb3";
import ipfsAPI from "ipfs-api";

import "./App.css";

const ipfs = ipfsAPI('localhost', '5001', {protocol: 'http'});

let saveImageOnIpfs = (reader) => {
        return new Promise(function (resolve, reject) {
                const buffer = Buffer.from(reader.result);
                ipfs.add(buffer).then((response) => {
                        console.log(response);
                        resolve(response[0].hash);
                }).catch((err) => {
                        console.error(err);
                        reject(err);
                })  
        })  
}

class App extends Component {
        state = { 
                web3: null,
                accounts: null,
                contract: null,
                hash: "", 
                writeOK: false,
                response: "", 
        };  

        componentDidMount = async () => {
                try {
                        const web3 = await getWeb3();
                        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,
                        );  
                        this.setState({ web3, accounts, contract: instance }); 
                } catch (error) {
                        alert(
                                `Failed to load web3, accounts, or contract. Check console for details.`,
                        );  
                        console.error(error);
                }   
        };  

        upload = async (info) => {
                console.log("info", info)
                let reader = new FileReader()
                reader.readAsArrayBuffer(info)
                console.log("reader", reader)
                console.log("reader.result", reader.result) //null
                reader.onloadend = () => {
                        console.log("reader", reader)
                        console.log("reader.result", reader.result)
                        saveImageOnIpfs(reader).then((hash) => {
                                console.log("hash", hash)
                                this.setState({hash})
                        })
                }
        };

        saveHashToEth = async () => {
                let {contract, hash, accounts} = this.state;
                try {
                        await contract.methods.set(hash).send({from: accounts[0]});
                        console.log('writeOK:', true)
                        this.setState({writeOK: true})
                }
                catch(e) {
                        console.log(e)
                        this.setState({writeOK: false})
                        console.log('writeOK :', false)
                }
        }

        getHashFromEth = async () => {
                let {contract} = this.state
                try {
                        let response = await contract.methods.get().call();
                        console.log('response:', response)
                        this.setState({response})
                }
                catch (e) {
                        console.log(e)
                }
        }

        render() {
                if (!this.state.web3) {
                        return <div>Loading Web3, accounts, and contract...</div>;
                }
                let {hash, writeOK, response} = this.state
                return (
                        <div>
                                <h2>请上传图片</h2>
                                <div>
                                        <input type='file' ref="fileid"/>
                                        <button onClick={() => this.upload(this.refs.fileid.files[0])}>点击我上传到ipfs
                                        </button>
                                        {
                                                hash && <h2>图片已经上传到ipfs: {hash}</h2>
                                        }
                                        {
                                                hash && <button onClick={() => this.saveHashToEth()}>点击我上传到以太坊</button>
                                        }
                                        {
                                                writeOK && <button onClick={() => this.getHashFromEth()}>点击我获取图片</button>
                                        }
                                        {
                                                response &&
                                                <div>
                                                        浏览器访问结果:{"http://localhost:8080/ipfs/" + response}                                           
                                                        <img src={"http://localhost:8080/ipfs/" + response}/>
                                                </div>
                                        }
                                </div>
                        </div>
                );
        }
}

export default App;

js api的文档
https://github.com/dn3010/js-ipfs-api

  1. 启动ipfs节点
ipfs daemon
  1. 启动项目
npm run start
  1. 测试项目
    以太坊和ipfs结合使用的小demo

发现 ‘点击我上传到ipfs’有报错,错误内容

localhost/:1 Access to fetch at ‘http://localhost:5001/api/v0/add?stream-channels=truefrom origin ‘http://localhost:3000’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
localhost/:1 Uncaught (in promise) TypeError: Failed to fetch

查询后得知要对CORS进行配置。

In a web browser IPFS API (either browserified or CDN-based) might encounter an error saying that the origin is not allowed. This would be a CORS ("Cross Origin Resource Sharing") failure: IPFS servers are designed to reject requests from unknown domains by default. You can whitelist the domain that you are calling from by changing your ipfs config like this:
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin "[\"http://example.com\"]"
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials "[\"true\"]"
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods "[\"PUT\", \"POST\", \"GET\"]"

关别ipfs节点,然后输入下面配置,然后再启动节点

ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin "[\"*\"]"
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials "[\"true\"]"
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods "[\"PUT\", \"POST\", \"GET\"]"
  1. 再测试
    以太坊和ipfs结合使用的小demo