Ethereum Tutorials

SMART CONTRACTS

Web UI for "ShareHolder"

A corresponding HTML shows tables, charts, and a verbose security warning for customers - something most web pages for some reasons tend to omit:


<!DOCTYPE html>
<html>
<head>
	<title>ShareHolder Ethereum Contract: participate in profit.</title>

	<script src="https://cdn.rawgit.com/ethereum/web3.js/develop/dist/web3.js"></script>
	<script src="https://snowcron.com/jquery-2.1.1.min.js"></script>

	<script src="lib/moment.min.js"></script>
	 <link rel="stylesheet" href="lib/chartist.min.css">
	<script src="lib/chartist.min.js"></script>
	
	<link rel="stylesheet" type="text/css" href="http://snowcron.com/styles.css">
	
<!-- replacer_ethereum_show_hide -->
	<script>
		function show_hide() 
		{
			var x = $('#hidable');
			if(!x.is(':visible')) 
			{
				x.show();
				$('#show_hide').html('Hide...');
			} 
			else 
			{
				x.hide();
				$('#show_hide').html('See more...');
			}
		}	
	</script>	
<!-- /replacer_ethereum_show_hide -->	
</head>

<body style="background:white;margin-left:20px;margin-right:20px;" onload="init();">
	
<div id="holder">
	
<p><div class="footer">
	<div id="main">	
	
		<fieldset style="text-align:center;">
			<legend class="image_with_link">
				<a href="duke_of_ether.htm"><img src="images/duke_of_ether_grey.png"></a>
				<a href="share_holder.htm"><img src="images/share_holder.png"></a>
			</legend>
		
			<div id="share_holder_info" style="text-align:left;"></div>
			<div id="error_message">Ethereum network not available</div> 
				
			<table border=0 width="100%">
				<td colspan="2">
