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
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
//...
<main id="contract-deploy">
<app-fixed-supply *ngIf="FixedSupplyDeployment == contractType"></app-fixed-supply>
</main>
Get the deployer instance in the FixedSupplyComponent
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

Following the ngOnInit()
method of the contract-deploy/fixed-supply/fixed-supply.component.ts
, the algorithm takes us to the init()
method:
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:
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:
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:
<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:
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:
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:
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