Lab 4 - Create a token and put it to use

And finally it's time to create our very own CFToken that users can use to pay for flights. We will also add functionality to exchange Ether to CFTokens and transfer to other users.

Introduction

So far we have just supposed that the travellers and airlines are trading using some Ether. Another way to approach this is to offer our very own currency that can be used for betting on seats on a flight. This is a very common approach in the blockchain-community and also allows us as creators to gain some more benefit from the platform.

We will create our own token, CFToken, which conforms to the ERC20-protocol. We will then transform our CryptoFlight-DAPP make use of this token instead of Ether. To make it possible for people that believe in our amazing idea, we will also make it possible to exchange Ether to CFTokens and place bids on flights.

Finally, we are extending the functionality of the web frontend to make it possible for users to check their current balance of CFTokens and transfer them to other users. It's gonna be a fairly long lab so make sure to pour up some good strong coffee and follow along!

Introduction to ERC20

ERC stands for Ethereum Request for Comments and is similar to RFC but for the Ethereum network. ERC20 defines a protocol that tokens on the Ethereum network can implement and that makes it easier to trade with them on cryptocurrency exhanges such as Coinbase. ERC20 simply defines a set of functions that you need to implement.

We will create an ERC20 token contract for our DAPP and prepare for an initial coin offoring (ICO). The actual ERC20 interface is pretty straight forward, create a new file in the contracts-folder and add the following file:

IERC20.sol
pragma solidity >=0.5.0 <0.6.0;

/**
 * @title ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/20
 */
interface IERC20 {
    function transfer(address to, uint256 value) external returns (bool);
    function approve(address spender, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
    function totalSupply() external view returns (uint256);
    function balanceOf(address who) external view returns (uint256);
    function allowance(address owner, address spender) external view returns (uint256);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

We also need to use a library called SafeMath from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol) to prevent overflowing ints when calling functions in the token-contract. Create a new file in the same folder and name it SafeMath.sol:

SafeMath.sol
pragma solidity >=0.5.0 <0.6.0;

/**
 * @title SafeMath
 * @dev Unsigned math operations with safety checks that revert on error
 */
library SafeMath {
    /**
     * @dev Multiplies two unsigned integers, reverts on overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }
        uint256 c = a * b;
        require(c / a == b);
        return c;
    }

    /**
     * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0);
        uint256 c = a / b;
        return c;
    }

    /**
     * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a);
        uint256 c = a - b;
        return c;
    }

    /**
     * @dev Adds two unsigned integers, reverts on overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a);
        return c;
    }

    /**
     * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
     * reverts when dividing by zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0);
        return a % b;
    }
}

Create a ERC20 token contract

There are a lot of examples to be found on how to write your token contract to make it conform to the ERC20 interface. To keep this lab of reasonable length, we're going to use the standard ERC20 token contract provided by OpenZeppelin and make some small additions to it.

Create a new file in your contracts-folder and name it CFToken.sol, then copy and paste the following code:

CFToken.sol
pragma solidity >=0.5.0 <0.6.0;

import './IERC20.sol';
import './SafeMath.sol';

/**
 * @title Standard ERC20 token
 *
 * @dev Implementation of the basic standard token.
 * https://eips.ethereum.org/EIPS/eip-20
 * Originally based on code by FirstBlood:
 * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
 *
 * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for
 * all accounts just by listening to said events. Note that this isn't required by the specification, and other
 * compliant implementations may not do it.
 */
contract CFToken is IERC20 {
    using SafeMath for uint256;

    string public name;
    string public symbol;
    uint8 public decimals = 18;
    mapping (address => uint256) private _balances;
    mapping (address => mapping (address => uint256)) private _allowed;
    uint256 private _totalSupply;
    address payable public creator;

    /**
     * Constructor function
     *
     * Initializes contract with initial supply tokens to the creator of the contract
     */
    constructor(
        uint256 initialSupply,
        string memory tokenName,
        string memory tokenSymbol
    ) public {
        _totalSupply = initialSupply * 10 ** uint256(decimals);  // Update total supply with the decimal amount
        _balances[msg.sender] = _totalSupply;                // Give the creator all initial tokens
        creator = msg.sender;                               // Set the creator of the contract
        name = tokenName;                                   // Set the name for display purposes
        symbol = tokenSymbol;                               // Set the symbol for display purposes
    }

    /**
     * @dev Total number of tokens in existence
     */
    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev Gets the balance of the specified address.
     * @param owner The address to query the balance of.
     * @return A uint256 representing the amount owned by the passed address.
     */
    function balanceOf(address owner) public view returns (uint256) {
        return _balances[owner];
    }

    /**
     * @dev Function to check the amount of tokens that an owner allowed to a spender.
     * @param owner address The address which owns the funds.
     * @param spender address The address which will spend the funds.
     * @return A uint256 specifying the amount of tokens still available for the spender.
     */
    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowed[owner][spender];
    }

    /**
     * @dev Transfer token to a specified address
     * @param to The address to transfer to.
     * @param value The amount to be transferred.
     */
    function transfer(address to, uint256 value) public returns (bool) {
        _transfer(msg.sender, to, value);
        return true;
    }

    /**
     * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
     * Beware that changing an allowance with this method brings the risk that someone may use both the old
     * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
     * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     */
    function approve(address spender, uint256 value) public returns (bool) {
        _approve(msg.sender, spender, value);
        return true;
    }

    /**
     * @dev Transfer tokens from one address to another.
     * Note that while this function emits an Approval event, this is not required as per the specification,
     * and other compliant implementations may not emit the event.
     * @param from address The address which you want to send tokens from
     * @param to address The address which you want to transfer to
     * @param value uint256 the amount of tokens to be transferred
     */
    function transferFrom(address from, address to, uint256 value) public returns (bool) {
        _transfer(from, to, value);
        _approve(from, msg.sender, _allowed[from][msg.sender].sub(value));
        return true;
    }

    /**
     * @dev Increase the amount of tokens that an owner allowed to a spender.
     * approve should be called when _allowed[msg.sender][spender] == 0. To increment
     * allowed value is better to use this function to avoid 2 calls (and wait until
     * the first transaction is mined)
     * From MonolithDAO Token.sol
     * Emits an Approval event.
     * @param spender The address which will spend the funds.
     * @param addedValue The amount of tokens to increase the allowance by.
     */
    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
        _approve(msg.sender, spender, _allowed[msg.sender][spender].add(addedValue));
        return true;
    }

    /**
     * @dev Decrease the amount of tokens that an owner allowed to a spender.
     * approve should be called when _allowed[msg.sender][spender] == 0. To decrement
     * allowed value is better to use this function to avoid 2 calls (and wait until
     * the first transaction is mined)
     * From MonolithDAO Token.sol
     * Emits an Approval event.
     * @param spender The address which will spend the funds.
     * @param subtractedValue The amount of tokens to decrease the allowance by.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
        _approve(msg.sender, spender, _allowed[msg.sender][spender].sub(subtractedValue));
        return true;
    }

    /**
     * @dev Transfer token for a specified addresses
     * @param from The address to transfer from.
     * @param to The address to transfer to.
     * @param value The amount to be transferred.
     */
    function _transfer(address from, address to, uint256 value) internal {
        require(to != address(0));

        _balances[from] = _balances[from].sub(value);
        _balances[to] = _balances[to].add(value);
        emit Transfer(from, to, value);
    }

    /**
     * @dev Internal function that mints an amount of the token and assigns it to
     * an account. This encapsulates the modification of balances such that the
     * proper events are emitted.
     * @param account The account that will receive the created tokens.
     * @param value The amount that will be created.
     */
    function _mint(address account, uint256 value) internal {
        require(account != address(0));

        _totalSupply = _totalSupply.add(value);
        _balances[account] = _balances[account].add(value);
        emit Transfer(address(0), account, value);
    }

    /**
     * @dev Internal function that burns an amount of the token of a given
     * account.
     * @param account The account whose tokens will be burnt.
     * @param value The amount that will be burnt.
     */
    function _burn(address account, uint256 value) internal {
        require(account != address(0));

        _totalSupply = _totalSupply.sub(value);
        _balances[account] = _balances[account].sub(value);
        emit Transfer(account, address(0), value);
    }

