Smart Contract Escrow Dapp – Buyer View

This is the second of a 2-part article that explains the codes behind Smart Contract Explained by Demonstration. You may read part one here.

In this part, I will focus on the UI for the buyer. The StartEscrow smart contract used by the DApp can be found here and I have previously written an article about how it works here

You may find the complete buyer’s source codes in my Github repository here. In the sections that follow, I will refer to the line numbers from my codes in Github.

Initializations

Here’s where I perform the initializations.

var currentContractAddress;
var Purchase;
    
$(document).ready( function () {
   $("#confirmpurchasediv").hide();
   $("#confirmdeliverdiv").hide();
} );

In lines 71 to 77, the currentContractAddress variable is declared to keep the address of my Contract. The Purchase variable will store the Purchase contract. I also hide the confirmpurchasediv and confirmdeliverdiv panels. Read lines 44-56 and 58-68 to understand what the panels do.  These panels draw the user interfaces for the buyer to interact with the contract. They are initially hidden until the buyer loads the item that he wishes to purchase.

if (typeof web3 !== 'undefined') {
         web3 = new Web3(web3.currentProvider);
} else {
         // set the provider you want from Web3.providers
         web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}

Lines 79 to 84 initialize web3. Here, the web3 library checks if the UI is executed from a web browser that has a web3 provider. If you have MetaMask installed on your Chrome browser or are running this page within Status, the mobile app for Dapps execute, this will work. Or else, web3 will attempt to look for a running Web3 provider in your machine to use.

web3.eth.defaultAccount = web3.eth.accounts[0];
var PurchaseContract = web3.eth.contract(
[
  {
	"constant": true,
	"inputs": [],
	"name": "seller",
	"outputs": [
     {
		.... <truncated>
	]      
);

Loading Contract

Lines 86 to 221 states the Application Binary Interface (ABI) for PurchaseContract. The ABI of a Smart Contract can be obtained by copying and pasting the Smart Contract codes into Remix and compiling your contract.

$("#button").click(function() {
   	currentContractAddress = $("#contractAddress").val();
   	$("#contractaddress").html("Contract: " + $("#contractAddress").val());
   	loadContractDetail($("#contractAddress").val());
});

Lines 223 to 228 declare what happens when the buyer presses the [Get Contract] button. The buyer enters the address of the Smart Contract address for the item that the seller is selling. This contract will then be loaded by the loadContractDetail function.

    function loadContractDetail(address){
    	Purchase = PurchaseContract.at(address);
    
    	Purchase.value(function(error, result){
   			if(!error){
            	console.log(JSON.stringify(result));
       			$("#itemvalue").html("Price: " + Number(result)/1000000000000000000);
            }
   			else{
       			console.error(error);
            }
		})/18;
    
    	Purchase.seller(function(error, result){
   			if(!error){
            	console.log(JSON.stringify(result));
       			$("#seller").html("Seller: " + result);
            }
   			else{
       			console.error(error);
            }
		});
    
        Purchase.buyer(function(error, result){
   			if(!error){
            	console.log(JSON.stringify(result));
       			$("#buyer").html("Buyer: " + result);
            }
   			else{
       			console.error(error);
            }
		});
        Purchase.ipfsHash(function(error, result){
   			if(!error){
            	console.log(JSON.stringify(result));
       			$("#ipfshash").html("IPFS Hash: " + result);
       			$("#ipfsimage").html("<img src=https://gateway.ipfs.io/ipfs/" + result + " width='400'>");
            }
   			else{
       			console.error(error);
            }
		});
		
        Purchase.state(function(error, result){
   			if(!error){
            	console.log(JSON.stringify(result));
            	if (Number(result) === 0){
                	$("#state").html('<span class="badge progress-bar-success">Created</span>');
                	$("#confirmpurchasediv").show();
        			$("#confirmdeliverdiv").hide();
                }
            	else if (Number(result) === 1){
                	$("#state").html('<span class="badge progress-bar-info">Locked</span>');
                	$("#confirmpurchasediv").hide();
        			$("#confirmdeliverdiv").show();
                }
            	else {
                	$("#state").html('<span class="badge progress-bar-danger">Inactive</span>');      
                	$("#confirmpurchasediv").hide();
        			$("#confirmdeliverdiv").hide();
                }
            }
   			else{
       			console.error(error);
            }
		});
    }

The loadContractDetail function is declared between lines 280 and 347. It calls the getter functions of the Purchase contract and displays the corresponding information on the screen.

  • Purchase.value: This displays the value that the seller is selling this item for. Notice that I converted the value of the item from wei to ether. 118 wei gives you one ether.
  • Purchase.seller: This displays the wallet address of the seller.
  • Purchase.buyer: This displays the wallet address of the buyer. If this item has no buyer, then it stays empty until a buyer comes along.
  • Purchase.ipfsHash: This displays the ipfsHash value of the item’s image. Item images are stored in ipfs. Here, I take the ipfs hash value of the image and displays the image using an ipfs gateway.
  • Purchase.state: I display the state of this purchase contract here. A purchase contract can have 3 possible states: created, when it is available for sale, locked, when it is in the middle of a purchase process, and inactive, when it has been sold. I also turns on and off the confirmpurchasediv and confirmdeliverdiv panels depending on whether the user should or should not be able to interact with them. For example, an item in its “created” status will have its confirmpurchasediv turned on and confirmdeliverdiv turned off.

Confirming Purchase

var newConfirmPurchaseEvent;
  $("#purchaseButton").click(function() {	
     $("#loader").show();
      Purchase.confirmPurchase({value: $("#price").val()*1000000000000000000, gas: 1000000, gasPrice: web3.toWei(2, 'gwei')}, function(error, result){
	     if(!error){
       		console.log(JSON.stringify(result));
			newConfirmPurchaseEvent = Purchase.PurchaseConfirmed();
    		newConfirmPurchaseEvent.watch(function(error, result){
         	if (!error)
         	{
           		$("#loader").hide();
         		loadContractDetail(currentContractAddress);
         	} 
           	else {
           		$("#loader").hide();
           		console.log(error);
         	}
            });
      }
   	 else{
     		console.error(error);
     }
	});
});

Lines 229 to 252 process what happens when the buy hits [Purchase].

I run Purchase.confirmPurchase on the smart contract and watch and wait for the Smart contract to emit a confirmation event using newConfirmPurchaseEvent.watch(function(error, result). Once emitted, I run loadContractDetail() again to refresh this contract’s information on the page.

Confirming Delivery

var newConfirmDeliverEvent;
$("#deliverButton").click(function() {	
  $("#loaderdeliver").show();
  Purchase.confirmReceived({gas: 1000000, gasPrice: web3.toWei(2, 'gwei')}, function(error, result){
      if(!error){
      	  console.log(JSON.stringify(result));
	      newConfirmDeliverEvent = Purchase.ItemReceived();
     	  newConfirmDeliverEvent.watch(function(error, result){
        		if (!error)
        		{
        	       $("#loaderdeliver").hide();
                   loadContractDetail(currentContractAddress);
                } 
                else {
                  $("#loaderdeliver").hide();
                  console.log(error);
                }
       });
      }
      else{
        console.error(error);
      }
   });
});

Lines 254 to 277 processes what happens when the user presses [Deliver].

I run Purchase.confirmReceived() on the smart contract and watch and wait for the contract to emit newConfirmDeliverEvent.watch(function(error, result)) when it is done executing. 

Once emitted, I run loadContractDetail() again to refresh this contract’s information on the page.

Finishing Off

I wrote several articles about Escrow Services Smart Contracts throughout 2018  because I believed that there ought to be more well illustrated use cases for Smart Contracts on the Blockchain platforms. The world of cryptocurrencies and blockchain needs more well documented business cases with executable examples and less whitepapers and ICOs.

Here’s a summary of everything I have written so far:

Photo by Clark Young on Unsplash