Deploy a smart contract
The next step in our fixed-supply crowdsale SCUI flow is to actually deploy the contracts to the Ethereum Network.
Creating the real ERC20 token contract
Since we are deploying the actual contract, the SCUI is not simulating the deploy transaction to estimate the gas price. The SCUI will show the user the real thing.

The previous step (estimateTxCosts()
) includes the next action to be taken by the user: deployToken()
...
<div *ngIf="steps.estimateTxCosts.isComplete">
<button class="btn btn-primary btn-block" (click)="deployToken()">Next</button>
</div>
</div>
</section>
<section class="step" id="step-2" *ngIf="steps.deployToken.isCurrent">
...
This action triggers the next action (deployToken()
), which readies the token to be deployed to the current network (mainnet or ropsten):
async deployToken(){
this.supply = this.token.supply.toString()
this.steps.estimateTxCosts.isCurrent = false
this.steps.deployToken.isCurrent = true
this.steps.deployToken.hasError = false
try {
let receipt = await this.deployer.deployToken()
// Set the token address after it is deployed to the network
// This will be consumed by the crowdsale contract
this.token.setAddress(receipt.contractAddress)
await this.deployer.getTokenSupply()
this.steps.deployToken.isComplete = true
} catch (error) {
this.steps.deployToken.hasError = true
this.steps.deployToken.errorMessage = `Your token wasn't deployed but you didn't lose ETH funds.
This may be caused by the network performance.
If the <a href="${this.eth.etherscanURL}/address/${this.wallet.getAddress()}">transaction</a> is still running, wait before you retry.`
}
}
Let's have a closer look to the let receipt = await this.deployer.deployToken()
method, it should be self-explanatory:
Web3 deploy
The following deployer method was developed following the web3 deploy method documentation as well as for the signTransaction and sendSignedTransaction methods.
async deployToken(){
// Returning a Promise lets the component await for the tx receipt on resolve
// or listen to the error on reject
return new Promise(async (resolve, reject) => {
try {
let nonce = await this.eth.getNonce(this.token)
let txObject = await this.token.deploy()
let txOptions = {
from: this.wallet.address,
value: '0x0',
gas: Web3.utils.toHex(this.gas),
gasLimit: Web3.utils.toHex(this.gas),
gasPrice: Web3.utils.toHex(this.eth.defaultGasPrice),
data: txObject.encodeABI(),
nonce: Web3.utils.toHex(nonce)
}
let signedTx = await this.token.web3.eth.accounts.signTransaction(txOptions, this.wallet.privateKey)
let tx = this.token.web3.eth.sendSignedTransaction(signedTx.rawTransaction)
tx.on('transactionHash', hash => {
this.token.tx = hash
})
tx.on('error', error => {
reject(error)
})
tx.on('receipt', receipt => {
resolve(receipt)
})
} catch (error) {
reject(error)
}
})
}

Creating the fixed-supply crowdsale contract

The pattern for deploying the fixed-supply crowdsale contract is the same:
Set the current step (
this.steps.deployToken
) as completeSet the next step (
this.steps.deployCrowdsale
) as currentInitiate the contract deployment (
this.deployer.deployCrowdsale()
)Wait for the transaction to finish or handle errors
Trigger the next step
<section class="step" id="step-2" *ngIf="steps.deployToken.isCurrent">
...
<div *ngIf="steps.deployToken.isComplete">
<button class="btn btn-primary btn-block" (click)="deployCrowdsale()">Next</button>
</div>
</section>
<section class="step" id="step-3" *ngIf="steps.deployCrowdsale.isCurrent">
...
async deployCrowdsale(){
this.steps.deployToken.isCurrent = false
this.steps.deployCrowdsale.isCurrent = true
this.steps.deployCrowdsale.hasError = false
try {
await this.deployer.deployCrowdsale()
this.steps.deployCrowdsale.isComplete = true
} catch (error) {
console.log(error)
this.steps.deployCrowdsale.hasError = true
this.steps.deployCrowdsale.errorMessage = `Your crowdsale wasn't deployed but you didn't lose ETH funds. Retry this deployment or go to your token page`
}
}
async deployCrowdsale(){
return new Promise(async (resolve, reject) => {
try {
let txObject = await this.crowdsale.deploy(this.token.price, this.token.getAddress())
let nonce = await this.eth.getNonce(this.crowdsale)
let txOptions = {
from: this.wallet.address,
value: '0x0',
gas: Web3.utils.toHex(this.gas),
gasLimit: Web3.utils.toHex(this.gas),
gasPrice: Web3.utils.toHex(this.eth.defaultGasPrice),
data: txObject.encodeABI(),
nonce: Web3.utils.toHex(nonce)
}
let signedTx = await this.crowdsale.web3.eth.accounts.signTransaction(txOptions, this.wallet.privateKey)
let tx = this.crowdsale.web3.eth.sendSignedTransaction(signedTx.rawTransaction)
tx.on('transactionHash', hash => {
this.crowdsale.tx = hash
})
tx.on('error', error => {
reject(error)
})
tx.on('receipt', async receipt => {
this.crowdsale.setAddress(receipt.contractAddress)
resolve(receipt)
})
} catch (error) {
reject(error)
}
})
}

Transfer the ERC20 token supply to the crowdsale
This operation needs to be made after both the token contract and the crowdsale contract are deployed to the Ethereum network. You cannot transfer the token supply to the crowdsale if there's no crowdsale yet, right?
This transaction is cheap and shouldn't take long.

<section class="step" id="step-3" *ngIf="steps.deployCrowdsale.isCurrent">
...
<div *ngIf="steps.deployCrowdsale.isComplete">
<button class="btn btn-primary btn-block" (click)="transferToken()">Next</button>
<a href="{{ eth.etherscanURL }}/address/{{ crowdsale.address }}" target="_blank" class="btn btn-outline-primary btn-block">See on etherscan</a>
</div>
</section>
<section class="step" id="step-3" *ngIf="steps.transferToken.isCurrent">
...
<div *ngIf="steps.transferToken.isComplete">
<button class="btn btn-primary btn-block" (click)="finish()">Finish</button>
</div>
</section>
async transferToken(){
this.steps.transferToken.isCurrent = true
this.steps.deployCrowdsale.isCurrent = false
this.steps.transferToken.hasError = false
try {
await this.deployer.transferToken()
this.steps.transferToken.isComplete = true
} catch (error) {
console.log(error)
this.steps.transferToken.hasError = true
this.steps.transferToken.errorMessage = `Something went wrong`
}
}
async transferToken(){
return new Promise(async (resolve, reject) => {
try {
let nonce = await this.eth.getNonce(this.token)
let txObject = this.token.instance.methods.transfer(this.crowdsale.getAddress(), this.token.supply)
let txOptions = {
from: this.wallet.address,
to: this.token.getAddress(),
value: '0x0',
gas: Web3.utils.toHex(this.gas),
gasLimit: Web3.utils.toHex(this.gas),
gasPrice: Web3.utils.toHex(this.eth.defaultGasPrice),
data: txObject.encodeABI(),
nonce: Web3.utils.toHex(nonce)
}
let signedTx = await this.token.web3.eth.accounts.signTransaction(txOptions, this.wallet.privateKey)
let tx = this.token.web3.eth.sendSignedTransaction(signedTx.rawTransaction)
tx.on('transactionHash', hash => {
this.token.tx = hash
})
tx.on('error', error => {
reject(error)
})
tx.on('receipt', async receipt => {
resolve(receipt)
})
} catch (error) {
reject(error)
}
})
}

Last but not least, the finish button should take the user to the next route contract/0x00123<crowdsale-address>/show/fixed-supply
finish(){
try {
// This is optional in your implementation
// This method saves the crowdsale address to a SimpleICO smart contract
// to list all the crowdsales in the contract/fixed-supply/index page
this.deployer.addCrowdsaleToSimpleICOContract()
return this.router.navigate([`/contract/${this.crowdsale.address}/show/${this.deployer.type}`])
} catch (error) {
console.log(error)
}
}
Last updated