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

Alex Yevseyevich
Oct 2 · 9 min read

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

  • 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

It introduces the route-guardProductsLoadedGuard 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 loadProducts$:

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 mergeMap and 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 thePurchaseWidgetModel 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.

Since 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 products$ observable:

As a result, the component template should render the collection of our purchase widgets as follows:

The widget collection

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

Purchase Contract Details

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.

We set selectedPurchaseContract$ in the ngOnInit() life-cycle hook method as follows:

It is hooked-up into the feature selectorgetSelectedProduct:

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 objectPurchaseContractModel.

We then dispatch the loadPurchaseContractSuccess action, providing the PurchaseContractModel object as the payload to update the feature state property selectedPurchaseContrac.

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 observableimage$ 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.

The image$ selector property is defined in the in ngOnInit()as follows:

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 thegetFile 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 src property:

this.imageRef.nativeElement.src = this.windowRef.URL.createObjectURL(this.image);

3. Abort Contract

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 theabortSelectedPurchaseContract action.

Here, we use an appropriate method from the PurchaseContractService to perform the asynchronous call on the smart contract using ethers.js.

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.

Canceled contract

4. Remove Contract

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 removeContractByKey() using 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:

Removing contract from the collection

5. Buyer Makes Purchase

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.

Confirming purchase

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 confirmPurchase().

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.

Purchase confirmed

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

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.

Buyer confirms delivery

This is what happens when the buyer clicks the confirmation button. First, we dispatch the confirmDelivery action to the store. Then, we use the confirmDelivery$ effect.

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.

Better Programming

Advice for programmers.

Thanks to Daniel Yevseyevich

Alex Yevseyevich

Written by

Enjoy studying modern web and blockchain smart contract technologies. The current focus of interest: Angular, NgRx, Ethereum DApps

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade