[區塊鏈-以太坊] 募資平台建立-Part 2. 贊助
繼上一篇完成募資平台的Part 1. 提案之後,我們可以開始正式募資囉,讓對我們的提案有興趣的人來贊助。
- 由首頁的合約位址點入,帶到該募資計畫的詳細頁面。
- 募資計畫頁面分為兩大部分,左邊是該計畫的詳細內容,包含提案人位址、最小贊助金額、贊助人數、總贊助金額。右邊則是贊助的表單,對該計畫有興趣的人可以直接輸入金額,再按下贊助的按鈕。
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. 提出資金花費項目