<!-- replacer_ethereum_word_of_caution -->
					<div align="left">
						<h3 style="color:#CD2626">A word of caution:</h3>
						<p>This is not exactly a commercial site: it teaches rather than asking for money
							(please, take a look at our "donate" link :) So let's discuss a security
							issue that most DAO web sites would rather avoid mentioning.
							
						<p><button class="btn-normal" type="button" id="show_hide" onClick="show_hide();" style="min-width:200px;">See more...</button>
							
						<div id="hidable" style="display:none;">	
							<p>Note: this is not the issue of this particular site, but of all sites that 
								provide Web interface to Ethereum payments, so it is a very good idea to 
								familiarize yourself with the subject.
							<p>Below, you have two payment choices. First of all, you can start your favourite wallet
								(Mist, MetaMask, geth - whatever) and click the "Become Duke!" button below.
								You will be prompted for a password, because wallets require you to reconfirm the 
								password in order to send coins. 
							<p>Is it safe? The answer is NO. It is hard to hack the ethereum network, so you can be
								reasonably confident paying with your wallet, but a web site is a different thing.
								It can be hacked.
							<p>Now, imagine someone hacking the "Duke of Ether" web site and placing there some malicious
								code. It can: a) send your coins elsewhere and b) send more than you expected.
							<p>This is an important issue to keep in mind: for example (source: http://fortune.com/2017/07/18/ethereum-coindash-ico-hack/):
								<font color="#8B8682">CoinDash, an Israeli startup, planned to raise capital by 
								selling its own digital tokens in exchange for the cryptocurrency Ethereum... But just 13 
								minutes into the token sale... an "unknown perpetrator" hacked CoinDash's website and 
								changed the address for sending investments to a fake one, the company later announced 
								on its website. That diverted millions of dollars in contributions to the attacker...</font>
							<p>To avoid the problem, you have few choices. 
							<p>First, if you are a web guru, you can examine the web site... which is long, boring and there still is 
								a chance you'll miss something.
							<p>Second, do not pay from a wallet that has a lot of money. If you have $1 in your wallet, 
								and a hacker drains it all, he'll get $1, which isn't that much after all. 
							<p>Third, keep as much control over your payment as you can: do not pay through a web site, pay directly. 
								Below, in the "If you don't trust us" section a cold payment instructions are displayed.
								Use your wallet to send money (look in the suggested amount in the left column).
								If the site is hacked, you will still loose your money, but at least you will loose 
								only the amount you sent, not all coins a malicious script finds in your wallet.
							<p>Finally, to avoid that last danger, you can use <a href="https://etherscan.org">etherscan.org</a>
								to find the Duke contract by its address (see below in the "If you don't trust us" section)
								and study it. First, if you can not find it, then the site was probably hacked; second, 
								if you know Solidity, you can analyze the code, and finally, look at the date a contract was published. 
							<p>As an additional precaution, you can save the web site on your disk and run the local copy. 
								It will not help if the site ALREADY contains malicious code, but it can protect you from the 
								future attacks. There are two disadvantages: first, not all sites support independent work 
								(ours does), and second, if the site changes, providing more functional, you will miss it.
							<p>How realistic is the danger? Well, there are thousands sites online that have "please donate" Bitcoin
								and Ethereum addresses... and they seem to work. But - see the CoinDash story above - it is
								possible.							
						</div>		
					</div>		
<!-- /replacer_ethereum_word_of_caution -->
				</td>
				<tr>
				<td colspan=2><h2 class="header">Invest in ShareHolders Shares</h2></td>
				<tr>
				<td width="50%" style="background-color:#FFDAB9;"><h3>If you trust us:</h3></td>
				<td width="50%" style="background-color:#A4D3EE;"><h3>If you don't trust us:</h3></td>
				<tr>
				<td style="background-color:#FAEBD7;text-align:left;">
					<table class="table_align_baseline" border=0 style="vertical-align:baseline;">
						<td><h3 style="display:inline;">Wallet password: </h3></td>
						<td><input type="text" id="edt_wallet_password_buy" value="password"/></td>
					<tr>
						<td><h3 style="display:inline;">Num. of Shares to Buy: </h3></td>
						<td><input type="text" id="edt_buy_shares" onInput="adjustSpendEther();"/></td>
					<tr>
						<td><h3 style="display:inline;">Enter amount (ether): </h3></td>
						<td><input type="text" id="edt_spend_ether" onInput="adjustBuyShares();" />
							</td>
							
					</table>	
				
					<p><button class="btn-large" type="button" value="Buy!" id="btn_buy_shares"  style="min-width:300px;"
						onClick="buyShares();">Buy!</button>
				</td>
				
				
				<td width="50%" align="left" style="background-color:#D1EEEE;padding:5px;">
					<p>Thansfer (<span id="amount_ether">>>>Use calculator on the left to get amount<<<</span>) ether 
					<p>to the function "shareHolderInvest" of the following contract: 
					<p><span id="contract_address"/>
				</td>
				
				<tr>
				<td colspan=2><h2 class="header">Sell ShareHolders Shares (Shares)</h2></td>
				<tr>
				<td width="50%" style="background-color:#FFDAB9;"><h3>If you trust us:</h3></td>
				<td width="50%" style="background-color:#A4D3EE;"><h3>If you don't trust us:</h3></td>
				<tr>
				<td style="background-color:#FAEBD7;text-align:left;">
					<table class="table_align_baseline" border=0 style="vertical-align:baseline;">
						<td><h3 style="display:inline;">Wallet password: </h3></td>
						<td><input type="text" id="edt_wallet_password_sell" value="password"/></td>
					<tr>
						<td><h3 style="display:inline;">Num. of Shares to Sell: </h3></td>
						<td><input type="text" id="edt_sell_shares" onInput="adjustReceiveEther();"/></td>
					<tr>
						<td><h3 style="display:inline;">Expected amount (ether): </h3></td>
						<td><input type="text" id="edt_receive_ether" disabled="disabled"/>
							</td>
							
					</table>	
				
					<p><button class="btn-large" type="button" value="Buy!" id="btn_sell_shares"  style="min-width:300px;"
						onClick="sellShares();">Sell!</button>
				</td>
				
				
				<td width="50%" align="left" style="background-color:#D1EEEE;padding:5px;">
					<p>From your wallet (geth, Mist, etc.) call shareHolderWithdraw function of the following contract: 
					<p><span id="contract_address"/> 
					<p>and pass to it the amount of shares to withdraw 
						(<span id="amount_shares">>>>Use calculator on the left to get amount<<<</span>).
				</td>				
			</table>	

			<p><p><p>
				<table border=0 id="history" style="background-color:#FFDAB9;text-align:left;" class="zebra">
					<td colspan=6><h2 class="header">The History of ShareHolder Contract</h2></td>
					<tr>
					<td>Date</td>
					<td>Daily Profit<br>(ether)</td>
					<td>Cumulative<br>Profit (ether)</td>
					<td>Shareholders'<br>Money (ether)</td>
					<td>Number of Shares<br>Currently in Posession</td>
					<td>Token Price</td>
				</table>
				
			<p><h2 class="header">Daily Profit</h2>
			<p align="left">Amount of money (which is a specified fraction of an original contract(s) profit) to be
				distributed among share holders (14 days Moving Average).
			<p><div id="chart_daily_profit" style="height:600px;"></div>

			<p><h2 class="header">Cumulative Daily Profit</h2>
			<p align="left">Cumulative of "Daily Profit" column, the money (which is a specified fraction of an original contract(s) profit) to be
				distributed among share holders.
			<p><div id="chart_cumulative_profit" style="height:600px;"></div>

			<p><h2 class="header">ShareHolders' Money</h2>
			<p align="left">A total cost of all shares Share Holders currently posess (ethers).
			<p><div id="chart_share_holders_money" style="height:600px;"></div>

			<p><h2 class="header">Num. of Shares Carrently in Posession</h2>
			<p align="left">Number of Shares Share Holders currently own.
			<p><div id="chart_num_of_shares_in_posession" style="height:600px;"></div>
			
			<p><h2 class="header">Token Price (ether)</h2>
			<p align="left">Price of a Token in ether. The price is calculated based on profit,
				initially (and until it exceeds the min. token price) it is set to min. token price.
			<p><div id="chart_token_price" style="height:600px;"></div>

		</fieldset>	
	</main>	
</footer>
	<script>

	var nNextMinBet = 0;
	
	var Web3 = require('web3');

	var strContractAddress = "0x916629cFf963A21951a25B4AEcBd38De9814B9d8";
	var abi = [ { "constant": true, "inputs": [], "name": "getMinNextBet", "outputs": [ { "name": "nNextBet", "type": "uint256", "value": "1000000000000000" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "getInitBlock", "outputs": [ { "name": "nInitBlock", "type": "uint256", "value": "6117" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [ { "name": "addr", "type": "address" } ], "name": "getShareHolderBalance", "outputs": [ { "name": "nBalance", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "getCurrentDay", "outputs": [ { "name": "nProfit", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "getCurrentDukePaid", "outputs": [ { "name": "nPaid", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "getSharePrice", "outputs": [ { "name": "nPrice", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "getTimeStarted", "outputs": [ { "name": "nTime", "type": "uint256", "value": "1506856119" } ], "payable": false, "type": "function" }, { "constant": false, "inputs": [ { "name": "amountInShares", "type": "uint256" } ], "name": "shareHolderWithdraw", "outputs": [], "payable": false, "type": "function" }, { "constant": false, "inputs": [], "name": "withdrawDukeOwnersMoney", "outputs": [], "payable": false, "type": "function" }, { "constant": false, "inputs": [ { "name": "strNickName", "type": "string" } ], "name": "becomeDuke", "outputs": [], "payable": true, "type": "function" }, { "constant": true, "inputs": [], "name": "isOwner", "outputs": [ { "name": "bIsOwner", "type": "bool", "value": true } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "getNumberOfSharesCurrentlyInPosession", "outputs": [ { "name": "nShares", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "getDukeNickName", "outputs": [ { "name": "date", "type": "string", "value": "Vacant" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "getOwnersMoney", "outputs": [ { "name": "nAmount", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "constant": false, "inputs": [], "name": "shareHolderInvest", "outputs": [], "payable": true, "type": "function" }, { "constant": true, "inputs": [], "name": "getDailyProfit", "outputs": [ { "name": "nProfit", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "isDuke", "outputs": [ { "name": "bIsDuke", "type": "bool", "value": true } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "getCumulativeShareHoldersProfit", "outputs": [ { "name": "nProfit", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "getTradeComission", "outputs": [ { "name": "nTradeComission", "type": "uint256", "value": "1" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "getCurrentDuke", "outputs": [ { "name": "addr", "type": "address", "value": "0x0d47fef347aedfc1a8209303025e3a4ecfb75675" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "getDukeDate", "outputs": [ { "name": "date", "type": "uint256", "value": "1506856119" } ], "payable": false, "type": "function" }, { "constant": false, "inputs": [], "name": "withdrawOwner", "outputs": [], "payable": false, "type": "function" }, { "constant": false, "inputs": [ { "name": "newOwner", "type": "address" } ], "name": "transferOwnership", "outputs": [], "payable": false, "type": "function" }, { "constant": true, "inputs": [ { "name": "addr", "type": "address" } ], "name": "getShareHolderShares", "outputs": [ { "name": "nShares", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "inputs": [], "payable": false, "type": "constructor" }, { "payable": true, "type": "fallback" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "strNickName", "type": "string" }, { "indexed": true, "name": "addrCurrentDuke", "type": "address" }, { "indexed": false, "name": "nCurrentDukePaid", "type": "uint256" }, { "indexed": false, "name": "nMinNextBet", "type": "uint256" }, { "indexed": false, "name": "date", "type": "uint256" } ], "name": "updateDukeStatus", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "strNickName", "type": "string" }, { "indexed": true, "name": "addrCurrentDuke", "type": "address" }, { "indexed": false, "name": "nCurrentDukePaid", "type": "uint256" }, { "indexed": false, "name": "nMinNextBet", "type": "uint256" }, { "indexed": false, "name": "date", "type": "uint256" } ], "name": "updateDukeHistory", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "strType", "type": "string" }, { "indexed": false, "name": "addr", "type": "address" }, { "indexed": false, "name": "nAmount", "type": "uint256" }, { "indexed": false, "name": "nShares", "type": "uint256" } ], "name": "tradeEvent", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "strMessage", "type": "string" } ], "name": "errorMessage", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "nCurrentDay", "type": "uint256" }, { "indexed": false, "name": "nDailyProfit", "type": "uint256" }, { "indexed": false, "name": "nShareHoldersMoney", "type": "uint256" }, { "indexed": false, "name": "numberOfSharesCurrentlyInPosession", "type": "uint256" }, { "indexed": false, "name": "nTokenPrice", "type": "uint256" } ], "name": "profitEvent", "type": "event" } ];

	var contract = null;	
	
	var nPeriod = 14;
//	var arrDailyProfit = [];
	var arrDailyProfitEma = [];
	var arrCumulativeProfit = [];
	var arrShareHoldersMoney = [];
	var arrNumOfSharesInPosession = [];
	var arrTokenPrice = [];
		
	function init() 
	{
<!-- replacer_ethereum_init -->	
		document.getElementById("error_message").style.display = "none";
	
		// Checks Web3 support
		if(typeof web3 !== 'undefined' && typeof Web3 !== 'undefined') 
		{
			// If there's a web3 library loaded, then make your own web3
			web3 = new Web3(web3.currentProvider);
		} 
		else if (typeof Web3 !== 'undefined') 
		{
			// If there isn't then set a provider
			web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
		} 
		else if(typeof web3 == 'undefined') 
		{
			// This isn't an ethereum browser
			document.getElementById("share_holder_info").style.display = "none";
			document.getElementById("error_message").style.display = "block";
			return;    
		}
<!-- /replacer_ethereum_init -->

		var share_holder = web3.eth.contract(abi);
		contract = share_holder.at(strContractAddress);	
		
		// --- Process error message sent by ShareHolder contract
		
		var errorMessageEvent = contract.errorMessage();//{_from: web3.eth.coinbase});
		errorMessageEvent.watch(function(err, result) 
		{
			$("#error_message").css("display", "block");
			
			if(err) 
			{
				console.log(err);
				$("#error_message").text(err);
				return;
			}

			console.log(result.args);
			$("#error_message").text(result.args.strMessage);
		});
		
		// --- tradeEvent(string strType, address addr, uint nAmount, uint nShares);
		
		var tradeEvent = contract.tradeEvent();//{_from: web3.eth.coinbase});
		tradeEvent.watch(function(err, result) 
		{
			if(err) 
			{
				console.log(err);
				$("#error_message").css("display", "block");
				$("#error_message").text(err);
				return;
			}

			$("#error_message").css("display", "none");
			console.log(result.args);
			
/*			We can provide data for individual trades at no cost, but currently we'll just update contract info
			var strTradeType = result.args.strType;
			var strTraderAddress = result.args.addr;
			var nAmount = result.args.nAmount.shift(-18).toNumber();
			var nShares = result.args.nShares;
*/						
			var nShareHolderBalance = contract.getShareHolderBalance.call(web3.eth.coinbase).shift(-18).toNumber();
			var nShareHolderShares = contract.getShareHolderShares.call(web3.eth.coinbase);
			var nCumulativeShareHoldersProfit = contract.getCumulativeShareHoldersProfit.call().shift(-18).toNumber();
			var nNumberOfSharesCurrentlyInPosession = contract.getNumberOfSharesCurrentlyInPosession.call();
			var nOwnersMoney = contract.getOwnersMoney.call().shift(-18).toNumber();
			var nInitBlock = contract.getInitBlock.call();
			var nTokenPrice = contract.getSharePrice().shift(-18).toNumber();
		
			fill_share_holder_info(nShareHolderBalance, nShareHolderShares, nCumulativeShareHoldersProfit, nNumberOfSharesCurrentlyInPosession,
				nTokenPrice, nOwnersMoney);

		});

		//---
		
		var nShareHolderBalance = contract.getShareHolderBalance.call(web3.eth.coinbase).shift(-18).toNumber();
		var nShareHolderShares = contract.getShareHolderShares.call(web3.eth.coinbase);
		var nCumulativeShareHoldersProfit = contract.getCumulativeShareHoldersProfit.call().shift(-18).toNumber();
		var nNumberOfSharesCurrentlyInPosession = contract.getNumberOfSharesCurrentlyInPosession.call();
		var nOwnersMoney = contract.getOwnersMoney.call().shift(-18).toNumber();
		var nInitBlock = contract.getInitBlock.call();
		var nTokenPrice = contract.getSharePrice().shift(-18).toNumber();
	
		//var rawDate = new Date(contract.getShareDate() * 1000).toISOString();
		//var date = rawDate.slice(0, 10) + " " + rawDate.slice(11, 19);
		
		fill_share_holder_info(nShareHolderBalance, nShareHolderShares, nCumulativeShareHoldersProfit, nNumberOfSharesCurrentlyInPosession,
			nTokenPrice, nOwnersMoney);
		
		// ---
		
		var shareHolderHistory = contract.profitEvent({_from: web3.eth.coinbase}, {fromBlock: contract.getInitBlock(), toBlock: 'latest'});
		shareHolderHistory.watch(function(err, result) 
		{
			if(err) 
			{
				console.log(err);
				$("#error_message").css("display", "block");
				$("#error_message").text(err);
				return;
			}

			$("#error_message").css("display", "none");
			
			// ---
			
			var nCurrentDay = result.args.nCurrentDay.toNumber();
			var nDailyProfit = result.args.nDailyProfit.shift(-18).toNumber();
			var nShareHoldersMoney = result.args.nShareHoldersMoney.shift(-18).toNumber();
			var numberOfSharesCurrentlyInPosession = result.args.numberOfSharesCurrentlyInPosession.toNumber();
			var nTokenPrice = result.args.nTokenPrice.shift(-18).toNumber();
			
			add_share_holder_info_to_history(nCurrentDay, nDailyProfit, nShareHoldersMoney, numberOfSharesCurrentlyInPosession, nTokenPrice);
		});
		// ---
		
		window.setTimeout(function() 
		{
			var nCurrentDay = contract.getCurrentDay.call().toNumber();
			var nTimeStarted = contract.getTimeStarted.call().toNumber();
			var date = new Date((nTimeStarted + 86400 * nCurrentDay) * 1000);

			var nDailyProfit = contract.getDailyProfit.call().shift(-18).toNumber();
//			arrDailyProfit.push({x:date, y:nDailyProfit});
			arrDailyProfitEma.push({x:date, y:getNextEMA(nPeriod, nDailyProfit, arrDailyProfitEma)});
			
			var dCumulativeProfit = arrCumulativeProfit.length == 0 ? 0 : arrCumulativeProfit[arrCumulativeProfit.length - 1].y;
			arrCumulativeProfit.push({x:date, y:dCumulativeProfit + nDailyProfit});
			
			arrShareHoldersMoney.push({x:date, y:contract.getCumulativeShareHoldersProfit.call().shift(-18).toNumber()});
			
			arrNumOfSharesInPosession.push({x:date, y:contract.getNumberOfSharesCurrentlyInPosession.call().toNumber()});
			
			arrTokenPrice.push({x:date, y:contract.getSharePrice.call().shift(-18).toNumber()});
			
			chart();
		}, 3000);

	}	
	
	// ------
	
	function getNextEMA(nPeriod, dNewData, array)
	{
		var dMa;
		if(nPeriod >= array.length)
			dMa = dNewData;
		else
		{
			var dLastValue = 0;
			for(var i = array.lenght - nPeriod - 1; i < array.lenght; i++)
				dLastValue += array[i];
			
			dLastValue /= nPeriod;

			dMa = dNewData * (2.0 / (nPeriod + 1)) + dLastValue * (1 - 2.0 / (nPeriod + 1));
		}
		
		return dMa;
	}	
	// ------
	
	function chart()
	{
		if(arrDailyProfitEma.length == 1)
			return;
	
		var chart = new Chartist.Line('#chart_daily_profit', {
		  series: [
//			{
//			  name: 'Daily Profit',
//			  data: arrDailyProfit
//			},
			{
			  name: 'Daily Profit EMA(' + nPeriod + ')',
			  data: arrDailyProfitEma,
			}
		  ],
		}, 
		{
			axisX: 
			{
				type: Chartist.FixedScaleAxis,
				divisor: 5,
				labelInterpolationFnc: function(value) 
				{
					return moment(value).format('MMM D YY');
				}
			},
			axisY:
			{
				offset: 80,
			},			
			showArea: true
		});
		
		// ---
		
		var chart = new Chartist.Line('#chart_cumulative_profit', {
		  series: [
			{
			  name: 'Cumulative Profit',
			  data: arrCumulativeProfit,
			}
		  ],
		}, 
		{
			axisX: 
			{
				type: Chartist.FixedScaleAxis,
				divisor: 5,
				labelInterpolationFnc: function(value) 
				{
					return moment(value).format('MMM D YY');
				}
			},
			axisY:
			{
				offset: 80,
			},			
			showArea: true
		});		
		
		// ---
		
		var chart = new Chartist.Line('#chart_share_holders_money', {
		  series: [
			{
			  name: 'Share Holders Money',
			  data: arrShareHoldersMoney,
			}
		  ],
		}, 
		{
			axisX: 
			{
				type: Chartist.FixedScaleAxis,
				divisor: 5,
				labelInterpolationFnc: function(value) 
				{
					return moment(value).format('MMM D YY');
				}
			},
			axisY:
			{
				offset: 80,
			},			
			showArea: true
		});		
		
		// ---
		
		var chart = new Chartist.Line('#chart_num_of_shares_in_posession', {
		  series: [
			{
			  name: 'Num. of Shares Currently in Posession',
			  data: arrNumOfSharesInPosession,
			}
		  ],
		}, 
		{
			axisX: 
			{
				type: Chartist.FixedScaleAxis,
				divisor: 5,
				labelInterpolationFnc: function(value) 
				{
					return moment(value).format('MMM D YY');
				}
			},
			axisY:
			{
				offset: 80,
			},			
			showArea: true,
			//low:0
		});		
		
		// ---
		
		var chart = new Chartist.Line('#chart_token_price', {
		  series: [
			{
			  name: 'Token Price',
			  data: arrTokenPrice,
			}
		  ],
		}, 
		{
			axisX: 
			{
				type: Chartist.FixedScaleAxis,
				divisor: 5,
				labelInterpolationFnc: function(value) 
				{
					return moment(value).format('MMM D YY');
				}
			},
			axisY:
			{
				offset: 80,
			},			
			showArea: true,
			//low:0
		});		
		
	}

	// ------	
	
	function fill_share_holder_info(nShareHolderBalance, nShareHolderShares, nCumulativeShareHoldersProfit, nNumberOfSharesCurrentlyInPosession,
			nTokenPrice, nOwnersMoney)
	{
		var strStatus = 
			"<table border=0><td colspan=2 style='background-color:#A4D3EE;'><h3 style='display:inline;'>Share Holder Info</h3></td>" + 
			"<tr><td>Your Address: </td><td>" + web3.eth.coinbase + 
			"</td><tr><td>Your Shares: </td><td>" + nShareHolderShares +
			"</td><tr><td>Your Balance: </td><td>" + nShareHolderBalance + " ether" + 
			"</td><tr><td colspan=2 style='background-color:#A4D3EE;'><h3 style='display:inline;'>Contract Info</h3></td>" +
			"<tr><td>Contract Address: </td><td>" + strContractAddress +
			"<tr><td>Share Holders Profit: </td><td>" + nCumulativeShareHoldersProfit + 
			"</td><tr><td>Token Price: </td><td>" + nTokenPrice + " ether" + 
			"</td><tr><td>Shares Currently in Posession: </td><td>" + nNumberOfSharesCurrentlyInPosession + "</td><table>";
		
		$('#share_holder_info').html(strStatus);
		$('#contract_address').html(strContractAddress);
	}

	// ------
	
	function add_share_holder_info_to_history(nCurrentDay, dDailyProfit, nShareHoldersMoney, numberOfSharesCurrentlyInPosession, nTokenPrice)
	{
		var nTimeStarted = contract.getTimeStarted.call().toNumber();
		var date = new Date((nTimeStarted + 86400 * nCurrentDay) * 1000);
		var strDate = date.toISOString().slice(0, 10);
		var dCumulativeProfit = arrCumulativeProfit.length == 0 ? 0 : arrCumulativeProfit[arrCumulativeProfit.length - 1].y + dDailyProfit;	
		
		var strRecord = 
			"<tr><td>" + strDate + "</td><td>" + dDailyProfit + "</td><td>" + dCumulativeProfit + "</td><td>" + nShareHoldersMoney + 
				"</td><td>" + numberOfSharesCurrentlyInPosession + "</td><td>" + nTokenPrice + "</td>";
		
		var strHtml = $('#history').html() + strRecord;
		$('#history').html(strHtml);
		
		// ---
		
///		arrDailyProfit.push({x:date,y:dDailyProfit});
		arrDailyProfitEma.push({x:date,y:getNextEMA(nPeriod, dDailyProfit, arrDailyProfitEma)});
		
		arrCumulativeProfit.push({x:date, y:dCumulativeProfit});
		
		arrShareHoldersMoney.push({x:date, y:nShareHoldersMoney});
		
		arrNumOfSharesInPosession.push({x:date, y:numberOfSharesCurrentlyInPosession});
		
		arrTokenPrice.push({x:date, y:nTokenPrice});
	}

	// ------
	
	function sellShares()
	{
		document.getElementById("error_message").style.display = "block";
		
		// ---
		
		var nShares = $('#edt_sell_shares').val();
		if (isNaN(nShares) || !nShares)
		{
			document.getElementById("error_message").innerHTML = "Wrong number of shares";
			return;
		}	
		
		var nSharesAvailable = contract.getShareHolderShares.call(web3.eth.coinbase).toNumber();
		if(nSharesAvailable < nShares)
		{
			document.getElementById("error_message").innerHTML = "There are only " + nSharesAvailable + " available for sale";
			return;
		}
		// ---
		
		var strPassword = $('#edt_wallet_password_sell').val();
		
		// ---
		var nPayForGas = 1000000;
		var nPayForGasEther = web3.fromWei(nPayForGas, "ether");

		// ---
		var account = web3.eth.accounts[0];
		var acctBal = web3.fromWei(web3.eth.getBalance(account), "ether").toNumber();
		if(acctBal < nPayForGasEther)
		{
			document.getElementById("error_message").innerHTML = "Insufficient funds to cover gas cost;";
			return;
		}

		// ---
		web3.personal.unlockAccount(account, strPassword, 3000);
		
		contract.shareHolderWithdraw.sendTransaction(nShares, {from: web3.eth.coinbase, value: 0, gas: nPayForGas },
			function(err, transactionHash) 
			{
				if (err)
					document.getElementById("error_message").innerHTML = err;
			});
		
		var date = new Date().toISOString().slice(0, 10) + " " + new Date().toISOString().slice(11, 19);
		document.getElementById("error_message").innerHTML  = "Date: " + date + "; Transaction sent.";
	}
	
	// ------
	
	function buyShares()
	{
		// In order to send transactions to an account, I have to unlock it.
		// TBD: allow the user to select a wallet, instead of using the first one.

		document.getElementById("error_message").style.display = "block";
		
		// ---
		
		if (isNaN($('#edt_buy_shares').val()))
		{
			document.getElementById("error_message").innerHTML = "Wrong number of shares";
			return;
		}	
		
		// ---
		var strPassword = $('#edt_wallet_password_buy').val();
		// ---
		var nEther = $('#edt_spend_ether').val();
		if (isNaN(nEther))
		{
			document.getElementById("error_message").innerHTML = "Not a number";
			return;
		}	
				
		// ---
		
		var nPayForGas = 1000000;
		var nPayForGasEther = web3.fromWei(nPayForGas, "ether");
		
		var account = web3.eth.accounts[0];
		var acctBal = web3.fromWei(web3.eth.getBalance(account), "ether").toNumber();
		if(acctBal < nEther + nPayForGasEther)
		{
			document.getElementById("error_message").innerHTML = "Insufficient funds, required amount is " + nEther + " ether";
			return;
		}
				
		web3.personal.unlockAccount(account, strPassword, 3000);
		
		var nPay = web3.toWei(nEther, "ether");
		contract.shareHolderInvest.sendTransaction({from: web3.eth.coinbase, value: nPay, gas: nPayForGas },
			function(err, transactionHash) 
			{
				if (err)
					document.getElementById("error_message").innerHTML = err;
			});
		
		var date = new Date().toISOString().slice(0, 10) + " " + new Date().toISOString().slice(11, 19);
		document.getElementById("error_message").innerHTML  = "Date: " + date + "; Transaction sent.";
	}
	
	// ------
	
	function adjustSpendEther()
	{
		var nShares = $('#edt_buy_shares').val();
		nShares = nShares.replace(/[^\d]/,'');
		$('#edt_buy_shares').val(nShares);
		
		var nTokenPrice = contract.getSharePrice().shift(-18).toNumber();
		var nEther = Math.ceil(10000000 * nShares * nTokenPrice / (1 - 0.01 * contract.getTradeComission())) / 10000000;
		$('#edt_spend_ether').val(nEther);
		$('#amount_ether').html(nEther);
	}
	
	function adjustReceiveEther()
	{
		var nShares = $('#edt_sell_shares').val();
		nShares = nShares.replace(/[^\d]/,'');
		$('#edt_sell_shares').val(nShares);
		
		var nTokenPrice = contract.getSharePrice().shift(-18).toNumber();
		var nEther = 0;
		if(nTokenPrice != 0)
			nEther = Math.ceil(10000000 * nShares * nTokenPrice / (1 - 0.01 * contract.getTradeComission())) / 10000000;
		
		$('#edt_receive_ether').val(nEther);
		$('#amount_shares').html(nShares);
	}
	
	// ------
	
	function adjustBuyShares()
	{
		var nEther = $('#edt_spend_ether').val();
		nEther = nEther.replace(/[^\d\.]/,'');
		if(nEther.split(".").length - 1 > 1)
		{
			var nFirstDotIdx = nEther.indexOf('.');
			
			var strStart = nEther.slice(0, nFirstDotIdx + 1);
			var strEnd = nEther.slice(nFirstDotIdx + 1, nEther.length);
			strEnd = strEnd.replace('.', '');
			nEther = strStart + strEnd;
		}
		
		$('#edt_spend_ether').val(nEther);
		
		var nTokenPrice = contract.getSharePrice().shift(-18).toNumber();
		if(nTokenPrice == 0)
			$('#edt_buy_shares').val(0);
		else	
		{
			var nShares = Math.floor((1 - 0.01 * contract.getTradeComission()) * nEther / nTokenPrice);
			if(nShares < 1)
				$('#edt_buy_shares').val(0);
			else	
				$('#edt_buy_shares').val(nShares);	
		}	
		// ---
		$('#amount_ether').html(nEther);
	}
	
	// ------
	
	</script>
	
</body>	
</html>

Note a strange looking <--- replacer_ethereum_show_hide --> tag. You do not have to use it, but this is something that might prove useful. See, both duke_of_ether.htm and share_holder.htm files include the same show_hide() function. There are many ways to avoid redundancy, for example, by using JQuery's "load". Or "include". Or... However, all these approaches have security limitations, in other words, if your customer chooses to save your HTML locally (for example, in order to check it once and to stop worrying about hackers altering an online version)... well, it wouldn't work locally as it is based on AJAX.

An alternative approach is to use some kind of a bulk replacement tool: I used Replacer, a tool allowing to insert and update when necessary, blocks of code marked with <-- replacer... --> tags. The tool allows you to apply bulk changes to all files in a folder.







(C) snowcron.com, all rights reserved

Please read the disclaimer