以太坊和ipfs结合使用的小demo
思路很简单,文件存在ipfs,然后对应的QmHash存在合约里。主要试试ipfs的api。
操作系统:centos 7.6
- 创建项目目录,并初始化
mkdir eth-ipfs
cd eth-ipfs
truffle unbox react
- 修改合约
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;
}
}
- 修改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: '*',
},
}
};
- 安装 truffle-hdwallet-provider
npm init
npm install truffle-hdwallet-provider --save
- 编译合约
truffle compile
- 部署合约
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
- 修改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
- 启动ipfs节点
ipfs daemon
- 启动项目
npm run start
- 测试项目
发现 ‘点击我上传到ipfs’有报错,错误内容
localhost/:1 Access to fetch at ‘http://localhost:5001/api/v0/add?stream-channels=true’ from 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\"]"
- 再测试