Now let's build the frontend of our application. Our frontend will let users see the list of recent matches, deploy the betting contract, bet on a game, and let them see information about a betting contract.
Let's first implement the matches.ejs file, which will display the list of recent matches. Here is the code for this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<link rel="stylesheet" href="/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<br>
<div class="row m-t-1">
<div class="col-md-12">
<a href="/">Home</a>
</div>
</div>
<br>
<div class="row">
<div class="col-md-12">
<table class="table table-inverse">
<thead>
<tr>
<th>Match ID</th>
<th>Start Time</th>
<th>Home Team</th>
<th>Away Team</th>
<th>Winner</th>
</tr>
</thead>
<tbody>
<% for(var i=0; i < matches.length; i++) { %>
<tr>
<td><%= matches[i].dbid %></td>
<% if (matches[i].start) { %>
<td><%= matches[i].start %></td>
<% } else { %>
<td>Time not finalized</td>
<% } %>
<td><%= matches[i].homeTeam.name %></td>
<td><%= matches[i].awayTeam.name %></td>
<% if (matches[i].outcome) { %>
<td><%= matches[i].outcome.winner %></td>
<% } else { %>
<td>Match not finished</td>
<% } %>
</tr>
<% } %>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
The preceding code is self-explanatory. Now let's write the HTML code for our home page. Our homepage will display three forms. The first form is to deploy a betting contract, the second form is to invest in a betting contract, and the third form is to display information on a deployed betting contract.
Here is the HTML code for the home page. Place this code in the index.html page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<link rel="stylesheet" href="/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<br>
<div class="row m-t-1">
<div class="col-md-12">
<a href="/matches">Matches</a>
</div>
</div>
<br>
<div class="row">
<div class="col-md-4">
<h3>Deploy betting contract</h3>
<form id="deploy">
<div class="form-group">
<label>From address: </label>
<input type="text" class="form-control" id="fromAddress">
</div>
<div class="form-group">
<label>Private Key: </label>
<input type="text" class="form-control" id="privateKey">
</div>
<div class="form-group">
<label>Match ID: </label>
<input type="text" class="form-control" id="matchId">
</div>
<div class="form-group">
<label>Bet Amount (in ether): </label>
<input type="text" class="form-control" id="betAmount">
</div>
<p id="message" style="word-wrap: break-word"></p>
<input type="submit" value="Deploy" class="btn btn-primary" />
</form>
</div>
<div class="col-md-4">
<h3>Bet on a contract</h3>
<form id="bet">
<div class="form-group">
<label>From address: </label>
<input type="text" class="form-control" id="fromAddress">
</div>
<div class="form-group">
<label>Private Key: </label>
<input type="text" class="form-control" id="privateKey">
</div>
<div class="form-group">
<label>Contract Address: </label>
<input type="text" class="form-control"
id="contractAddress">
</div>
<div class="form-group">
<label>Team: </label>
<select class="form-control" id="team">
<option>Home</option>
<option>Away</option>
</select>
</div>
<p id="message" style="word-wrap: break-word"></p>
<input type="submit" value="Bet" class="btn btn-primary" />
</form>
</div>
<div class="col-md-4">
<h3>Display betting contract</h3>
<form id="find">
<div class="form-group">
<label>Contract Address: </label>
<input type="text" class="form-control"
d="contractAddress">
</div>
<p id="message"></p>
<input type="submit" value="Find" class="btn btn-primary" />
</form>
</div>
</div>
</div>
<script type="text/javascript" src="/js/web3.min.js"></script>
<script type="text/javascript" src="/js/ethereumjs-tx.js"></script>
<script type="text/javascript" src="/js/main.js"></script>
</body>
</html>
The preceding code is self-explanatory. Now let's write JavaScript code to actually deploy the contract, invest in contracts, and display information on contracts. Here is the code for all this. Place this code in the main.js file:
var bettingContractByteCode = "6060604...";
var bettingContractABI = [{"constant":false,"inputs":[{"name":"team","type":"uint256"}],"name":"betOnTeam","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"myid","type":"bytes32"},{"name":"result","type":"string"}],"name":"__callback","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"myid","type":"bytes32"},{"name":"result","type":"string"},{"name":"proof","type":"bytes"}],"name":"__callback","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"url","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"matchId","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"amount","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"homeBet","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"awayBet","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"inputs":[{"name":"_matchId","type":"string"},{"name":"_amount","type":"uint256"},{"name":"_url","type":"string"}],"payable":false,"type":"constructor"}];
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
function getAJAXObject()
{
var request;
if (window.XMLHttpRequest) {
request = new XMLHttpRequest();
} else if (window.ActiveXObject) {
try {
request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {}
}
}
return request;
}
document.getElementById("deploy").addEventListener("submit", function(e){
e.preventDefault();
var fromAddress = document.querySelector("#deploy #fromAddress").value;
var privateKey = document.querySelector("#deploy #privateKey").value;
var matchId = document.querySelector("#deploy #matchId").value;
var betAmount = document.querySelector("#deploy #betAmount").value;
var url = "/getURL?matchId=" + matchId;
var request = getAJAXObject();
request.open("GET", url);
request.onreadystatechange = function() {
if (request.readyState == 4) {
if (request.status == 200) {
if(request.responseText != "An error occured")
{
var queryURL = request.responseText;
var contract = web3.eth.contract(bettingContractABI);
var data = contract.new.getData(matchId,
web3.toWei(betAmount, "ether"), queryURL, {
data: bettingContractByteCode
});
var gasRequired = web3.eth.estimateGas({ data: "0x" + data
});
web3.eth.getTransactionCount(fromAddress, function(error, nonce){
var rawTx = {
gasPrice: web3.toHex(web3.eth.gasPrice),
gasLimit: web3.toHex(gasRequired),
from: fromAddress,
nonce: web3.toHex(nonce),
data: "0x" + data,
};
privateKey = EthJS.Util.toBuffer(privateKey, "hex");
var tx = new EthJS.Tx(rawTx);
tx.sign(privateKey);
web3.eth.sendRawTransaction("0x" +
tx.serialize().toString("hex"), function(err, hash) {
if(!err)
{document.querySelector("#deploy #message").
innerHTML = "Transaction Hash: " + hash + ".
Transaction is mining...";
var timer = window.setInterval(function(){
web3.eth.getTransactionReceipt(hash, function(err, result){
if(result)
{window.clearInterval(timer);
document.querySelector("#deploy #message").innerHTML =
"Transaction Hash: " + hash + " and contract address is: " +
result.contractAddress;}
})
}, 10000)
}
else
{document.querySelector("#deploy #message").innerHTML = err;
}
});
})
}
}
}
};
request.send(null);
}, false)
document.getElementById("bet").addEventListener("submit", function(e){
e.preventDefault();
var fromAddress = document.querySelector("#bet #fromAddress").value;
var privateKey = document.querySelector("#bet #privateKey").value;
var contractAddress = document.querySelector("#bet #contractAddress").value;
var team = document.querySelector("#bet #team").value;
if(team == "Home")
{
team = 1;
}
else
{
team = 2;
}
var contract = web3.eth.contract(bettingContractABI).at(contractAddress);
var amount = contract.amount();
var data = contract.betOnTeam.getData(team);
var gasRequired = contract.betOnTeam.estimateGas(team, {
from: fromAddress,
value: amount,
to: contractAddress
})
web3.eth.getTransactionCount(fromAddress, function(error, nonce){
var rawTx = {
gasPrice: web3.toHex(web3.eth.gasPrice),
gasLimit: web3.toHex(gasRequired),
from: fromAddress,
nonce: web3.toHex(nonce),
data: data,
to: contractAddress,
value: web3.toHex(amount)
};
privateKey = EthJS.Util.toBuffer(privateKey, "hex");
var tx = new EthJS.Tx(rawTx);
tx.sign(privateKey);
web3.eth.sendRawTransaction("0x" + tx.serialize().toString("hex"), function(err, hash) {
if(!err)
{
document.querySelector("#bet #message").innerHTML = "Transaction
Hash: " + hash;
}
else
{
document.querySelector("#bet #message").innerHTML = err;
}
})
})
}, false)
document.getElementById("find").addEventListener("submit", function(e){
e.preventDefault();
var contractAddress = document.querySelector("#find
#contractAddress").value;
var contract =
web3.eth.contract(bettingContractABI).at(contractAddress);
var matchId = contract.matchId();
var amount = contract.amount();
var homeAddress = contract.homeBet();
var awayAddress = contract.awayBet();
document.querySelector("#find #message").innerHTML = "Contract balance is: " + web3.fromWei(web3.eth.getBalance(contractAddress), "ether") + ", Match ID is: " + matchId + ", bet amount is: " + web3.fromWei(amount, "ether") + " ETH, " + homeAddress + " has placed bet on home team and " + awayAddress + " has placed bet on away team";
}, false)
This is how the preceding code works:
- At first, we store the contract byte code and ABI in the bettingContractByteCode and bettingContractABI variables, respectively.
- Then, we are create a Web3 instance, which is connected to our testnet node.
- Then, we have the getAJAXObject function (a cross-browser compatible function), which returns an AJAX object.
- Then, we attach a submit event listener to the first form, which is used to deploy the contract. In the event listener's callback, we make a request to the getURL endpoint by passing matchId to get the encrypted query string. And then, we generate the data to deploy the contract. Then, we find out the gasRequired. We use the function object's estimateGas method to calculate the gas required, but you can use the web3.eth.estimateGas method too. They both differ in terms of arguments; that is, in the preceding case, you don't need to pass the transaction data. Remember that estimateGas will return the block gas limit if the function call throws an exception. Then, we calculate the nonce. Here, we just use the getTransactionCount method instead of the actual procedure we learned earlier. We do this just for simplification of the code. Then, we create the raw transaction, signing it and broadcasting it. Once the transaction is mined, we display the contract address.
- Then, we attach a submit event listener for the second form, which is used to invest in a contract. Here, we generate the data part of the transaction, calculating the gas required, creating the raw transaction, signing it, and broadcasting it. While calculating the gas required for the transaction, we pass the contract address from the account address and value object properties as it's a function call, and the gas differs depending on the value, the from address, and contract address. Remember that while finding the gas required to call a contract's function, you can pass the to, from,and value properties because gas depends on these values.
- Finally, we have a submit event listener for the third form, that is, to display information on a deployed betting contract.