    /**
     * @dev Approve an address to spend another addresses' tokens.
     * @param owner The address that owns the tokens.
     * @param spender The address that will spend the tokens.
     * @param value The number of tokens that can be spent.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        require(spender != address(0));
        require(owner != address(0));

        _allowed[owner][spender] = value;
        emit Approval(owner, spender, value);
    }

    /**
     * @dev Internal function that burns an amount of the token of a given
     * account, deducting from the sender's allowance for said account. Uses the
     * internal burn function.
     * Emits an Approval event (reflecting the reduced allowance).
     * @param account The account whose tokens will be burnt.
     * @param value The amount that will be burnt.
     */
    function _burnFrom(address account, uint256 value) internal {
        _burn(account, value);
        _approve(account, msg.sender, _allowed[account][msg.sender].sub(value));
    }
}

(https://github.com/ryggan/CryptoFlight/blob/master/contracts/CFToken.sol)

This contract code is should be fairly self-explainatory. In the constructor we take in the initial supply of the token, a name (CFToken) and a token-symbol (CFT). We will define these later when we deploy the contract. When the token-contract is created we set all of its initial supply to belong to us.

The function transfer() lets us transfer tokens from one address to another, burn() destroys tokens and approve() lets another contract spend tokens on your behalf. There also methods for transferFrom() and burnFrom() that are used for transfer respective burning money from another caller (given that the contract calling these methods are first allowed to do so with the approve() function).

Deploy the token contract

Now that we have a working contract the next step is to compile it and deploy to the testnet. Add an import statement for the new contract in the deploy-script (in the migrations-folder) and call the deploy function with a 100 CFT as initial supply, CFToken as the name and CFT as symbol. Your deploy-file should now look like:

2_deploy_contracts.js
var CryptoFlightFactory = artifacts.require('./CryptoFlightFactory.sol');
var CFToken = artifacts.require('./CFToken.sol');

module.exports = function(deployer, network, accounts) {
  deployer.deploy(CryptoFlightFactory);
  deployer.deploy(CFToken, 100, 'CFToken', 'CFT');
};

(https://github.com/ryggan/CryptoFlight/blob/master/migrations/2_deploy_contracts.js)

That's it for deploying! You are now the owner of 100 CryptoFlightTokens (at least on your private blockchain).

Let users see their CF-token balance

In this section we will let the user see its CFToken balance directly in the frontend. We will start building a new React component where we show the current balance of

Create a new file under client/component and name it CryptoFlightToken.js. Then declare a class-based React component:

CryptoFlightToken.js
import React from 'react';

class CryptoFlightToken extends React.Component {
    render() {
        return (
            <div>CFToken</div>
        );
    }
}

export default CryptoFlightToken;

Import the new component in your App.js by adding an import-statement at the top:

import CryptoFlightToken from './components/CryptoFlightToken';

Then add this component to the page: <CryptoFlightToken />. You can add it anywhere on the page, I would recommend putting it below the actual functionality of adding flights.

Before we start building out the actual component we need to think a little about what data we need to send down to it. If you have taken a web-development course you are probably used to state management libraries such as Redux or Vuex that makes use of one-way dataflow. When building a larger application it's definitely recommended to use such a library to keep things a little more clean and avoid props-drilling. For this application we are only making use of two-levels of components so we're just gonna let the root-node be responsible for global state and send down the props needed directly to the child-components.

We will need to have the user's account available as well as the balance, the web3 library and the cfToken contract object which we instantiated when the App-component mounted. Apart from that we also need a way for the App-component to know when the balance of the current user updates since this information will be useful when bidding for flights.

A common design-pattern for this is to define a function that bind to the current context and pass it into the child-component as a prop. The function will look like:

App.js
updateBalance = async () => {
    const balance = await this.state.cfToken.methods.balanceOf(this.state.account).call();
    this.setState({ balance });
}

Remember that all methods on contracts return promises so we need to use the async/await pattern to wait until this method resolves and then update the current balance in the state-object for the App component. The reason we define this function as an arrow function instead of a normal function directly on the component is because we want to bind the current context to it. The previous approach was to bind the this-context to the function when passing along.

Now pass all of these as props inside the CryptoFlightToken component:

App.js
<CryptoFlightToken
    account={this.state.account}
    balance={this.state.balance}
    web3={this.state.web3}
    cfToken={this.state.cfToken}
    updateBalance={this.updateBalance}
/>

All of these are now available as props inside the CryptoFlightToken-component so we can start building that one. We will add functionality for sending transactions in the next section so for now you can simply try to show the current balance of the user:

CryptoFlightToken.js
render() {
  return() {
    <Grid.Row>
      <Grid.Column width={16}>
        <Card style={{ padding: '2em 0em' }} fluid>
          <Header as="h1" textAlign="center">
            CFToken
          </Header>
          <Divider />
            <Grid>
              <Grid.Row>
                <Grid.Column width={16}>
                  <Input label="My CFToken balance" value={this.props.balance / 1e18 || '0'} fluid />
                </Grid.Column>
              </Grid.Row>
            </Grid>
          </Card.Content>
        </Card>
      </Grid.Column>
    </Grid.Row>
  );
}

And don't forget to import the components from semantic-ui by adding the following import statement at the top:

import { Grid, Card, Header, Divider, Input } from 'semantic-ui-react';

Now go back to your browser and make sure that you can see the current balance. If everything worked out correctly you should see 100 in the "My CFToken balance" field.

Sending CF-token to other users

Let's now extend the CryptoFlightToken-component with functionality for transfering tokens. If you payed attention to the standard token-contract you will see that there's a function for transfering tokens from one address to another, this function is simply called transfer and takes in a receiver-address and an amount. If the sender has enough CFT in its balance the funds will be transfered to the recipients address.

Go back to Flights.js and make the following changes to the component:

Import Form, Button and Loader from semantic-ui:

import { Grid, Card, Header, Divider, Form, Button, Input, Loader } from 'semantic-ui-react';

Declare an initial state object:

state = {
  transferAmount: '0',
  receiver: '',
  sendingTransaction: false,
};

Create a new asynchronous function for sending transactions:

transferTokens = async event => {
  event.preventDefault();

  this.setState({ sendingTransaction: true });

  await this.props.cfToken.methods
    .transfer(this.state.receiver, String(Number(this.state.transferAmount) * 1e15))
    .send({ from: this.props.account });
  this.props.updateBalance();

  this.setState({ transferAmount: '0', sendingTransaction: false });
};

And finally update the render-function to include the following:

<Grid.Row>
  {!this.state.sendingTransaction ? (
    <>
      <Grid.Column width={4} textAlign="center">
        <Form.Input
          fluid
          placeholder="0"
          icon="money bill alternate outline"
          value={this.state.transferAmount}
          onChange={input =>
            this.setState({ transferAmount: input.target.value.trim().replace(/[^0-9]/g, '') })
          }
        />
        milliCFT = {Number(this.state.transferAmount) / 1000} CFT
      </Grid.Column>
      <Grid.Column width={8}>
        <Form.Input
          fluid
          placeholder="0x000000000000000000000000000"
          icon="address card outline"
          value={this.state.receiver}
          onChange={input => this.setState({ receiver: input.target.value.trim() })}
        />
      </Grid.Column>
      <Grid.Column width={4}>
        <Button
          color="black"
          fluid
          disabled={
            !this.state.transferAmount ||
            Number(this.state.transferAmount) === 0 ||
            !this.state.receiver ||
            this.state.sendingTransaction
          }
        >
          Send
        </Button>
      </Grid.Column>
    </>
  ) : (
    <Grid.Column width={16}>
      <Header as="h3" className="info">
        Transfering tokens, this might take 30 seconds or so...
      </Header>
    </Grid.Column>
  )}
</Grid.Row>

Note that we are making use of controlled components for updating the state the user types something in the text-fields. This is also where you would to some error handling and make sure that the user enters a valid address before she is able to click send. In

The user enters the amount of milliCFT she wants to send and gets an instant conversion to CFT. This is just to make it a little easier to test the functionality, if you are about to deploy the DAPP you might want to instead use CFT directly.

The full CryptoFlightToken.js component should now look like this:

CryptoFlightToken.js
import React from 'react';
import { Grid, Card, Header, Divider, Form, Button, Input, Loader } from 'semantic-ui-react';

class CryptoFlightToken extends React.Component {
  state = {
    transferAmount: '0',
    receiver: '',
    sendingTransaction: false,
  };

  transferTokens = async event => {
    event.preventDefault();
  
    this.setState({ sendingTransaction: true });
  
    await this.props.cfToken.methods
      .transfer(this.state.receiver, String(Number(this.state.transferAmount) * 1e15))
      .send({ from: this.props.account });
    this.props.updateBalance();
  
    this.setState({ transferAmount: '0', sendingTransaction: false });
  };

  render() {
    return (
      <Grid.Row>
        <Grid.Column width={16}>
          <Card style={{ padding: '2em 0em' }} fluid>
            <Header as="h1" textAlign="center">
              CFToken
            </Header>
            <Divider />
            <Card.Content style={{ minHeight: 160 }}>
              <Form onSubmit={this.sendTransaction}>
                <Grid>
                  <Grid.Row>
                    {!this.state.sendingTransaction ? (
                      <>
                        <Grid.Column width={4} textAlign="center">
                          <Form.Input
                            fluid
                            placeholder="0"
                            icon="money bill alternate outline"
                            value={this.state.transferAmount}
                            onChange={input =>
                              this.setState({ transferAmount: input.target.value.trim().replace(/[^0-9]/g, '') })
                            }
                          />
                          milliCFT = {Number(this.state.transferAmount) / 1000} CFT
                        </Grid.Column>
                        <Grid.Column width={8}>
                          <Form.Input
                            fluid
                            placeholder="0x000000000000000000000000000"
                            icon="address card outline"
                            value={this.state.receiver}
                            onChange={input => this.setState({ receiver: input.target.value.trim() })}
                          />
                        </Grid.Column>
                        <Grid.Column width={4}>
                          <Button
                            color="black"
                            fluid
                            disabled={
                              !this.state.transferAmount ||
                              Number(this.state.transferAmount) === 0 ||
                              !this.state.receiver ||
                              this.state.sendingTransaction
                            }
                          >
                            Send
                          </Button>
                        </Grid.Column>
                      </>
                    ) : (
                      <Grid.Column width={16}>
                        <Header as="h3" className="info">
                          Transfering tokens, this might take 30 seconds or so...
                        </Header>
                      </Grid.Column>
                    )}
                  </Grid.Row>
                </Grid>
              </Form>
            </Card.Content>
          </Card>
        </Grid.Column>
      </Grid.Row>
    );
  }
}

export default CryptoFlightToken;

(https://github.com/ryggan/CryptoFlight/blob/master/client/src/components/CryptoFlightToken.js)

If you did everything right you should now be able to send CFTokens from one account to another. Try it out by entering the address of a second account in the address field and some amount of milliCFT in the amount-field and click send. You should now see a confirmation box appear where you can see the amount of CFT as well as the gas cost for sending the transaction. Note that the gas fee is payed in Ether:

You will now get a confimation message by MetaMask if the transaction was successful:

Switch to the account to which you sent the CFT and refresh the page. You should now see the newly transfered CFToken appear on you account:

However, reloading the page every time you switch between accounts in MetaMask isn't a very good user experience, it would be better if we could refetch the account address automatically whenever the address changes. Luckely for us there's an event emitter for that in web3 that we can attach a function to.

Open up App.js and add the following snippet to the componentDidMount lifecycle-method, just before you set the state:

App.js
web3.currentProvider.publicConfigStore.on('update', ({ selectedAddress: account }) => {
    this.setState({ account }, this.updateBalance);
});

You can also remove the account-assignment from the initial setState-call. Your componentDidMount in App.js should now look like:

App.js
componentDidMount = async () => {
  try {
    const web3 = await getWeb3();
    const accounts = await web3.eth.getAccounts();
    const networkId = await web3.eth.net.getId();

    const cfTokenNetwork = CFToken.networks[networkId];
    const cfToken = new web3.eth.Contract(CFToken.abi, cfTokenNetwork && cfTokenNetwork.address);
    const balance = await cfToken.methods.balanceOf(accounts[0]).call();

    web3.currentProvider.publicConfigStore.on('update', ({ selectedAddress: account }) => {
      this.setState({ account }, this.updateBalance);
    });

    this.setState({ web3, cfToken, balance });
  } catch (err) {
    console.error(err);
  }
};

(https://github.com/ryggan/CryptoFlight/blob/master/client/src/App.js)

Save the file and wait a second for the site to reload, then try switching between different accounts in MetaMask using the symbol in the top-right corner. You should now see how the Flights-component updates accordingly whenever you switch between accounts.

Sidenote on crowdsale

Now that we have a working dapp up and running we want to offer the public to buy some CFTokens. A common way to do this is via a crowdsale, or initial coin offering (ICO) which serves several purposes. It can be used to take in funding before a project is even started, it's a way to let enthusiasts and early adopters to be a part of a project in its early stages and it also helps to build hype around the product. There was big hype around ICOs a couple of years ago and you probably read a lot about ICO scams. Since then the concept has died out a bit, but the core idea is still useful to understand.

The idea of crowdsale contract is fairly simple, the contract gets deployed with a certain time-limit, for example 2 weeks as well as a funding goal. During that period, anyone can transfer funds to the contract and get tokens. When the time is up, two things can happen. If the funding goal is reached, the beneficiary (creator of the crowdsale contract) can call a function which transmits all of the funds to him. If the funding goal is not reached, the backers can reclaim their funds.

In this project I choose to not do a classic crowdsale but instead let users buy CFTokens directly from the contract. The reason for this is to make it easier for users to obtain tokens at any time. It also makes the tutorial more straight forward to follow.

Add functionality to buy tokens

To make it possible for users to buy tokens we need to add some functionality to the CFToken contract. Open up CFToken.sol and add the following function at the end of it:

CFToken.sol
/**
 * Send ether to the contract and receive CFTokens
 */
function() payable external {
    require(msg.sender != creator);
    uint256 amount = msg.value;
    creator.transfer(amount);
    _transfer(creator, msg.sender, amount);
}

This is a so called fallback function which will be invoked whenever someone sends Ether to the contract. When it gets invoked we will transfer CFTokens from the creator to the sender's address and the Ether will be sent to the creator of the contract, simple as that!

We now need to add some functionality to the frontend so that the user can send Ether and invoke this function. Open up CryptoFlightToken.js and add the following:

In the initial state object, add buyAmount: '0', and buyingTokens: false. Then a function for buying tokens:

CryptoFlightToken.js
buyTokens = async event => {
  event.preventDefault();

  const { web3, account, cfToken, updateBalance } = this.props;

  this.setState({ buyingTokens: true });

  await web3.eth.sendTransaction({
    from: account,
    to: cfToken._address,
    value: web3.utils.toWei(this.state.buyAmount, 'milli')
  });

  updateBalance();

  this.setState({ buyAmount: '0', buyingTokens: false });
};

We're creating an arrow function named buyTokens which is asyncronous and accepts one argument, namely the event. The event is what gets passed from a form submission and calling preventDefault() on it will prevent the page from reloading. We then pull of web3, account and cfToken from the props object using object destructuring and set the buyingTokens state to true. The reason for this is to give us the possibility for having a UI that communicates what's happening to the user. Remember that when using blockchain we have a fairly long latency for all operations which communicates with the chain, around 20-30 seconds, so it's important that we give the user feedback. Otherwise they think that something's wrong and might shut down the application.

Next up, we are sending a transaction from our account to the cfToken account with the given amount of Ether attached to it. This will invoke the fallback-function in the CFToken-contract and transfer the CFTokens to the user. The sendTransaction-function returns a Promise and using the await keyword we make sure to block the rest of the function execution until this Promise resolves. When it does, we call the updateBalance function and set the buyingTokens variable of the state object to be false again.

We now need to add the actual UI for invoking this function and send the transaction. Add the following code to the end of the Card Content component (in the render function):

CryptoFlightToken.js
<Form onSubmit={this.buyTokens}>
  <Grid>
    <Grid.Row>
      {this.props.account.toLowerCase() !== this.props.cfTokenCreator.toLowerCase() ? (
        !this.state.buyingTokens ? (
          <>
            <Grid.Column width={6}>
              <Form.Input
                fluid
                value={this.state.buyAmount}
                label="MilliCFT"
                onChange={input =>
                  this.setState({ buyAmount: input.target.value.trim().replace(/[^0-9]/g, '') })
                }
              />
            </Grid.Column>
            <Grid.Column width={6}>
              <Form.Input label="Cost" fluid value={`${this.state.buyAmount / 1000} Ether`} />
            </Grid.Column>
            <Grid.Column width={4} verticalAlign="bottom">
              <Button color="black" fluid disabled={this.state.buyingTokens}>
                Buy
              </Button>
            </Grid.Column>
          </>
        ) : (
          <Grid.Column width={16}>
            <Header as="h3" className="info">
              Buying tokens, hang tight!
            </Header>
          </Grid.Column>
        )
      ) : (
        <Grid.Column width={16}>
          <Header style={{ color: '#7a7a7a' }}>
            Your account address is identical to the contract creator.
          </Header>
          <p style={{ color: '#7a7a7a' }}>Switch to a different account to buy some CFTokens.</p>
        </Grid.Column>
      )}
    </Grid.Row>
  </Grid>
</Form>

(https://github.com/ryggan/CryptoFlight/blob/master/client/src/components/CryptoFlightToken.js)

Make use of our ERC20 token in CryptoFlight

Now that we have our CFToken set up and has made it possible for users to both transfer tokens to other accounts and to use Ether to buy tokens, we will make use of it inside in CryptoFlight. Instead of placing bids on flights using Ether we will use CFToken as a means of payment.

We want to user to send CFTokens to the CryptoFlight contract instead of Ether. It's not possible to send CFTokens directly to the contract the way we send attach Ether with a transaction. Instead we somehow need to make make use of both the CFToken-contract and the CryptoFlight-contract to transfer the tokens from one account to another. There are several ways we can do this.

One obvious way is to make use of the approve-function specified by the ERC20-interface. Approve lets someone else transfer tokens on your behalf. To implement this we would have two contract calls on the frontend, first one to invoke the approve function in CFToken and then another one to place the actual bid in the CryptoFlight-contract. The obvious downside of this is that it makes the user responsible for both approving the transaction and placing the bid for the contract. Every transaction in the Ethereum-network takes around 20-30 second so the user would first need to wait until the approve-call finished and then call the CryptoFlight-contract to place the actual bid. On the flip side, it doesn't require any alterations of the CFToken contract.

While this approach would work, it's far from optimal. We are instead using a design where the user just makes one contract call. In the CFToken-contract, we will add a function addTraveller(uint256 _bid, address _cryptoFlight) which takes in both the bid (in terms of CFTokens) and the address to the CryptoFlight-contract of which to user wants to make a bid. This function will transfer the CFTokens from the user to the given CryptoFight-contract and invoke a function inside that contract to add the actual traveller. The flow will look as follows:

So let get our hands dirty and create these two functions. Open up CFToken.sol and add the following function:

CFToken.sol
/**
 * Add traveller to a CryptoFlight contract
 */
function addTraveller(uint256 _bid, address _cryptoFlight) external {
    CryptoFlight cryptoFlight = CryptoFlight(_cryptoFlight);

    require(_bid >= cryptoFlight.minBid());
    require(!cryptoFlight.finalized());
    require(!cryptoFlight.bidPlaced(msg.sender));
    require(msg.sender != cryptoFlight.creator());

    transfer(_cryptoFlight, _bid);
    cryptoFlight.placeBid(msg.sender, _bid);
}

First we will cast the address to an instance of CryptoFlight. To do this we also need to import the CryptoFlight contract into CFToken, so in the very top of the file add import './CryptoFlight.sol'; after the import statements for IERC20 and SafeMath.

Next we want to make sure that it's a valid bid using a couple of require statements. These are similar to assert-statements if you are used to some testing library like Jest. If any of them fails the contract invocation will fail and return an error. Next up we will do the actual transfer of CFTokens from the invoker to the CryptoFlight-contract and finally we'll call placeBid() inside CryptoFlight to add the actual instance of that contract. So, now we need to also add this function in CryptoFlight.sol:

CryptoFlight.sol
function placeBid(address payable _traveller, uint256 _bid) public {
    require(msg.sender == address(cfToken));

    Traveller memory newTraveller = Traveller({
       user: _traveller,
       bid: _bid,
       approved: false
    });

    bidPlaced[_traveller] = true;
    travellers.push(newTraveller);
}

This function is very similar to the previous approach, except that we already did most of the error handling inside CFToken (checking for minimum bid etc). The two big differences are that we no longer have the function as payable since it won't receive any Ether. The other big difference is that we only allow the CFToken-contract to call this function. If not, anyone could call the function and add a traveller without transferring any assets.

To be able to do this we must of course set the address of the CFToken-contract in the CryptoFlight-contract when we first create it. And since we use CryptoFlightFactory to create the contract we must first set the address inside this contract. Might sound complicated but it's pretty straight forward.

There are a few steps involved in this process so we'll start from the very beginning, namely in the deployment of the contracts. Since we need access to the CFToken contract before we deploy the CryptoFlightFactory contract. Open up 2_deploy_contracts.js and rewrite it as follows:

var CryptoFlightFactory = artifacts.require('./CryptoFlightFactory.sol'); var CFToken = artifacts.require('./CFToken.sol');

module.exports = deployer => { deployer.deploy(CFToken, 1000, 'CFToken', 'CFT').then(() => { return deployer.deploy(CryptoFlightFactory, CFToken.address); }); };

Whenever we call the deploy-function on the deployer-object it will return a Promise. When this Promise resolves it means that the contract got successfully deployed to the blockchain and we can access its address. The address gets attached to the contract-object itself and we can gain access to it. Depending on what version of Node you're running you might not be able to use the async/await syntax so we'll stick to the good old .then() here. When the first Promise resolves the arrow function passed to .then will trigger and we can deploy the CryptoFlightFactory contract, passing in CFToken.address as the second argument.

Next, open up CryptoFlightFactory and add an instance variable for the cfToken address, then create a constructor which takes in the address as an argument and sets it to cfToken. Now whenever we create a new CryptoFlight contract we will also pass the address to CFToken as an argument to the constructor in that contract:

CryptoFlightFactory.sol
address payable cfToken;

constructor(address payable _cfToken) public {
    cfToken = _cfToken;
}

function createFlight(uint256 minimumBid, string memory departure, string memory destination) public {
    CryptoFlight flight = new CryptoFlight(minimumBid, departure, destination, msg.sender, cfToken);
    deployedFlights.push(flight);
}

Which brings us to the last step, adding CFToken inside CryptoFlight. Open up CryptoFlight.sol and import import CFToken.sol and add an instance variable for cfToken. Then set this to be equal to the address of CFToken:

CryptoFlight.sol
pragma solidity >=0.5.0 <0.6.0;

import './CFToken.sol';

contract CryptoFlight {
    ...
    CFToken cfToken;
    ...

    constructor(uint256 _minBid, string memory _departure, string memory _destination, address payable _creator, address payable _cfToken) public {
        creator = _creator;
        minBid = _minBid;
        departure = _departure;
        destination =  _destination;
        CFToken _token = CFToken(_cfToken);
        cfToken = _token;
        finalized = false;
    }
...
}

As always, you can get the full code over at GitHub: https://github.com/ryggan/CryptoFlight/tree/master/contracts

Now we of course also need to make a small change in the frontend to let the user place bids using CFTokens instead of Ether. This is actually just one-line change, instead of calling addTraveller on the CryptoFlight-contract and attach Ether we'll call addTraveller on the CFToken-contract and pass in our bid as a value in terms of CFTokens:

await cfToken.methods.addTraveller(web3.utils.toWei(currentBid, 'milli'), show).send({ from: account });

(https://github.com/ryggan/CryptoFlight/blob/master/client/src/components/Flights.js)

Deploy the final version

We now have a pretty solid version of our dapp so to finish up the project we'll deploy it to the Ropsten network and update the frontend to be of the latest version. To do this, run:

truffle compile && truffle migrate --network ropsten --reset

Deploying probably takes a minute or two so just be patient. Then copy the address to CFToken and CryptoFlightFactory to config.js:

module.exports = {
  cfTokenAddress: '0x61511Ce174AC127F5Fd9C4eb4b86c3aED6aFC1Ff',
  cryptoFlightFactoryAddress: '0x5dA82F414c55590Ebca75aFF55B0417844195e13'
};

If you want you can use the addresses given above since these are valid deployments of the last version of our dapp. Then log into your server using ssh and make sure that NODE_ENV is set to 'production'. In Ubuntu it looks like this:

Now you're ready to deploy the frontend. In the client-folder you can find a very simple deployment script, deploy.sh. Here we suppose that the username for your server are the same as for your local machine (in my case "andreas") and that you set up the ssh-keys already. Also, make sure that you have the right to execute deploy.sh. Running if everything is correctly setup the output should be similar to (followed by the output from scp):

Hopefully everything ran smoothly and you can now navigate to your site and use the dapp. If you wanna try out the live version you can go to cryptoflight.andyafk.com

Wrap up

That's it, well done! We now have our very own dapp up and running. We learned both how to use Solidity to build a truly distributed application running on the Ethereum blockchain as well as creating our own token and make use of it in the contract. We also learned a bit of React, HTML, CSS, JavaScript and made use of Semantic UI. We learned how to set up an NGinx instance, configure the firewall, deploy the frontend to a real server and let other users access the dapp. Apart from that we are also familiarized ourselves with Geth, Ganache, Truffle, MetaMask, the ERC20 interface, the Ropsten testnet and the Remix editor.

Last updated