Estimate transaction costs

This page describes the actions happening in the contract/fixed-supply/deploy route.

In the previous step we redirected our users to the contract/fixed-supply/deploy route. This route works with the ContractDeploy module and its components.

Creating a fixed-supply component for the contract-deploy module

The next step is to follow the same pattern of the contract-create module for creating a component that will handle our fixed-supply crowdsale deployment.

ng generate component contract-deploy/fixed-supply --project=simple-ico

Display the fixed-supply.component.html in the deploy route

https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/container.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { ContractDeploymentFactory } from '@factory/contract-deployment.factory';

import { FixedSupplyDeployment } from '@factory/fixed-supply.deployment';

@Component({
  selector: 'app-container',
  templateUrl: './container.component.html',
  styleUrls: ['./container.component.css']
})
export class ContainerComponent implements OnInit {

  contractType: string

  FixedSupplyDeployment: string = FixedSupplyDeployment._type
  
  //...
https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/container.component.html
<main id="contract-deploy">

  <app-fixed-supply *ngIf="FixedSupplyDeployment == contractType"></app-fixed-supply>

</main>

Get the deployer instance in the FixedSupplyComponent

https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/fixed-supply/fixed-supply.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { ContractDeploymentFactory } from '@factory/contract-deployment.factory';
import { ContractDeployment } from '@factory/contract-deployment';
import { Crowdsale } from '@model/crowdsale.model';
import { Token } from '@model/token.model';

@Component({
  selector: 'app-fixed-supply',
  templateUrl: './fixed-supply.component.html',
  styleUrls: ['./fixed-supply.component.css']
})
export class FixedSupplyComponent implements OnInit {

  deployer: ContractDeployment

  token: Token

  crowdsale: Crowdsale

  constructor(
    private contractFactory: ContractDeploymentFactory) {

    this.deployer = contractFactory.deployer
  }
  
  ngOnInit() {
    this.token = this.deployer.getToken()
    this.crowdsale = this.deployer.getCrowdsale()

    this.init()
  }
}

As you may have noticed, we are getting the token and the crowdsale instance through the deployer methods. These methods are simply doing the following:

export abstract class ContractDeployment {
  
  getToken(){
    return this.token
  }

  getCrowdsale(){
    return this.crowdsale
  }
  
  //...

And deliver the same token and crowdsale contract instances we created in the contract/fixed-supply/create route.

These instances contain the NAME, SYMBOL, SUPPLY, PRICE and BENEFICIARY ADDRESS values we set in the previous step.

Estimating the gas costs of each contract transaction

Transactions that change the state of the smart contract imply a transaction cost represented as GAS and GAS PRICE. This is native to the Ethereum network and makes it sustainable.

Web3JS provides a way of estimating gas costs without actually changing the state of the contract. It does it by simulating the contract method call and returns a TransactionObject:

// Gas estimation
myContract.deploy({
    data: '0x12345...',
    arguments: [123, 'My String']
})
.estimateGas(function(err, gas){
    console.log(gas);
});

A note about transparency

The SCUI needs to be very transparent with the end-user, and remind them that the underlying processes may consume their ETH, which is real money.

This is why we will present the user the costs of:

  • Deploying the ERC20 token contract

  • Deploying the fixed-supply crowdsale contract

