-
-
Save theraaz/a5552434a29dd767752383057322f589 to your computer and use it in GitHub Desktop.
MODULE 5 - START, Hub Contracts and UI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var app = angular.module('HubApp', []); | |
app.config(function( $locationProvider) { | |
$locationProvider.html5Mode({ | |
enabled: true, | |
requireBase: false | |
}); | |
}); | |
app.controller("HubController", | |
[ '$scope', '$location', '$http', '$q', '$window', '$timeout', | |
function($scope, $location, $http, $q, $window, $timeout) { | |
var hub; | |
Hub.deployed().then(function(instance) { | |
hub = instance; | |
newCampaignWatcher = watchForNewCampaigns(); | |
}); | |
txn = {}; // workaround for repetitive event emission (testRPC) | |
$scope.campaigns=[]; // array of structs | |
$scope.campaignIndex={}; // row pointers | |
$scope.campaignLog=[]; // verbose on-screen display of happenings | |
$scope.new = {}; // new campaign | |
$scope.campaignSelected; // campaign selector | |
$scope.contribution; // contribution field | |
// INTERACTIONS | |
// select account | |
$scope.setAccount = function() { | |
$scope.account = $scope.accountSelected; | |
$scope.balance = web3.eth.getBalance($scope.account).toString(10); | |
var countCampaigns = $scope.campaigns.length; | |
// the "User Contributed" col needs a new context, so refresh them | |
for(i=0; i<countCampaigns; i++) { | |
upsertCampaign($scope.campaigns[i].campaign); | |
} | |
console.log('Using account',$scope.account); | |
} | |
// new campaign | |
$scope.newCampaign = function() { | |
if(parseInt($scope.new.goal) > 0 && parseInt($scope.new.duration) > 0) { | |
hub.createCampaign($scope.new.duration, $scope.new.goal, {from: $scope.account, gas: 4000000}) | |
.then(function(txn) { | |
$scope.new.goal = ""; | |
$scope.new.duration = ""; | |
}); | |
} else { | |
alert('Integers over Zero, please'); | |
} | |
} | |
// contribute to campaign | |
$scope.contribute = function() { | |
if($scope.campaignSelected=="") return; | |
if(parseInt($scope.contribution)<=0) return; | |
var campaign = Campaign.at($scope.campaignSelected); | |
var amount = $scope.contribution; | |
$scope.contribution = ""; | |
campaign.contribute({from: $scope.account, value: parseInt(amount), gas: 4000000}) | |
.then(function(txn) { | |
return; | |
}); | |
} | |
// claim a refund | |
$scope.refund = function(campaign) { | |
var campaign = Campaign.at(campaign); | |
return campaign.requestRefund({from: $scope.account, gas: 4000000}) | |
.then(function(txn) { | |
// an event will arrive | |
}); | |
} | |
// DISPLAY | |
// watch hub campaigns created. UI starts here. | |
function watchForNewCampaigns() { | |
hub.LogNewCampaign( {}, {fromBlock: 0}) | |
.watch(function(err,newCampaign) { | |
if(err) | |
{ | |
console.error("Campaign Error:",err); | |
} else { | |
// normalizing data for output purposes | |
console.log("New Campaign", newCampaign); | |
newCampaign.args.user = newCampaign.args.sponsor; | |
newCampaign.args.amount = newCampaign.args.goal.toString(10); | |
// only if non-repetitive (testRPC) | |
if(typeof(txn[newCampaign.transactionHash])=='undefined') | |
{ | |
$scope.campaignLog.push(newCampaign); | |
txn[newCampaign.transactionHash]=true; | |
upsertCampaign(newCampaign.args.campaign); | |
} | |
} | |
}) | |
}; | |
// watch functions for each campaign we know about | |
// watch receipts | |
function watchReceived(address) { | |
var campaign = Campaign.at(address); | |
var watcher = campaign.LogContribution( {}, {fromBlock: 0}) | |
.watch(function(err,received) { | |
if(err) | |
{ | |
console.error('Received Error', address, err); | |
} else { | |
console.log("Contribution", received); | |
if(typeof(txn[received.transactionHash+'rec'])=='undefined') | |
{ | |
received.args.user = received.args.sender; | |
received.args.amount = parseInt(received.args.amount); | |
received.args.campaign = address; | |
$scope.campaignLog.push(received); | |
upsertCampaign(address); | |
txn[received.transactionHash+'rec']=true; | |
} | |
} | |
}); | |
} | |
// watch refunds | |
function watchRefunded(address) { | |
var campaign = Campaign.at(address); | |
var watcher = campaign.LogRefundSent( {}, {fromBlock: 0}) | |
.watch(function(err,refunded) { | |
if(err) | |
{ | |
console.error('Refunded Error', address, err); | |
} else { | |
console.log("Refund", refunded); | |
if(typeof(txn[refunded.transactionHash+'ref'])=='undefined') | |
{ | |
refunded.args.user = refunded.args.funder; | |
refunded.args.amount = parseInt(refunded.args.amount); | |
refunded.args.campaign = address; | |
$scope.campaignLog.push(refunded); | |
upsertCampaign(address); | |
txn[refunded.transactionHash+'ref']=true; | |
} | |
} | |
}); | |
} | |
// update display (row) and instantiate campaign watchers | |
// safe to call for newly discovered and existing campaigns that may have changed in some way | |
function upsertCampaign(address) { | |
console.log("Upserting campaign", address); | |
var campaign = Campaign.at(address); | |
// console.log("Campaign", campaign); | |
var campaignDeadline; | |
var campaignGoal; | |
var campaignFundsRaised; | |
var campaignIsSuccess; | |
var campaignHasFailed; | |
return campaign.deadline.call({from: $scope.account}) | |
.then(function(_deadline) { | |
campaignDeadline = _deadline; | |
//console.log("Deadline", campaignDeadline); | |
return campaign.goal.call({from: $scope.account}); | |
}) | |
.then(function(_goal) { | |
campaignGoal = _goal; | |
//console.log("Goal", campaignGoal); | |
return campaign.fundsRaised.call({from: $scope.account}); | |
}) | |
.then(function(_fundsRaised) { | |
campaignFundsRaised = _fundsRaised; | |
//console.log("Funds Raised", campaignFundsRaised); | |
return campaign.withdrawn.call({from: $scope.account}); | |
}) | |
.then(function(_withdrawn) { | |
campaignWithdrawn = _withdrawn; | |
//console.log("Withdrawn", _withdrawn); | |
return campaign.sponsor.call({from: $scope.account}); | |
}) | |
.then(function(_sponsor) { | |
campaignSponsor = _sponsor; | |
//console.log("Sponsor", campaignSponsor); | |
return campaign.isSuccess.call({from: $scope.account}); | |
}) | |
.then(function(_isSuccess) { | |
campaignIsSuccess = _isSuccess; | |
//console.log("is Success", campaignIsSuccess); | |
return campaign.hasFailed.call({from: $scope.account}); | |
}) | |
.then(function(_hasFailed) { | |
campaignHasFailed = _hasFailed; | |
//console.log("has Failed", campaignHasFailed); | |
// build a row step-by-step | |
var c = {}; | |
c.campaign = address; | |
c.sponsor = campaignSponsor; | |
c.goal = campaignGoal.toString(10); | |
c.deadline = parseInt(campaignDeadline.toString(10)); | |
c.accepted = parseInt(campaignFundsRaised.toString(10)); | |
c.withdrawn = parseInt(campaignWithdrawn.toString(10)); | |
c.isSuccess = campaignIsSuccess; | |
c.hasFailed = campaignHasFailed; | |
c.status = "open"; | |
if(c.isSuccess) c.status = "success"; | |
if(c.hasFailed) c.status = "failed"; | |
if(typeof($scope.campaignIndex[address]) == 'undefined') | |
{ | |
$scope.campaignIndex[c.campaign]=$scope.campaigns.length; | |
$scope.campaigns.push(c); | |
var receiveWatcher = watchReceived(address); | |
var refundWatcher = watchRefunded(address); | |
$scope.$apply(); | |
} else { | |
var index = $scope.campaignIndex[c.campaign]; | |
$scope.campaigns[index].accepted = c.accepted; | |
$scope.campaigns[index].refunded = c.refunded; | |
$scope.campaigns[index].withdrawn = c.withdrawn; | |
$scope.campaigns[index].isSuccess = c.isSuccess; | |
$scope.campaigns[index].hasFailed = c.hasFailed; | |
$scope.$apply(); | |
} | |
return getFunder(address); | |
}); | |
} | |
// Check contributions from the current user | |
function getFunder(address) { | |
var campaign = Campaign.at(address); | |
var index = $scope.campaignIndex[address]; | |
return campaign.funderStructs.call($scope.account, {from: $scope.account}) | |
.then(function(funder) { | |
// when a function returns multiple values, we get an array | |
$scope.campaigns[index].userAccepted = parseInt(funder[0].toString(10)); | |
$scope.campaigns[index].userRefunded = parseInt(funder[1].toString(10)); | |
$scope.$apply(); | |
return true;; | |
}) | |
} | |
// work with the first account. | |
web3.eth.getAccounts(function(err, accs) { | |
if (err != null) { | |
alert("There was an error fetching your accounts."); | |
return; | |
} | |
if (accs.length == 0) { | |
alert("Couldn't get any accounts! Make sure your Ethereum client is configured correctly."); | |
return; | |
} | |
$scope.accounts = accs; | |
$scope.account = $scope.accounts[0]; | |
$scope.balance = web3.eth.getBalance($scope.account).toString(10); | |
console.log('Using account',$scope.account); | |
}); | |
}]); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
////////////////////////////////////////////////////////// | |
// For training purposes. | |
// Solidity Contract Factory | |
// Module 5 - START | |
// Copyright (c) 2017, Rob Hitchens, all rights reserved. | |
// Not suitable for actual use | |
////////////////////////////////////////////////////////// | |
pragma solidity ^0.4.6; | |
import "./Stoppable.sol"; | |
contract Campaign is Stoppable { | |
address public sponsor; | |
uint public deadline; | |
uint public goal; | |
uint public fundsRaised; | |
uint public withdrawn; | |
struct FunderStruct { | |
uint amountContributed; | |
uint amountRefunded; | |
} | |
mapping (address => FunderStruct) public funderStructs; | |
modifier onlySponsor { | |
if(msg.sender != sponsor) throw; | |
_; | |
} | |
event LogContribution(address sender, uint amount); | |
event LogRefundSent(address funder, uint amount); | |
event LogWithdrawal(address beneficiary, uint amount); | |
function Campaign(address campaignSponsor, uint campaignDuration, uint campaignGoal) { | |
sponsor = campaignSponsor; | |
deadline = block.number + campaignDuration; | |
goal = campaignGoal; | |
} | |
function isSuccess() | |
public | |
constant | |
returns(bool isIndeed) | |
{ | |
return(fundsRaised >= goal); | |
} | |
function hasFailed() | |
public | |
constant | |
returns(bool hasIndeed) | |
{ | |
return(fundsRaised < goal && block.number > deadline); | |
} | |
function contribute() | |
public | |
onlyIfRunning | |
payable | |
returns(bool success) | |
{ | |
if(msg.value==0) throw; | |
if(isSuccess()) throw; | |
if(hasFailed()) throw; | |
fundsRaised += msg.value; | |
funderStructs[msg.sender].amountContributed += msg.value; | |
LogContribution(msg.sender, msg.value); | |
return true; | |
} | |
function withdrawFunds() | |
onlySponsor | |
onlyIfRunning | |
returns(bool success) | |
{ | |
if(!isSuccess()) throw; | |
uint amount = fundsRaised - withdrawn; | |
withdrawn += amount; | |
if(!owner.send(amount)) throw; | |
LogWithdrawal(owner, amount); | |
return true; | |
} | |
function requestRefund() | |
public | |
onlyIfRunning | |
returns(bool success) | |
{ | |
uint amountOwed = funderStructs[msg.sender].amountContributed - funderStructs[msg.sender].amountRefunded; | |
if(amountOwed == 0) throw; | |
if(!hasFailed()) throw; | |
funderStructs[msg.sender].amountRefunded += amountOwed; | |
if(!msg.sender.send(amountOwed)) throw; | |
LogRefundSent(msg.sender, amountOwed); | |
return true; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
////////////////////////////////////////////////////////// | |
// For training purposes. | |
// Solidity Contract Factory | |
// Module 5 | |
// Copyright (c) 2017, Rob Hitchens, all rights reserved. | |
// Not suitable for actual use | |
////////////////////////////////////////////////////////// | |
pragma solidity ^0.4.6; | |
import "./Campaign.sol"; | |
contract Hub is Stoppable { | |
address[] public campaigns; | |
mapping(address => bool) campaignExists; | |
modifier onlyIfCampaign(address campaign) { | |
if(campaignExists[campaign] != true) throw; | |
_; | |
} | |
event LogNewCampaign(address sponsor, address campaign, uint duration, uint goal); | |
event LogCampaignStopped(address sender, address campaign); | |
event LogCampaignStarted(address sender, address campaign); | |
event LogCampaignNewOwner(address sender, address campaign, address newOwner); | |
function getCampaignCount() | |
public | |
constant | |
returns(uint campaignCount) | |
{ | |
return campaigns.length; | |
} | |
function createCampaign(uint campaignDuration, uint campaignGoal) | |
public | |
returns(address campaignContract) | |
{ | |
Campaign trustedCampaign = new Campaign(msg.sender,campaignDuration, campaignGoal); | |
campaigns.push(trustedCampaign); | |
campaignExists[trustedCampaign] = true; | |
LogNewCampaign(msg.sender, trustedCampaign, campaignDuration, campaignGoal); | |
return trustedCampaign; | |
} | |
// Pass-through Admin Controls | |
function stopCampaign(address campaign) | |
onlyOwner | |
onlyIfCampaign(campaign) | |
returns(bool success) | |
{ | |
Campaign trustedCampaign = Campaign(campaign); | |
LogCampaignStopped(msg.sender, campaign); | |
return(trustedCampaign.runSwitch(false)); | |
} | |
function startCampaign(address campaign) | |
onlyOwner | |
onlyIfCampaign(campaign) | |
returns(bool success) | |
{ | |
Campaign trustedCampaign = Campaign(campaign); | |
LogCampaignStarted(msg.sender, campaign); | |
return(trustedCampaign.runSwitch(true)); | |
} | |
function changeCampaignOwner(address campaign, address newOwner) | |
onlyOwner | |
onlyIfCampaign(campaign) | |
returns(bool success) | |
{ | |
Campaign trustedCampaign = Campaign(campaign); | |
LogCampaignNewOwner(msg.sender, campaign, newOwner); | |
return(trustedCampaign.changeOwner(newOwner)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<base href="." /> | |
<title>FundingHub</title> | |
<!-- Latest compiled and minified bootstrap CSS --> | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> | |
<!-- Optional bootstrap theme --> | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> | |
<!-- JQuery --> | |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> | |
<!-- Latest compiled and minified bootstrap JavaScript --> | |
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> | |
<link href="./app.css" rel='stylesheet' type='text/css'> | |
<!-- app controller --> | |
<script src="./app.js"></script> | |
</head> | |
<body ng-cloak ng-controller="HubController"> | |
<div class="container-fluid"> | |
<div class="row"> | |
<div class="col-md-12"> | |
<h3>Funding<span class="black">Hub </span> | |
<table> | |
<tr> | |
<td> | |
<form id="campaignForm" ng-submit="newCampaign()"> | |
<input type="text" name="goal" ng-model="new.goal" placeholder="goal in Wei" size="10" required /> | |
<input type="text" name="duration" ng-model="new.duration" placeholder="duration in Blocks" required /> | |
<input type="submit" class="btn btn-primary addnew" value="New Campaign"></h3> | |
</form> | |
</td> | |
<td> | |
<h3> | |
<form id="accountForm" ng-submit="setAccount()"> | |
| |
<select name="account" ng-model="accountSelected" required> | |
<option ng-repeat="account in accounts" value="{{ account }}"> | |
{{ account }} | |
</option> | |
</select> | |
<input type="submit" class="btn btn-primary addnew" value="Select Account"></h3> | |
</form> | |
</h3> | |
</td> | |
</tr> | |
</table> | |
<h6>Your Balance (wei): {{ balance }} <span style="color: #777;">(account: {{ account }})</span></h6> | |
<div class="panel panel-default"> | |
<div class="panel-body"> | |
<form id="contributeForm" ng-submit="contribute()"> | |
<table class="table table-striped table-bordered"> | |
<tr> | |
<th>Choose Campaign to Contribute</th> | |
<th>Contribution in Wei</th> | |
<th> </th> | |
</tr> | |
<tr> | |
<td> | |
<select name="campaign" ng-model="campaignSelected" required> | |
<option></option> | |
<option ng-repeat="campaign in campaigns track by campaign.campaign" value="{{ campaign.campaign }}"> | |
{{ campaign.campaign }} | |
</option> | |
</select> | |
</td> | |
<td><input type="text" class="form-control" ng-model="contribution" placeholder="0 in Wei" required/></td> | |
<td><input type="submit" class="btn btn-primary addnew pull-right" value="Contribute"></td> | |
</tr> | |
</table> | |
</form> | |
<table class="table table-striped table-bordered"> | |
<thead> | |
<tr> | |
<th>Campaign</th> | |
<th>Campaign Goal</th> | |
<th>Campaign Accepted</th> | |
<th>Campaign Withdrawn</th> | |
<th>Deadline</th> | |
<th>User Contributed</th> | |
<th>User Refunded</th> | |
<th>Status</th> | |
<th></th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr ng-repeat="campaign in campaigns track by campaign.campaign"> | |
<td>{{ campaign.campaign }}</td> | |
<td>{{ campaign.goal }}</td> | |
<td>{{ campaign.accepted }}</td> | |
<td>{{ campaign.withdrawn }}</td> | |
<td>{{ campaign.deadline }}</td> | |
<td>{{ campaign.userAccepted }}</td> | |
<td>{{ campaign.userRefunded }}</td> | |
<td>{{ campaign.status }}</td> | |
<td> | |
<form> | |
<input | |
ng-if= 'campaign.status == "failed" && campaign.userAccepted - campaign.userRefunded > 0' | |
type="submit" class="btn btn-success addnew pull-right" value="Refund" | |
ng-click="refund(campaign.campaign)"> | |
<input | |
ng-if= 'campaign.status != "failed" || campaign.userAccepted - campaign.userRefunded <= 0' | |
type="submit" class="btn btn-success addnew pull-right | |
disabled" value="Refund"> | |
</form> | |
</td> | |
</tr> | |
</tbody> | |
</table> | |
<table class="table table-striped table-bordered"> | |
<tr> | |
<th>Event</th> | |
<th>Amount</th> | |
<th>Campaign</th> | |
<th>User</th> | |
</tr> | |
<tr ng-repeat="campaignInfo in campaignLog"> | |
<td>{{ campaignInfo.event }}</td> | |
<td>{{ campaignInfo.args.amount }}</td> | |
<td>{{ campaignInfo.args.campaign }}</td> | |
<td>{{ campaignInfo.args.user }}</td> | |
</tr> | |
</table> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
window.addEventListener('load', function() { | |
angular.bootstrap(document, ['HubApp']) | |
}); | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
////////////////////////////////////////////////////////// | |
// For training purposes. | |
// Solidity Contract Factory | |
// Module 5 - START | |
// Copyright (c) 2017, Rob Hitchens, all rights reserved. | |
// Not suitable for actual use | |
////////////////////////////////////////////////////////// | |
pragma solidity ^0.4.6; | |
contract Owned { | |
address public owner; | |
event LogNewOwner(address sender, address oldOwner, address newOwner); | |
modifier onlyOwner { | |
if(msg.sender != owner) throw; | |
_; | |
} | |
function Owned() { | |
owner = msg.sender; | |
} | |
function changeOwner(address newOwner) | |
onlyOwner | |
returns(bool success) | |
{ | |
if(newOwner == 0) throw; | |
LogNewOwner(msg.sender, owner, newOwner); | |
owner = newOwner; | |
return true; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
////////////////////////////////////////////////////////// | |
// For training purposes. | |
// Solidity Contract Factory | |
// Module 5 - START | |
// Copyright (c) 2017, Rob Hitchens, all rights reserved. | |
// Not suitable for actual use | |
////////////////////////////////////////////////////////// | |
pragma solidity ^0.4.6; | |
import "./Owned.sol"; | |
contract Stoppable is Owned { | |
bool public running; | |
event LogRunSwitch(address sender, bool switchSetting); | |
modifier onlyIfRunning { | |
if(!running) throw; | |
_; | |
} | |
function Stoppable() { | |
running = true; | |
} | |
function runSwitch(bool onOff) | |
onlyOwner | |
returns(bool success) | |
{ | |
running = onOff; | |
LogRunSwitch(msg.sender, onOff); | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment