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.

contract/fixed-supply/deploy #step-2

The previous step (estimateTxCosts()) includes the next action to be taken by the user: deployToken()

https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/fixed-supply/fixed-supply.component.html
    ...
    <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):

https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/fixed-supply/fixed-supply.component.ts
  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.

https://github.com/SimpleICO/web-app/blob/master/src/app/%40factory/fixed-supply.deployment.ts
   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)
      }
    })
  }
contract/fixed-supply/deploy #step-2 on tx success

Creating the fixed-supply crowdsale contract

contract/fixed-supply/deploy #step-3

The pattern for deploying the fixed-supply crowdsale contract is the same:

  1. Set the current step (this.steps.deployToken) as complete

  2. Set the next step (this.steps.deployCrowdsale) as current

  3. Initiate the contract deployment (this.deployer.deployCrowdsale())

  4. Wait for the transaction to finish or handle errors

  5. Trigger the next step

https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/fixed-supply/fixed-supply.component.html
<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">
 ...
https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/fixed-supply/fixed-supply.component.ts
   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`
    }
  }
https://github.com/SimpleICO/web-app/blob/master/src/app/%40factory/fixed-supply.deployment.ts
   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)
      }
    })
  }
contract/fixed-supply/deploy #step-3 on tx success

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.

contract/fixed-supply/deploy #step-4
https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/fixed-supply/fixed-supply.component.html
<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>
https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/fixed-supply/fixed-supply.component.ts
 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`
    }
  }
https://github.com/SimpleICO/web-app/blob/master/src/app/%40factory/fixed-supply.deployment.ts
  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)
      }
    })
  }
contract/fixed-supply/deploy #step-3 on tx success

Last but not least, the finish button should take the user to the next route contract/0x00123<crowdsale-address>/show/fixed-supply

https://github.com/SimpleICO/web-app/blob/master/src/app/contract-deploy/fixed-supply/fixed-supply.component.ts
  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