[區塊鏈-以太坊] 募資平台建立-Part 2. 贊助

Chiwen Lai
11 min readMay 31, 2018

--

繼上一篇完成募資平台的Part 1. 提案之後,我們可以開始正式募資囉,讓對我們的提案有興趣的人來贊助。

  1. 由首頁的合約位址點入,帶到該募資計畫的詳細頁面。
  2. 募資計畫頁面分為兩大部分,左邊是該計畫的詳細內容,包含提案人位址、最小贊助金額、贊助人數、總贊助金額。右邊則是贊助的表單,對該計畫有興趣的人可以直接輸入金額,再按下贊助的按鈕。

STEP 1: Backend

▼開啟/contracts/Campaign.sol,在合約內加入贊助的功能。

contract Campaign {

address public owner;
uint public minimumContribution;
string public campaignName;
// 1 - 宣告贊助者變數,型態為mapping
mapping(address => bool) public contributors;
uint public contributorsCount;
constructor(uint minimum, string name, address creator) public {
owner = creator;
minimumContribution = minimum;
campaignName = name;
}
// 2 - 贊助
function contribute() public payable {
require(msg.value > minimumContribution);
contributors[msg.sender] = true;
contributorsCount++;
}
// 3 - 回傳募資計畫詳細內容
function getSummary() public view returns (
string, uint, uint, uint, address
){
return (
campaignName,
minimumContribution,
this.balance,
contributorsCount,
owner
);
}

}

// 1 — 宣告變數contributors型態為mapping(address => bool),表示key為位址型態,value為boolean型態的mapping,可視為以下:

mapping型態具有以下特色:

  • 與陣列相比,mapping的搜尋時間是不會隨著資料量的擴大而增加,也因此相對地有效率。
  • mapping是以hash table的形式存在,key已被虛擬化並未實際存在,僅提供用來查詢value時使用。

// 2 — 在贊助的function中,我們首先檢查贊助的金額是否大於提案人設定的最低贊助金額,是的話就將贊助人的位址加入contributors這個mapping並設定value=true。再將變數contributorsCount贊助者人數加1。
// 3 — 為方便我們在募資計畫頁面可以顯示詳細資料,我們先寫好一個getSummary() Function,以便待會讓前端程式可以呼叫。

STEP 2: 重新部署

▼合約寫好後記得得重新部署。

$ node compile.js
$ node deploy.js
Contract deployed to 0xB3578B0Fe4621CD188f17bd12aF88284fc08A8Cb

▼將新的合約位址更新到factory.js,如下。

import web3 from "./web3";
import CampaignFactory from "./build/CampaignFactory.json";
const instance = new web3.eth.Contract(
JSON.parse(CampaignFactory.interface),
"0xB3578B0Fe4621CD188f17bd12aF88284fc08A8Cb"
);
export default instance;

▼在ethereum目錄下面新增檔案campaign.js。我們利用factory.js作為web3部署CampaignFactory合約的中介,campaign.js則作為Campaign合約的中介。也因為在我們的募資計劃顯示頁面中,Campaign合約的位址為動態,因此在campaign.js我們將位址作為參數傳入。一樣回傳interface供前端使用。

import web3 from "./web3";
import Campaign from "./build/Campaign.json";
export default address => {
return new web3.eth.Contract(JSON.parse(Campaign.interface), address);
};

STEP 3: Frontend

▼在routes.js加上url routing,以募資計畫的位址為url。

const routes = require("next-routes")();routes
.add("/campaigns/new", "/campaigns/new")
.add("/campaigns/:address", "/campaigns/show");
module.exports = routes;

▼在首頁index.js加上連結,利用募資計畫的位址作為參數帶入。

renderCampaigns() {
const items = this.props.campaigns.map((campaign, index) => {
return {
header: campaign.campaignName,
description: (
<Link route={`/campaigns/${campaign.campaignAddress}`}>
<a> {campaign.campaignAddress}</a>
</Link>
),

fluid: true
};
});
return <Card.Group items={items} />;
}

▼在components目錄下面新增檔案ContributeForm.js,即畫面右邊贊助的表單。

import React, { Component } from "react";
import { Form, Input, Message, Button } from "semantic-ui-react";
import Campaign from "../ethereum/campaign";
import web3 from "../ethereum/web3";
import { Router } from "../routes";
class ContributeForm extends Component {
state = {
value: "",
errorMessage: "",
loading: false
};
onSubmit = async event => {
event.preventDefault();
const campaign = Campaign(this.props.address);
this.setState({ loading: true, errorMessage: "" });
try {
const accounts = await web3.eth.getAccounts();
// 1 - 呼叫合約內贊助的function
await campaign.methods.contribute().send({
from: accounts[0],
value: web3.utils.toWei(this.state.value, "ether")
});


Router.replaceRoute(`/campaigns/${this.props.address}`);
} catch (err) {
this.setState({ errorMessage: err.message });
}
this.setState({ loading: false });
};
render() {
return (
<Form onSubmit={this.onSubmit} error={!!this.state.errorMessage}>
<Form.Field>
<label>贊助金額</label>
<Input
value={this.state.value}
label="ether"
labelPosition="right"
onChange={event => this.setState({ value: event.target.value })}
/>
</Form.Field>
<Button primary loading={this.state.loading}>
贊助
</Button>
</Form>
);
}
}
export default ContributeForm;

▼在campaigns目錄下面新增檔案show.js,即畫面左邊計畫的詳細資料。

import React, { Component } from "react";
import { Card, Grid, Button } from "semantic-ui-react";
import Layout from "../../components/Layout";
import Campaign from "../../ethereum/campaign";
import web3 from "../../ethereum/web3";
import ContributeForm from "../../components/ContributeForm";
import { Link } from "../../routes";
class CampaignShow extends Component {
static async getInitialProps(props) {
const campaign = Campaign(props.query.address);
const summary = await campaign.methods.getSummary().call();
return {
address: props.query.address,
campaignName: summary[0],
minimumContribution: summary[1],
balance: summary[2],
contributorsCount: summary[3],
owner: summary[4]
};
}
renderCards() {
const {
campaignName,
minimumContribution,
balance,
contributorsCount,
owner
} = this.props;
const items = [
{
header: owner,
meta: "提案人位址",
description:
"起始這個募資計畫的人,提案人可以提出花費項目並將贊助金額匯出",
style: { overflowWrap: "break-word" }
},
{
header: minimumContribution,
meta: "最小贊助金額 (Wei)",
description: "由提案人提出,您的贊助金額需大於此金額才可成為贊助者"
},
{
header: contributorsCount,
meta: "贊助人數",
description: "已贊助此募資計畫的人數"
},
{
header: web3.utils.fromWei(balance, "ether"),
meta: "總贊助金額(ether)",
description: "此募資計畫目前剩餘的金額"
}
];
return <Card.Group items={items} />;
}
render() {
return (
<Layout>
<h3>{this.props.campaignName}</h3>
<Grid>
<Grid.Row>
<Grid.Column width={10}>{this.renderCards()}</Grid.Column>
<Grid.Column width={6}>
<ContributeForm address={this.props.address} />
</Grid.Column>
</Grid.Row>
</Grid>
</Layout>
);
}
}
export default CampaignShow;

▼接下來直接在終端機crowdfunding目錄下面輸入

$ npm run dev

即可在瀏覽器http://localhost:3000觀看到我們的募資畫面。

>>> 下一篇:Part 3. 提出資金花費項目

--

--