Supercharging Our Ethereum DApp with Ethers.js and Angular NgRx v8
This is Part V in our work-in-progress project demonstrating how to build an Escrow smart contract DApp with Angular and NgRx
One in the single and the first degree
One above one for eternity
No unit or triad, source or cause for the one, super one, this is the one above those
— “One Above One” by Vitalic
What We’re Going to Explore
In the previous article, we discussed how to create a new instance of the purchase contract. In this post, we will walk through the code that lets us implement the following functionalities of our FleaMarket Escrow smart contract:
- Display the list of all purchase contract widgets
- Implement the search for contract
- Load the purchase contract
- Abort contract by the seller
- Remove contract by the seller
- Confirm purchase by the buyer
- Confirm delivery by the buyer
The source of the inspiration to build this DApp was taken from this article. The full code for this blog is located in my GitHub repository:
1. Load Purchase Widget Collection
Routes array file for the lazy-loaded module
P2pBazaarModule has the following context:
It introduces the route-guard
ProductsLoadedGuard that controls the navigation to the top-level route
/products. The guard will wait for the collection of the purchase contract widgets to load from the blockchain by observing the
loadedproperty of the entity state:
If this value is false, the guard will dispatch the
loadProducts() action to the store effect
The effect then handles the call to the service method
getPurchaseContractList() to fetch those pieces of data:
We call the smart contract
getContractCount() function to retrieve the total amount of the purchase contract instances. Then, we use the combination of the
forkJoin operators to execute multiple requests to the smart contract to retrieve the purchase widget collection.
Once the method
getPurchaseContractList() gets resolved with the list of the
PurchaseWidgetModel entities, we pass it into the payload of the
loadProductsSuccess() action and dispatch it back to the entity store.
The entity state reducer then calls the entity adapter method
addAll() to replace the entity state with a new collection. The reducer will also change the state property
loaded to true and permit access to the
/products route becomes active, it will also activate the route component
ViewProductCollection. Here we implement the search by the contract key functionality and hook up the
getAllProducts selector value into the
As a result, the component template should render the collection of our purchase widgets as follows:
Each item in the widget collection includes the key and the address of the corresponding purchase contract. When a new instance of the purchase contract is created, the new purchase widget model is added to the entity store. This will trigger the
getAllProducts selector to emit a new value and cause the content of the list to get updated.
2. View Purchase Contract Details
When users click an item in the widget list, the app will navigate to the
ViewPurchaseContractComponent and load the selected purchase contract’s details from the blockchain:
Let’s take a look at the code that drives that piece of logic. In
ViewPurchaseContractComponent, we declare two public observable fields:
We use the
selectedPurchaseContract$ selector to pull out the purchase contract properties from the Ethereum blockchain and the
image$ selector to load the corresponding product image from the IPFS blockchain.
selectedPurchaseContract$ in the
ngOnInit() life-cycle hook method as follows:
It is hooked-up into the feature selector
Notice that the
getSelectedProduct is a combined selector that returns a specific member from the product widgets collection based on the product key which we specify in the route parameter. We then dispatch the widget contract address with the
loadPurchaseContract action to the side effect:
In the body of the effect, we use the
switchMap operator to switch to a new inner observable of type
PurchaseContractModel, which is emitted from the service method
loadPurchaseContract. This method loads the purchase contract from the blockchain as follows:
We use the
ethers.js library to retrieve the smart contract observable properties and apply the
zip operator to combine them into a new observable object
We then dispatch the
loadPurchaseContractSuccess action, providing the
PurchaseContractModel object as the payload to update the feature state property
Finally, we use the
switchMap again to observe the selected
PurchaseContractModel object using the
select() method on the store, providing the
getSelectedPurchaseContract selector function.
Let’s quickly review the code for another component’s observable
image$ that binds the purchase contract image to the template.
Notice that the image isn’t stored in the smart contract. We only store the corresponding IPFS hash value. The hash can be used to retrieve the image context from a different location on a decentralized file system called IPFS.
image$ selector property is defined in the in
We set it by observing the selected
PurchaseContractModel object using the
select() method and providing the
getSelectedPurchaseContract selector function. Then, the IPFS hash gets extracted and dispatched with
download_image action to the
downloadImage$ effect like this:
We use the
switchMap() operator to extract the IPFS hash value and then call the
getFile method in
IpfsDaemonService to retrieve the image Blob object from the IPFS node.
We are then dispatching the
download_image_success action to the store, providing the image Blob object as the payload.
It will cause the store reducer to update the feature state and trigger the
selector() function to emit a new value. Finally, we use the template reference variable
#ipfsImage to anchor the Blob image to its
this.imageRef.nativeElement.src = this.windowRef.URL.createObjectURL(this.image);
3. Abort Contract
In our DApp, we allow the seller to cancel a purchase contract. This is only allowed if the contract is in the state Created.
If the seller decides to abort the contract, the amount of ETH staked by the seller will be refunded back to him. Let’s take a look at what happened when the seller clicked on the flash-on icon.
We have created an effect that listens for the
Here, we use an appropriate method from the
PurchaseContractService to perform the asynchronous call on the smart contract using
Upon successful execution of the transaction on the blockchain, the effect then broadcasts the
abortSelectedPurchaseContractSuccess action. This action is picked up by another non-dispatching effect:
The sole purpose of this side effect is to force our application to reload the selected purchase contract from the blockchain and update the template. We do it by refreshing the route state by quickly changing the route parameter and then reversing it back. It will trigger the route parameter selector to emit the same selected product key value again.
Finally, we display the SnackBar notification to our users. The item now shows the balance of 0.0 ETH and the status changed to Canceled.
4. Remove Contract
After a contract has been canceled, the seller also has the option to completely remove it from the product collection.
When the user clicks on the trash icon, we‘ll dispatch the
removePurchaseContract action to the store, providing the product key as the payload. Then, it follows the standard NgRx Redux pattern. We have an effect that is listening to this action.
It will invoke the
removePurchaseContract() method on the
FleaMarketContractService service and call the smart contract method
ethers.js to initiate the transaction on the blockchain.
We’ll wait for the successful transaction execution and dispatch the
removePurchaseContractSuccess action to the store.
Going back to the store, we are using the NgRx entity adapter to remove the corresponding
PurchaseWidgetMode object from the entity state.
We then pass the action to another effect to navigate to the top-level feature route
/p2p-bazaar. Here is how it looks:
5. Buyer Makes Purchase
Let’s play as a buyer and switch to another MetaMask account that uses the buyer’s wallet.
According to the logic of the smart contract, to be able to purchase the item the buyer needs to deposit a double of the amount of ETH the item costs. The buyer then hits the Purchase button.
Here again, we use the powers of the NgRx Store. We have a dedicated side effect that manages the purchase logic.
Upon closing the dialog that confirms our intention to purchase the item, we invoke the service method
We send the transaction to deposit ethers by calling the
buyerConfirmPurchase() method on the contract using the
ethers.js library. Once we receive the transaction receipt from the blockchain, we return the control-flow logic back to the effect and dispatch the
confirmBuySuccess action to refresh the contract’s information on the page.
If everything goes as planned, we should see two important changes.
The first change is the contract now shows a balance of ETH equals 4x the value of the product price. It is the 2x from the seller and 2x from the buyer.
The second one is the status has been changed to Locked. At this point, the smart contract no longer allows the buyer nor the seller to pull back from the deal.
6. Confirming Delivery
This is the final step in our application.
After a buyer has staked the right amount of ETH into the contract, the seller is obligated to deliver the purchased item. Upon receiving the item, the buyer confirms the delivery by clicking on the Confirm Delivery button.
This is what happens when the buyer clicks the confirmation button. First, we dispatch the
confirmDelivery action to the store. Then, we use the
From the side effect, we handle the call to the service method.
Here we execute the
buyerConfirmReceived() method on the smart contract and wait for the contract to emit the transaction receipt. Finally, we dispatch the
confirmDeliverySuccess action to refresh the contract’s properties on the component template.
As we would expect, the contract item now shows the status Inactive and does not hold any balance ETH. According to the business logic built into the smart contract, all collateral funds staked in the contract have to be refunded back to the seller and buyer.
🐯 Thanks for reading this article. I really enjoyed my experience in building this DApp.
- ethers.js — Version 4.0 Release, by RicMoo
- Smart Contract Explained by Demonstration, by Jackson Ng
- Creating Purchase Contract with Ethers.js Using Angular NgRx v8, by Alex Yevseyevich
- Managing IPFS Image Uploads With Angular NgRx v8, by Alex Yevseyevich
- Angular NgRx Material Starter, by Tomas Trajan
- DApp development with ethers.js, by Sameep Singhania
- Angular + NgRx: Refactor Module, by Brian Love
- How Our Escrow Smart Contract Works, by LocalEthereum’s blog
- Ethereum Marketplace Step-by-Step Tutorial, by Dapp University