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:
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:
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:
(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:
(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:
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:
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:
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:
And don't forget to import the components from semantic-ui by adding the following import statement at the top:
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:
Declare an initial state object:
Create a new asynchronous function for sending transactions:
And finally update the render-function to include the following:
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:
(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:
You can also remove the account-assignment from the initial setState-call. Your componentDidMount in App.js should now look like:
(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:
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:
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):
(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:
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:
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:
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:
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:
(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:
Deploying probably takes a minute or two so just be patient. Then copy the address to CFToken and CryptoFlightFactory to config.js:
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.
References and Useful links
Last updated