创建项目
1.truffle unbox webpack
创建完成后
app文件夹用来存放 前台页面
contracts 用来存放合约代码
migrations 用来部署项目
node_modules 依赖文件
test 测试
sha3 跟改为 keccak256
keccak256(abi.encodePacked(_amount, _secret));
1通过 truffle framework和Solidity实现合约代码,并将其部署到truffle develop 自带的测试网络中,摒弃在truffle console中自由交互
使用ipfs通过命令安装并与之交互
后端实现完成后,构建web前端与合约喝ipfs进行交互
安装mongodb 并设计数据库结构来存储参评
数据库启动后 实现监听合约事件的nodejs 服务端代码,将请求记录到控制台,然后执行将产品插入数据库
我们将更新我的们的前端,从数据库而不是区块链中查找产品
实现托管合同和相应的前端,参与者可以向买方/卖方发放或者退款
在contracts 目录下创建 EcommerceStore.sol文件
pragma solidity ^0.4.17;
pragma solidity ^0.4.17;
contract EcommerceStroe {
enum ProductStatus{
// 开始买
Open,
//结束
Sold,
//没人买
Unsold
}
//产品
enum ProductCondition{
New,
Used
}
uint public productIndex;
//通过卖家的钱包地址找到卖家的详细信息
mapping(address =>mapping(uint => Product)) stores;
//卖家的钱包地址
mapping(uint => address ) productIdInStore;
//存取投标的相关信息
struct Bid{
address bidder;
uint productId;
uint value;
bool revealed;
}
struct Product{
uint id; // 产品id
string name; // 产品名字
string category;// 分类
string imageLink;//图片的hash
string descLink;//图片描述信息的hash
uint auctionStartTime;//开始竞标的时间
uint auctionEndTime;//竞标结束的时间
uint startPrice;//拍卖价格
address highestBidder;//赢家的钱包地址
uint highestBid;//赢家竞标的价格
uint secondHighestBid;//第二高的这个人的地址
uint totalBids;//一共有多少人参与竞标
ProductStatus status; //产品的状态
ProductCondition condition;// 是新的还是旧的
//存取投标人的信息
mapping(address =>mapping(bytes32 => Bid)) bids;
}
//构造函数
function EcommerceStore() public{
productIndex = 0;
}
/* 添加产品到区块链/
function addProductToStore(string _name, string _category, string _imageLink, string _descLink, uint _auctionStartTime, uint _auctionEndTime, uint _startPrice, uint _productCondition) public {
//断言 其实时间是否小于竞标时间
require(_auctionStartTime < _auctionEndTime);
//产品id直接让他自+1
productIndex += 1;
Product memory product = Product(productIndex, _name, _category, _imageLink, _descLink, _auctionStartTime, _auctionEndTime, _startPrice, 0, 0, 0, 0, ProductStatus.Open, ProductCondition(_productCondition));
stores[msg.sender][productIndex] = product;
productIdInStore[productIndex] = msg.sender;
}
/ 通过产品ID读取产品信息 */
function getProduct(uint _productId)view public returns(uint, string, string, string, string, uint, uint, uint, ProductStatus, ProductCondition) {
Product memory product = stores[productIdInStore[_productId]][_productId];
//元组
return (product.id, product.name, product.category, product.imageLink, product.descLink, product.auctionStartTime, product.auctionEndTime, product.startPrice, product.status, product.condition);
}
//投奔
function bid(uint _productId, bytes32 _bid) payable public returns(bool) {
Product storage product = stores[productIdInStore[_productId]][_productId];
//当前时间必须大于产品开始竞标的时间
require(now >= product.auctionStartTime);
//当前时间必须小于产品结束竞标的时间
require(now <= product.auctionEndTime);
require(msg.value > product.startPrice);
//投奔的时候信息为‘’
require(product.bids[msg.sender][_bid].bidder == 0);
//存储竞标人的信息msg.value 用于迷惑别人的价格
product.bids[msg.sender][_bid] = Bid(msg.sender, _productId, msg.value, false);
//统计投标人的数量
product.totalBids += 1;
return true;
}
//投标公告
function revealBid(uint _productId, string _amount, string _secret) public {
Product storage product = stores[productIdInStore[_productId]][_productId];
require(now > product.auctionEndTime);
bytes32 sealedBid = sha3(_amount,_secret);
//msg.sender 投标人的钱包地址
Bid memory bidInfo = product.bids[msg.sender][sealedBid];
require(bidInfo.bidder > 0);
require(bidInfo.revealed == false);
uint refund;//退款
//将字符串转化成uint 类型
uint amount = stringToUint(_amount);
if (bidInfo.value < amount) {
refund = bidInfo.value;
} else {
if (address(product.highestBidder) == 0) {
product.highestBidder = msg.sender;
product.highestBid = amount;
product.secondHighestBid = product.startPrice;
refund = bidInfo.value - amount;
} else {
if (amount > product.highestBid) {
product.secondHighestBid = product.highestBid;
// 退款
product.highestBidder.transfer(product.highestBid);
product.highestBidder = msg.sender;
product.highestBid = amount;
refund = bidInfo.value - amount;
} else if (amount > product.secondHighestBid) {
product.secondHighestBid = amount;
refund = amount;
} else {
refund = amount;
}
}
if (refund > 0) {
msg.sender.transfer(refund);
//表示公告成功
product.bids[msg.sender][sealedBid].revealed = true;
}
}
}
function highestBidderInfo(uint _productId) view public returns(address, uint, uint) {
Product memory product = stores[productIdInStore[_productId]][_productId];
return (product.highestBidder, product.highestBid, product.secondHighestBid);
}
//查询当前产品投标人的数量
function totalBids(uint _productId) view public returns(uint) {
Product memory product = stores[productIdInStore[_productId]][_productId];
return product.totalBids;
}
// 将字符串转换成uint类型
function stringToUint(string s) pure private returns(uint) {
bytes memory b = bytes(s);
uint result = 0;
for (uint i = 0; i < b.length; i++) {
if (b[i] >= 48 && b[i] <= 57) {
result = result * 10 + (uint(b[i]) - 48);
}
}
return result;
}
}
部署项目
在migrations下 2_deploy_contracts.js 修改代码
var EcommerceStroe = artifacts.require('./EcommerceStroe.sol')
module.exports = function (deployer) {
deployer.deploy(EcommerceStroe)
}
启动命令行到根目录
truffle deveplop
进行编译
compile
部署合约
migrate
添加启始价格
amt_1 =web3.toWei(1,'ether')
获取当前时间
current_time = Math.round(new Date() /1000);
EcommerceStore.deployed().then(function(i){i.addProductToStore('iphone 6s','Cell Phones & Accessories','imagelink','desclink',current_time,current_time +200, amt_1,0).then(function(f) {console.log(f)})});
查看添加的合约
var c
EcommerceStore.deployed().then((i)=>{ c=i});
//查看值
c.productIndex()
查看产品信息
EcommerceStore.deployed().then(function(i){i.getProduct.call(1).then(functino(f){console.log(f)})})
安装依赖包 对文件进行加密
插件 安装
第一种:npm install --save-dev ethereumjs-util
安装依赖包 对文件进行加密
找到package.json
在“webpack-dev-server” 下添加
第二种:"webpack-dev-server":"^2.3.0"
打开控制台
当前目录下 truffle develop
进行编译
compile
进行部署
migrate --reset
产品集成ipfs
找到package.json
在“webpack-dev-server” 下添加
"ipfs-api":"14.1.2"
把ipfs集成到项目里
在app/javascripts
import "../stylesheets/app.css";
import {
default as Web3
} from 'web3';
import {
default as contract
} from 'truffle-contract'
import ecommerce_store_artifacts from '../../build/contracts/EcommerceStore.json'
var EcommerceStore = contract(ecommerce_store_artifacts);
const ipfsAPI = require('ipfs-api');
const ethUtil = require('ethereumjs-util');
const ipfs = ipfsAPI({
host: 'localhost',
port: '5001',
protocol: 'http'
});
window.App = {
start: function() {
var self = this;
EcommerceStore.setProvider(web3.currentProvider);
renderStore();
var reader;
$("#product-image").change(function(event) {
console.log("选择图片");
const file = event.target.files[0]
reader = new window.FileReader()
reader.readAsArrayBuffer(file)
});
$("#add-item-to-store").submit(function(event) {
console.log("保存数据到ipfs和区块链");
const req = $("#add-item-to-store").serialize();
let params = JSON.parse('{"' + req.replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') + '"}');
let decodedParams = {}
Object.keys(params).forEach(function(v) {
decodedParams[v] = decodeURIComponent(decodeURI(params[v]));
});
saveProduct(reader, decodedParams);
event.preventDefault();
});
if ($("#product-details").length > 0) {
console.log("window.location");
console.log(window.location);
console.log("Search Params = " + new URLSearchParams(window.location))
let productId = new URLSearchParams(window.location.search).get('Id');
console.log(productId);
renderProductDetails(productId);
}
$("#bidding").submit(function(event) {
$("#msg").hide();
let amount = $("#bid-amount").val();
let sendAmount = $("#bid-send-amount").val();
let secretText = $("#secret-text").val();
let sealedBid = '0x' + ethUtil.sha3(web3.toWei(amount, 'ether') + secretText).toString('hex');
let productId = $("#product-id").val();
console.log(sealedBid + " for " + productId);
EcommerceStore.deployed().then(function(i) {
i.bid(parseInt(productId), sealedBid, {
value: web3.toWei(sendAmount),
from: web3.eth.accounts[0],
gas: 440000
}).then(
function(f) {
$("#msg").html("Your bid has been successfully submitted!");
$("#msg").show();
console.log(f)
}
)
});
event.preventDefault();
});
$("#revealing").submit(function(event) {
$("#msg").hide();
let amount = $("#actual-amount").val();
let secretText = $("#reveal-secret-text").val();
let productId = $("#product-id").val();
EcommerceStore.deployed().then(function(i) {
i.revealBid(parseInt(productId), web3.toWei(amount).toString(), secretText, {
from: web3.eth.accounts[0],
gas: 440000
}).then(
function(f) {
$("#msg").show();
$("#msg").html("Your bid has been successfully revealed!");
console.log(f)
}
)
});
event.preventDefault();
});
},
};
function renderProductDetails(productId) {
console.log("renderProductDetails");
console.log(productId);
EcommerceStore.deployed().then(function(i) {
i.getProduct(productId).then(function(p) {
console.log("getProduct");
console.log(p[4]);
let content = "";
ipfs.cat(p[4]).then((stream) => {
console.log(stream);
let content = Utf8ArrayToStr(stream);
$("#product-desc").append("<div>" + content + "</div>");
});
$("#product-image").append("<img src='http://localhost:8080/ipfs/" + p[3] + "' width='250px' />");
$("#product-price").html(displayPrice(p[7]));
$("#product-name").html(p[1].name);
$("#product-auction-end").html(displayEndHours(p[6]));
$("#product-id").val(p[0]);
$("#revealing, #bidding").hide();
let currentTime = getCurrentTimeInSeconds();
if (currentTime < p[6]) {
$("#bidding").show();
} else if (currentTime - (60) < p[6]) {
$("#revealing").show();
}
})
})
}
function Utf8ArrayToStr(array) {
var out, i, len, c;
var char2, char3;
out = "";
len = array.length;
i = 0;
while (i < len) {
c = array[i++];
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12:
case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
default:
break;
}
}
return out;
}
function getCurrentTimeInSeconds() {
return Math.round(new Date() / 1000);
}
function displayPrice(amt) {
return 'Ξ' + web3.fromWei(amt, 'ether');
}
function displayEndHours(seconds) {
let current_time = getCurrentTimeInSeconds()
let remaining_seconds = seconds - current_time;
if (remaining_seconds <= 0) {
return "Auction has ended";
}
let days = Math.trunc(remaining_seconds / (24 * 60 * 60));
remaining_seconds -= days * 24 * 60 * 60
let hours = Math.trunc(remaining_seconds / (60 * 60));
remaining_seconds -= hours * 60 * 60
let minutes = Math.trunc(remaining_seconds / 60);
if (days > 0) {
return "Auction ends in " + days + " days, " + hours + ", hours, " + minutes + " minutes";
} else if (hours > 0) {
return "Auction ends in " + hours + " hours, " + minutes + " minutes ";
} else if (minutes > 0) {
return "Auction ends in " + minutes + " minutes ";
} else {
return "Auction ends in " + remaining_seconds + " seconds";
}
}
function saveProduct(reader, decodedParams) {
let imageId, descId;
saveImageOnIpfs(reader).then(function(id) {
imageId = id;
saveTextBlobOnIpfs(decodedParams["product-description"]).then(function(id) {
descId = id;
saveProductToBlockchain(decodedParams, imageId, descId);
})
})
}
function saveProductToBlockchain(params, imageId, descId) {
console.log(params);
let auctionStartTime = Date.parse(params["product-auction-start"]) / 1000;
let auctionEndTime = auctionStartTime + parseInt(params["product-auction-end"]) * 24 * 60 * 60
EcommerceStore.deployed().then(function(i) {
i.addProductToStore(params["product-name"], params["product-category"], imageId, descId, auctionStartTime,
auctionEndTime, web3.toWei(params["product-price"], 'ether'), parseInt(params["product-condition"]), {
from: web3.eth.accounts[0],
gas: 440000
}).then(function(f) {
console.log(f);
("#msg").html("Your product was successfully added to your store!");
})
});
}
function 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);
})
})
}
function saveTextBlobOnIpfs(blob) {
return new Promise(function(resolve, reject) {
const descBuffer = Buffer.from(blob, 'utf-8');
ipfs.add(descBuffer)
.then((response) => {
console.log(response)
resolve(response[0].hash);
}).catch((err) => {
console.error(err)
reject(err);
})
})
}
function renderStore() {
EcommerceStore.deployed().then(function(i) {
i.getProduct.call(1).then(function(p) {
("#product-list").append(buildProduct(p, 2));
});
i.getProduct.call(3).then(function(p) {
("#product-list").append(buildProduct(p, 4));
});
i.getProduct.call(5).then(function(p) {
("#product-list").append(buildProduct(p, 6));
});
i.getProduct.call(7).then(function(p) {
$("#product-list").append(buildProduct(p, 7));
});
});
}
function buildProduct(product, id) {
console.log("buildProduct");
console.log(id);
let node = $("<div/>");
node.addClass("col-sm-3 text-center col-margin-bottom-1");
node.append("<img src='https://ipfs.io/ipfs/" + product[3] + "' width='150px' />");
node.append("<div>" + product[1] + "</div>");
node.append("<div>" + product[2] + "</div>");
node.append("<div>" + new Date(product[5] * 1000) + "</div>");
node.append("<div>" + new Date(product[6] * 1000) + "</div>");
node.append("<div>Ether " + product[7] + "</div>");
node.append("<a href='product.html?Id=" + id + "'class='btn btn-primary'>Show</a>")
return node;
};
window.addEventListener('load', function() {
if (typeof web3 !== 'undefined') {
console.warn("Using web3 detected from external source. If you find that your accounts don't appear or you have 0 MetaCoin, ensure you've configured that source properly. If using MetaMask, see the following link. Feel free to delete this warning. :) http://truffleframework.com/tutorials/truffle-and-metamask")
window.web3 = new Web3(web3.currentProvider);
} else {
console.warn("No web3 detected. Falling back to http://localhost:8545. You should remove this fallback when you deploy live, as it's inherently insecure. Consider switching to Metamask for development. More info here: http://truffleframework.com/tutorials/truffle-and-metamask");
window.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}
App.start();
});
在根目录创建种子文件seed.js
Eutil = require('ethereumjs-util');
EcommerceStore = artifacts.require("./EcommerceStore.sol");
module.exports = function(callback) {
current_time = Math.round(new Date() / 1000);
amt_1 = web3.toWei(1, 'ether');
EcommerceStore.deployed().then(function(i) {i.addProductToStore('iphone 5', 'Cell Phones & Accessories', 'QmNZWJ8dBQxWrk3YzjrHxkVpePaGajqASKCQQJLmaTVSy2', 'QmbLRFj5U6UGTy3o9Zt8jEnVDuAw2GKzvrrv3RED9wyGRk', current_time, current_time + 200, 2amt_1, 0).then(function(f) {console.log(f)})});
EcommerceStore.deployed().then(function(i) {i.addProductToStore('iphone 5s', 'Cell Phones & Accessories', 'QmNZWJ8dBQxWrk3YzjrHxkVpePaGajqASKCQQJLmaTVSy2', 'QmbLRFj5U6UGTy3o9Zt8jEnVDuAw2GKzvrrv3RED9wyGRk', current_time, current_time + 400, 3amt_1, 1).then(function(f) {console.log(f)})});
EcommerceStore.deployed().then(function(i) {i.addProductToStore('iphone 6', 'Cell Phones & Accessories', 'QmNZWJ8dBQxWrk3YzjrHxkVpePaGajqASKCQQJLmaTVSy2', 'QmbLRFj5U6UGTy3o9Zt8jEnVDuAw2GKzvrrv3RED9wyGRk', current_time, current_time + 14, amt_1, 0).then(function(f) {console.log(f)})});
EcommerceStore.deployed().then(function(i) {i.addProductToStore('iphone 6s', 'Cell Phones & Accessories', 'QmNZWJ8dBQxWrk3YzjrHxkVpePaGajqASKCQQJLmaTVSy2', 'QmbLRFj5U6UGTy3o9Zt8jEnVDuAw2GKzvrrv3RED9wyGRk', current_time, current_time + 86400, 4amt_1, 1).then(function(f) {console.log(f)})});
EcommerceStore.deployed().then(function(i) {i.addProductToStore('iphone 7', 'Cell Phones & Accessories', 'QmNZWJ8dBQxWrk3YzjrHxkVpePaGajqASKCQQJLmaTVSy2', 'QmbLRFj5U6UGTy3o9Zt8jEnVDuAw2GKzvrrv3RED9wyGRk', current_time, current_time + 86400, 5amt_1, 1).then(function(f) {console.log(f)})});
EcommerceStore.deployed().then(function(i) {i.addProductToStore('Jeans', 'Clothing, Shoes & Accessories', 'QmNZWJ8dBQxWrk3YzjrHxkVpePaGajqASKCQQJLmaTVSy2', 'QmbLRFj5U6UGTy3o9Zt8jEnVDuAw2GKzvrrv3RED9wyGRk', current_time, current_time + 86400 + 86400 + 86400, 5*amt_1, 1).then(function(f) {console.log(f)})});
EcommerceStore.deployed().then(function(i) {i.productIndex.call().then(function(f){console.log(f)})});
}
编译
compile
migrate
var c
c = EcommerceStore.deployed().then((i)=>{c = i});
c.productIndex();
//插入6条数据
exec seed.js
//再次查询数据
c.productIndex();
BigNumber { s: 1, e: 0, c: [ 6 ] }
前端页面
app/index.html
<!DOCTYPE html>
<html>
<head>
<title>Decentralized Ecommerce Store</title>
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="./app.js"></script>
</head>
<body>
<div class="container-fluid">
<h1>Ecommerce Store</h1>
<div>Total Products: <span id="total-products"></span></div>
<a href="list-item.html" class="btn btn-primary">List Item</a>
<div class="row">
<div class="col-sm-2">
<h2>Categories</h2>
<div id="categories">
</div>
</div>
<div class="col-sm-10">
<div class="row">
<h2 class="text-center">Products To Buy</h2>
<div class="row">
<div class="row" id="product-list">
</div>
</div>
</div>
<div class="row">
<h2 class="text-center">Products In Reveal Stage</h2>
<div class="row">
<div class="row" id="product-reveal-list">
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
app/stylesheets/app.css
.col-margin-bottom-1 {
margin-bottom: 1em;
}
a {
cursor: pointer;
}
.navbar.main {
background: #5f6d7f;
color: white;
}
.navbar.main .list.nav-link {
background: #01bf86;
}
.navbar.main a {
color: white;
}
.navbar ul {
list-style: none;
}
.side-bar.col-sm-2 {
margin-left: 1em;
border-right: 1px solid #5f6d7f;
}
product-list.row {
margin-left: 0;
}
product-list .row,
product-finalize-list {
margin-top: 2em;
}
.row h2,
.categories a,
categories a {
color: #5f6d7f;
}
product-list a,
product-reveal-list a,
product-list .row > div {
color: #01bf86;
}
.container-fluid .row {
margin-left: 0px;
}
product-list img,
product-reveal-list img,
product-finalize-list img {
width: 150px;
height: 150px;
}
.container-fluid .title {
font-size: 1.2em;
font-weight: bold;
margin-top: 1em;
}
product-list .details,
.container-fluid .title,
product-finalize-list a {
color: #5f6d7f;
}
.product .details {
text-decoration: underline;
}
.time {
font-style: italic;
}
product-finalize-list .row > div{
color: #e76e6e;
}
product-details-page {
color: #5f6d7f;
background-color: #e9ebef;
}
product-details-page h4 {
margin-top: 0px;
font-size: 20px;
}
product-details-page h4.align {
margin-left: 15px;
font-style: italic;
}
product-details-page #product-price {
color: #01bf86;
}
product-details-page .bid-details,
product-details-page #product-desc {
padding: 2em;
}
product-details-page #product-image {
padding: 2em 1em;
/border: 1px solid #b2bcc8;/
border-radius: 10px;
background-color: white;
}
product-details-page h1 {
color: #5f6d7f;
}
product-details-page h2.product-name {
color: #01bf86;
margin-top: 0;
margin-bottom: 1em
/font-size: 1.5em;/
}
product-details-page #product-auction-end {
color: #e76e6e;
font-style: italic;
font-size: 17px;
}
product-details-page #product-reveal {
margin-top: 3em;
}
product-details-page button.form-submit {
background-color: #01bf86;
color: white;
}
product-details-page #product-desc {
padding: 2em;
}
npm run dev 启动项目