Setup smart contract constructor params
Learn how to connect a Solidity smart contract ABI with the SCUI
Understanding the create SCUI workflow
In this chapter, we will connect a user interface with HTML form inputs to setup the NAME
, SYMBOL
and SUPPLY
of a new ERC20 token contract and the PRICE
and BENEFICIARY ADDRESS
of a fixed-supply crowdsale contract that will take charge of selling the ERC20 token for us.
The rationale of this fixed-supply crowdsale is that any end-user could get or scan the QRCode of the ETH address of the crowdsale contract, transfer ETH to it and if the crowdsale still has token supply, take the ETH and send the ERC20 token to the buyer if the buyer meets the price.
Also, if the buyer sends more ETH than what the ERC20 costs, the crowdsale contract takes charge of delivering the remaining ETH back.
Fixed-supply contract SCUI end result:

Required contracts
In order to create a complete fixed-supply crowdsale creation flow, we need 2 contracts.
SimpleTokenContract
Extends from the open-zeppelin StandardToken and DetailedERC20.
In the constructor we set the value of a supply public variable multiplied by 1 ETH. This is important as the end-user deploying the new ERC20 will specify a supply as ETH integers and not it WEI.
pragma solidity ^0.4.21;
import '../node_modules/openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol';
import '../node_modules/openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol';
contract SimpleToken is StandardToken, DetailedERC20 {
constructor (
string _name,
string _symbol,
uint8 _decimals,
uint256 _totalSupply) public DetailedERC20 (_name, _symbol, _decimals) {
uint256 supply = _totalSupply * 1 ether;
totalSupply_ = supply;
balances[msg.sender] = supply;
emit Transfer(0x0, msg.sender, supply);
}
}
SimpleCrowdsaleContract
The SimpleCrowdsaleContract
extends from the standard open-zeppelin crowdsale contract and modifies some of its methods, basically to give back the remaining ETH if the value is higher than the price
stored in the crowdsale.
pragma solidity ^0.4.21;
import '../node_modules/openzeppelin-solidity/contracts/crowdsale/Crowdsale.sol';
import './SimpleToken.sol';
contract SimpleCrowdsale is Crowdsale {
uint256 public price;
uint256 internal weiAmount;
event ProcessedRemainder(uint256 remainder);
constructor (uint256 _price, uint256 _rate, address _wallet, SimpleToken _token)
public Crowdsale(_rate, _wallet, _token) {
price = _price;
}
/**
* @dev low level token purchase ***DO NOT OVERRIDE***
* @param _beneficiary Address performing the token purchase
*/
function buyTokens(address _beneficiary) public payable {
_preValidatePurchase(_beneficiary, msg.value);
weiAmount = _processRemainder(_beneficiary, msg.value);
uint256 tokens = _getTokenAmount(weiAmount);
weiRaised = weiRaised.add(weiAmount);
_processPurchase(_beneficiary, tokens);
emit TokenPurchase(
msg.sender,
_beneficiary,
weiAmount,
tokens
);
_updatePurchasingState(_beneficiary, weiAmount);
_forwardFunds();
_postValidatePurchase(_beneficiary, weiAmount);
}
/**
* @dev Transfers back the remainder of the weiAmount against the token price to the beneficiary
* @param _beneficiary Address performing the token purchase
* @param _weiAmount Value in wei involved in the purchase
* @return _weiAmount Value without the remainder
*/
function _processRemainder(address _beneficiary, uint256 _weiAmount) internal returns (uint256) {
uint256 remainder = _weiAmount % price;
emit ProcessedRemainder(remainder);
if (remainder > 0) {
_beneficiary.transfer(remainder);
}
return _weiAmount.sub(remainder);
}
/**
* @dev Validation of an incoming purchase. Use require statements to revert state when conditions are not met. Use super to concatenate validations.
* @param _beneficiary Address performing the token purchase
* @param _weiAmount Value in wei involved in the purchase
*/
function _preValidatePurchase(address _beneficiary, uint256 _weiAmount) internal {
require(_beneficiary != address(0));
require(_weiAmount != 0);
require(_weiAmount >= price);
}
/**
* @dev Override to extend the way in which ether is converted to tokens.
* @param _weiAmount Value in wei to be converted into tokens
* @return Number of tokens that can be purchased with the specified _weiAmount
*/
function _getTokenAmount(uint256 _weiAmount) internal view returns (uint256) {
return _weiAmount.div(price).mul(1 ether);
}
/**
* @dev Determines how ETH is stored/forwarded on purchases.
*/
function _forwardFunds() internal {
wallet.transfer(weiAmount);
}
}
Using the contracts ABI in the fixed-supply crowdsale SCUI
A compiled version of these contracts should be put in the src/assets/contracts/
directory of the web-app:
{
"contractName": "SimpleCrowdsale",
"abi": [
{
"constant": true,
"inputs": [],
"name": "rate",
...
Then, it is ready to be used.
Add a user interface for configuring the smart contract constructor params
The contract/:contractType/create
is designed to add HTML components that allow the end-user to interact with the constructor params of our smart contract.
Generate the fixed-supply component
In your terminal, execute:
ng generate component contract-create/fixed-supply --project=simple-ico
This will create a new component inside the contract-create
module.
Let the parent contract-create container component decide what contract type component to display
This part takes 2 steps:
Adding the FixedSupplyDeployment _type
string to the parent container component
FixedSupplyDeployment _type
string to the parent container componentimport { Component, OnInit } from '@angular/core';
import { 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
//...
Adding the component element to the container.component.html
element
container.component.html
element<app-header></app-header>
<app-mobile-menu></app-mobile-menu>
<main id="contract-create">
<app-fixed-supply *ngIf="FixedSupplyDeployment == contractType"></app-fixed-supply>
</main>
Setup the FixedSupplyComponent
Open the fixed-supply.component.ts
file and add the ContractDeploymentFactory
to it. This will make the FixedSupplyDeployment
class available in the FixedSupplyComponent
:
import { Component, OnInit, Input } from '@angular/core';
import { ContractDeploymentFactory } from '@factory/contract-deployment.factory';
import { ContractDeployment } from '@factory/contract-deployment';
@Component({
selector: 'app-fixed-supply',
templateUrl: './fixed-supply.component.html',
styleUrls: ['./fixed-supply.component.css']
})
export class FixedSupplyComponent implements OnInit {
deployer: ContractDeployment
constructor(
private contractFactory: ContractDeploymentFactory) {
this.deployer = contractFactory.deployer
}
}
Initialize the fixed-supply crowdsale smart contract
Once you've integrated the deployer with the component, the next step is to integrate the smart contract ABI with the UI.
ngOnInit() {
this.deployer
.createToken()
.createCrowdsale()
.createSimpleICO()
this.token = this.deployer.getToken()
this.crowdsale = this.deployer.getCrowdsale()
}
In the ngOnInit()
method of our FixedSupplyComponent
, we are calling 3 deployer methods: createToken()
, createCrowdsale()
and createSimpleICO()
Connecting to a smart contract interface, ABI
createToken(){
this.token = new SimpleTokenContract(this.wallet)
this.token.connect()
return this
}
createCrowdsale(){
this.crowdsale = new SimpleCrowdsaleContract(this.wallet)
this.crowdsale.connect()
return this
}
The missing third method called in our component lives in the parent ContractDeployment class:
createSimpleICO(){
this.simpleICO = new SimpleICOContract(this.wallet)
this.simpleICO.connect()
}
Note the .connect()
method common in the 3 of the contract class instances. This method is defined in each of the *Contract classes.
Defining a *Contract class
*Contract
classes are in charge of interacting with the compiled Solidity smart contract ABI. This will allow us to call any Solidity contract methods through the deployer like this:
this.deployer.token.instance.methods.balanceOf("0x...")
Let's create the fixed-supply crowdsale contract class and place it in the src/app/@contract/
directory of our app:
Naming convention
For *Contract classes, the suggested naming convention is <contract-name>.contract.ts
import { Crowdsale } from '@model/crowdsale.model';
import { Wallet } from '@model/wallet.model';
declare var require: any
const SimpleCrowdsaleInterface = require('@abi/simplecrowdsale.abi.json')
export class SimpleCrowdsaleContract extends Crowdsale {
instance: any
web3: any
constructor(
wallet: Wallet) {
super(wallet)
this.web3 = wallet.web3
}
connect(){
let _contract = new this.web3.eth.Contract(SimpleCrowdsaleInterface.abi)
this.instance = _contract
return this
}
}
Notice that the constructor passes the Wallet
instance to the parent Crowdsale
contract. The parent Crowdsale
contract is used as a generic class, in case you need common methods or properties across other Crowdsale
contracts.
Also, we are getting the web3
instance from the Wallet
so we can access the Web3JS package methods.
Finally, the connect()
method is using the web3
instance to initialize the contract using the ABI as detailed in the docs (http://web3js.readthedocs.io/en/1.0/web3-eth-contract.html#new-contract)
We will do the same for the SimpleTokenContract
class and the SimpleICOContract
classes:
import { Token } from '@model/token.model';
import { Wallet } from '@model/wallet.model';
declare var require: any
const SimpleTokenInterface = require('@abi/simpletoken.abi.json')
export class SimpleTokenContract extends Token {
name: string = ''
symbol: string = ''
decimals: number = 18
supply: any = 10
price: any = '0.0001'
web3: any
constructor(
wallet: Wallet) {
super(wallet)
this.web3 = wallet.web3
}
connect(){
let _contract = new this.web3.eth.Contract(SimpleTokenInterface.abi)
this.instance = _contract
return this
}
}
import { SimpleICO } from '@model/simpleico.model';
import { Wallet } from '@model/wallet.model';
declare var require: any
const SimpleICOInterface = require('@abi/simpleico.abi.json')
export class SimpleICOContract extends SimpleICO {
web3: any
constructor(wallet: Wallet) {
super(wallet)
this.web3 = wallet.web3
}
connect(){
let _contract = new this.web3.eth.Contract(SimpleICOInterface.abi)
this.instance = _contract
return this
}
}
Last updated