  • Transferring the token supply to the crowdsale

Calculating the costs of deploying the ERC20 token contract

contract/fixed-supply/deploy #step-1

Following the ngOnInit() method of the contract-deploy/fixed-supply/fixed-supply.component.ts, the algorithm takes us to the init() method:

https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/fixed-supply/fixed-supply.component.ts
  async init(){
    this.reset()

    try {
      await this.estimateTransactionCosts()
    } catch (error) {
      if (error instanceof InsufficientFundsError) {
        this.steps.estimateTxCosts.hasError = true
      }
    }
  }

A note about error handling

SimpleICO provides error types for handling certain errors throughout the SCUI flow. See error handling for more information.

This async method awaits for the estimateTransactionCosts() method to finish:

https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/fixed-supply/fixed-supply.component.ts
  async estimateTransactionCosts(){
    
    this.steps.estimateTxCosts.estimates.push({
      text: 'ERC20 token deployment',
      txCost: '...'
    })
    
    let txCost = await this.deployer.estimateTokenDeploymentCost()
    this.steps.estimateTxCosts.estimates[0].txCost = txCost.ETH
    
    //...

Contribute

Have a better way of handling this pattern? Fork the project and submit a pull-request!

Having a closer look to this method, we identify a pattern that changes the state of the component steps

SCUI fixed-supply crowdsale deploy steps

These steps are defined in the component as a series of nested objects:

https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/fixed-supply/fixed-supply.component.ts
export class FixedSupplyComponent implements OnInit {

  //...

  steps: any = {
    estimateTxCosts: {
      step: 1,
      isCurrent: true,
      isComplete: false,
      hasError: false,
      estimates: []
    },
    deployToken: {
      step: 2,
      isCurrent: false,
      isComplete: false,
      hasError: false,
      errorMessage: '',
    },
    deployCrowdsale: {
      step: 3,
      isCurrent: false,
      isComplete: false,
      hasError: false,
      errorMessage: '',
    },
    transferToken: {
      step: 4,
      isCurrent: false,
      isComplete: false,
      hasError: false,
      errorMessage: '',
    }
  }

Each step is used in the component template to guide the user through the deployment and transactions process:

https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/fixed-supply/fixed-supply.component.html
<section class="step" id="step-1" *ngIf="steps.estimateTxCosts.isCurrent">
...

<section class="step" id="step-2" *ngIf="steps.deployToken.isCurrent">
...

<section class="step" id="step-3" *ngIf="steps.deployCrowdsale.isCurrent">
...

<section class="step" id="step-3" *ngIf="steps.transferToken.isCurrent">
...

As a developer of the SCUI for this contract, you are in complete control of displaying and changing the state of the deployment steps:

https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/fixed-supply/fixed-supply.component.ts
   async estimateTransactionCosts(){

    this.steps.estimateTxCosts.estimates.push({
      text: 'ERC20 token deployment',
      txCost: '...'
    })
    let txCost = await this.deployer.estimateTokenDeploymentCost()
    this.steps.estimateTxCosts.estimates[0].txCost = txCost.ETH

    this.steps.estimateTxCosts.estimates.push({
      text: 'Crowdsale contract deployment',
      txCost: '...'
    })
    txCost = await this.deployer.estimateCrowdsaleDeploymentCost()
    this.steps.estimateTxCosts.estimates[1].txCost = txCost.ETH

    this.steps.estimateTxCosts.estimates.push({
      text: 'Transfer token supply to crowdsale',
      txCost: '...'
    })
    this.token.setAddress(ContractDeployment.CONTRACT_DUMMY_ADDRESS)
    txCost = await this.deployer.estimateTokenTransferCost()
    this.steps.estimateTxCosts.estimates[2].txCost = txCost.ETH

    this.steps.estimateTxCosts.estimates.push({
      text: 'TOTAL',
      txCost: this.deployer.txCost.ETH
    })

    let wei = this.deployer.txCost.WEI
    
    let hasInsufficientFunds = wei.gt(this.wallet.balance)
    if (hasInsufficientFunds) {
      throw new InsufficientFundsError(InsufficientFundsError.MESSAGE)
    }

    this.steps.estimateTxCosts.isComplete = true
  }

ContractDeployment.CONTRACT_DUMMY_ADDRESS

Because the contract is not deployed yet, we have no official address of the crowdsale contract, but you may still calculate gas costs, by passing a dummy address.

The transaction costs pattern repeats until we hit the this.steps.estimateTxCosts.isComplete = true line or are caught in the InsufficientFundsError

Each iteration of the pattern, adds the transaction costs of the previous txCost returned by the current deployer:

https://github.com/SimpleICO/web-app/blob/master/src/app/@factory/contract-deployment.ts
   async estimateCrowdsaleDeploymentCost(){

    let txObject = await this.crowdsale.deploy(this.token.price, ContractDeployment.DUMMY_ADDRESS)

    let gas = await txObject.estimateGas()
    this.gas += gas + this.gasIncrement

    let txCost = await this.eth.getTxCost(gas)

    this.sumTxCost(txCost)

    return txCost
  }

Finally, the EthereumService takes charge of formatting the gas price and gas as returned WEI to ETH or from USD to ETH, as well as returning the cost as a bigNumber instance:

https://github.com/SimpleICO/web-app/blob/master/src/app/@service/ethereum.service.ts
  async getTxCost(gas, gasPrice = this.defaultGasPrice){ 
    let { USD } = await this.convertCurrency('ETH', 'USD')
    let cost = gas * gasPrice
    let ETH = ethers.utils.formatEther(cost.toString())
    return {
      cost: ethers.utils.bigNumberify(cost.toString()),
      ETH: ETH,
      USD: ( ETH * USD ).toFixed(2)
    }
  }

Last updated