# App to live LIVE DEMO
by Dev Advocate Dulce Villarreal
## Table of content
* [Pre-requisites](#Pre-requisites)
* [Objectives](#Objectives)
* [Run the app](#Run-the-app)
* [Clone this repo](#Clone-this-repo)
* [Install dependencies](#Install-dependencies)
* [Run the project](#Run-the-project)
* [App to dApp](#App-to-dApp)
* [Change the branch](#Change-the-branch)
* [Run swarm](#Run-swarm)
* [Getting the questions from swarm](#Getting-the-questions-from-swarm)
* [Init truffle](#Init-truffle)
* [Specify the soldity compiler version](#Specify-the-soldity-compiler-version)
* [Create a contract](#Create-a-contract)
* [Get the gas price](#Get-the-gas-price)
* [Config truffle](#Config-truffle)
* [Add the testnet rsk network](#Add-the-testnet-rsk-network)
* [Run migrations](#Run-migrations)
* [Modify the front end](#Modify-the-front-end)
* [Adding web3.js](#Adding-web3.js)
* [Getting the questions Second part](#Getting-the-questions-Second-part)
* [Modify the front end](#Modify-the-front-end)
## Pre-requisites or Set up
Before we begin,
you will need the following things set up on your system:
0. The basics
- Terminal:
- A [POSIX](https://en.wikipedia.org/wiki/POSIX)-compliant terminal
- Recommended option for Linux/ Mac: Default/ built in terminal. I'm using ["Oh My ZSH!"](https://ohmyz.sh/)
- Recommended option for Windows: [Git Bash](https://gitforwindows.org/) Here's a [great tutorial](https://www.atlassian.com/git/tutorials/git-bash) on installing and using Git Bash.
- [`git`](https://git-scm.com/)
- [`curl`](https://curl.haxx.se/)
- [Visual Studio Code](https://code.visualstudio.com/)
- [NodeJs](https://nodejs.org/en/)
- optional, only needed to preview site using a centralised HTTP server
- Recommended install method for Linux/ Mac: [`nvm`](https://github.com/nvm-sh/nvm)
- Recommended install method for Windows: [Official installer](https://nodejs.org/en/)
1. Install wallet browser
1.1 [Nifty Chrome extension](https://chrome.google.com/webstore/detail/nifty-wallet/jbdaocneiiinmjbjlgalhcelgbejmnid?hl=en)
1.2 [Metamask instructions](https://developers.rsk.co/tutorials/ethereum-devs/remix-and-metamask-with-rsk-testnet/)
2. Get some tokens in the [RSK faucet](https://faucet.rsk.co/)
3. Truffle.
To install truffle, enter the following commands in your terminal;
```bash=
$ npm install -g truffle
```
4. Install an add-on to enable CORS.
- In Mozilla use [Cors Everywhere](https://addons.mozilla.org/en-US/firefox/addon/cors-everywhere/)
- In Chrome use [Allow CORS](https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf?hl=en)
5.Swarm
## Installing Swarm
The easiest way to install Swarm is via its pre-compiled releases.
There are also instructions for
[compiling the source yourself instead](https://developers.rsk.co/rif/storage/providers/swarm/install/).
Visit [swarm.ethereum.org/downloads](https://swarm.ethereum.org/downloads/)
and select the appropriate package to install for your system.
This page should automatically select and highlight the right one for you (in bold).
Example commands on Linux.
```shell
curl https://ethswarm.blob.core.windows.net/builds/swarm-linux-amd64-0.5.7-5ccfd995.tar.gz > swarm-linux-amd64-0.5.7-5ccfd995.tar.gz
tar -zxvf swarm-linux-amd64-0.5.7-5ccfd995.tar.gz
mkdir -p ${HOME}/swarm/bin
mv swarm-linux-amd64-0.5.7-5ccfd995/swarm ${HOME}/swarm/bin
echo 'export PATH=$PATH:${HOME}/swarm/bin' >> ~/.zshrc
```
If you have bashrc terminal you must remplace ~/.zshrc for ~/.bashrc.
Mac OSX or Windows (with a POSIX-compliant shell such as git bash)
should be pretty similar.
Close this shell and open up a new one,
as we'll need the updated `PATH` environment variable.
Let's check that we have got a working binary.
```shell
swarm version
```
```text
Swarm
Version: 0.5.8-unstable
Git Commit: 6faff7fcb6f25c706e75d8d3c8945c4231663b93
Go Version: go1.14.3
OS: linux
```
Next, let's start swarm.
```shell
swarm
```
```text
INFO [05-19|15:03:36.058] Maximum peer count ETH=50 LES=0 total=50
INFO [05-19|15:03:36.059] You don't have an account yet. Creating one...
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase:
Repeat passphrase:
Unlocking swarm account 0xD1bCFFf13f996247d8A84a37bC7b32436B40c62F [1/3]
Passphrase:
```
> Note that Swarm is a service which uses peer to peer networking.
> Your computer is one node of many connected to this same network,
> and talking to this same protocol.
> Therefore, the very first time that you start up Swarm on your computer,
> you will be prompted to create an account,
> which will be used to uniquely identify this particular node -
> that is what the password is for.
```text
INFO [05-19|15:03:53.009] Starting peer-to-peer node instance=swarm/v0.5.8-6faff7fc/linux-amd64/go1.14.3
INFO [05-19|15:03:53.065] New local node record seq=1 id=0f1272cb73bcf1ba ip=127.0.0.1 udp=30399 tcp=30399
INFO [05-19|15:03:53.065] Updated bzz local addr oaddr=5c31b4c2924e4689554b80893c663833de5852b32f090969860739dbdb1a69c0 uaddr=enode://d18081c0f7bf09c021d519e0d8351473def7a408820bffabc62bf2e878fd2ff84df3b46407ab347d632dfbec4f13cd7635ea2ee4c8fdace17c442ae032615d48@127.0.0.1:30399
INFO [05-19|15:03:53.065] Starting bzz service
INFO [05-19|15:03:53.065] Starting hive baseaddr=5c31b4c2
INFO [05-19|15:03:53.066] Detected an existing store. trying to load peers
INFO [05-19|15:03:53.066] hive 5c31b4c2: no persisted peers found
INFO [05-19|15:03:53.066] Swarm network started bzzaddr=5c31b4c2924e4689554b80893c663833de5852b32f090969860739dbdb1a69c0
INFO [05-19|15:03:53.066] bzzeth starting...
INFO [05-19|15:03:53.066] Starting outbox
INFO [05-19|15:03:53.066] Started P2P networking self=enode://d18081c0f7bf09c021d519e0d8351473def7a408820bffabc62bf2e878fd2ff84df3b46407ab347d632dfbec4f13cd7635ea2ee4c8fdace17c442ae032615d48@127.0.0.1:30399
INFO [05-19|15:03:53.066] Started Pss
INFO [05-19|15:03:53.066] Loaded EC keys pubkey=04fbdbfa2ee4034122e076512e390f8348cf1d2dd3a249f8f49ff5178e917cd18dccfc42cea2a2e906f25d7cb88b61b205a73b0bb2b07d43de0ca6c2708a0dc058 secp256=02fbdbfa2ee4034122e076512e390f8348cf1d2dd3a249f8f49ff5178e917cd18d
INFO [05-19|15:03:53.066] starting bzz-retrieve
INFO [05-19|15:03:53.066] Starting Swarm HTTP proxy port=8500
INFO [05-19|15:03:53.068] Mapped network port proto=tcp extport=30399 intport=30399 interface=NAT-PMP(192.168.50.1)
INFO [05-19|15:03:53.069] IPC endpoint opened url=/home/bguiz/.ethereum/bzzd.ipc
INFO [05-19|15:03:53.070] Mapped network port proto=udp extport=30399 intport=30399 interface=NAT-PMP(192.168.50.1)
INFO [05-19|15:03:53.248] New local node record seq=2 id=0f1272cb73bcf1ba ip=172.23.144.94 udp=30399 tcp=30399
ERROR[05-19|15:04:06.517] batch has timed out peer=3de6224e3c9c430f:656e6f64653a2f2f ruid=3716585580
```
Now visit [http://localhost:8500](http://localhost:8500) and you will see a web user interface
for downloading and uploading files.
Have a play around with this if you like,
otherwise jump back into your terminal.
You should see output similar to this related to serving up the front end.
```text
INFO [05-19|15:08:08.421] created ruid for request ruid=ffcc6158 method=GET url=/
INFO [05-19|15:08:08.421] respondHTML ruid=ffcc6158 code=200
INFO [05-19|15:08:08.422] request served ruid=ffcc6158 code=200 time=570.234µs
INFO [05-19|15:08:08.453] created ruid for request ruid=d89959fa method=GET url=/favicon.ico
INFO [05-19|15:08:08.453] request served ruid=d89959fa code=200 time=41.936µs
```
## Objectives
- Use your JS portfolio to create a web3 and blockchain portafolio by reusing old apps and transforming to dApps.
- Learn smart contracts and decentralize storage.
- Create a fun dApp.
## Run the app
In order to run the simple JS app, follow the next instructions:
### Clone repo
Clone this repo, using the commands below;
```bash=
$ git clone https://github.com/rsksmart/quiz-dapp
$ cd quiz-dapp
```
### Install dependencies
```bash=
$ npm install
```
### Run the project
```bash=
$ npm run start
```
It should open a new tab in your browser.
Note the questions are stored in the `questions.json` file.
### Architecture
```sequence
User -> FE: Load page
FE -> FE: Get questions
FE --> User: Page files (static)
User -> FE: Submit responses
FE --> User: Display results
```
## App to dApp
It's time to convert your simple plain JS app to an dApp!
### Change the branch
Change to `tutorial` branch
```bash=
$ git checkout tutorial
```
Install dependencies
```bash=
$ npm install
```
### Run swarm
Run swarm in a new terminal and don't close it.
```bash=
$ swarm
```
Open a new terminal and upload your questions to swarm decentralized storage. When completed, swarm returns a hash.
```bash=
$ cd js
$ swarm --defaultpath questions.json up questions.json
# 54347e7150fdfa881f56d9845976b6d541930e60a16d6f5cd6877a6c3df31827
```
Copy the hash returned and get the file info:
```bash=
$ curl -s http://localhost:8500/bzz-raw:/54347e7150fdfa881f56d9845976b6d541930e60a16d6f5cd6877a6c3df31827 | jq
{
"entries": [
{
"hash": "809b82283b3d0c3459fde4340ecb6a7a297b1d25e6cab6084d21257ba29e82c2",
"contentType": "application/json",
"mode": 436,
"size": 2014,
"mod_time": "2020-06-23T03:40:25-05:00"
}
]
}
```
You can verify the file content by sending a request to this new hash
```bash=
curl -s http://localhost:8500/bzz-raw:/809b82283b3d0c3459fde4340ecb6a7a297b1d25e6cab6084d21257ba29e82c2 | jq
{
"questions": [
{
"id": 1,
"question": " ...? ",
"nextId": 2,
"previousId": null,
...
```
### Get the questions from swarm
**Important:** You need to get CORS disabled!
Add the new data source in `app.js`:
Before:
```javascript=
mounted: () {
axios.get('js/questions.json')
...
}
```
After:
```javascript=
mounted: () {
axios.get('http://localhost:8500/bzz-raw:/809b82283b3d0c3459fde4340ecb6a7a297b1d25e6cab6084d21257ba29e82c2')
}
```
Your data source is not static anymore. Your quiz application is now loading its data from a decentralised file store.
**Congratulations, your app is on its way to becoming a DApp!**
### Initialize truffle
Initialize truffle using the commands below;
```bash=
truffle init
```
It will create the `contract` folder, `migrations`, and `truffle-config.js` file.
Note: if you don't have truffle installed, you can install it using the command below:
```bash=
$ npm install -g truffle
```
### Specify the solidity compiler version
In the `truffle-config.js` file, in the `compilers` section, we need to change the solidity compiler version
```javascript=
compilers: {
solc: {
version: "^0.4.14", // Just change this line
}
}
```
### Create a contract
In the `contracts` folder, create a new file named `Quiz.sol`.
Copy and paste the following code in your editor:
```
pragma solidity ^0.4.14;
contract Quiz {
address public owner;
uint8[4] rightAnswers = [2, 3, 1, 4];
address[] public players;
uint8[] public playerHits;
string public questions;
event AnswerEvent(uint8 hitsCounter);
constructor() public {
owner = msg.sender;
}
function getPlayerHits() public view returns (uint8[] memory) {
return playerHits;
}
function getPlayers() public view returns (address[] memory) {
return players;
}
function answerQuestions (uint8[] playerAnswers) public returns (uint8) {
uint8 hits;
for (uint i = 0; i < playerAnswers.length; i++) {
if (playerAnswers[i] == rightAnswers[i]) {
hits++;
}
}
players.push(msg.sender);
playerHits.push(hits);
emit AnswerEvent(hits);
return hits;
}
}
```
Now, in the terminal run the `compile` command
```bash=
truffle compile
```
### Get the gas price
Get and save the gas price from `testnet`:
```bash=
$ curl https://public-node.testnet.rsk.co/2.0.1/ -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":1}' > .gas-price-testnet.json
$ ls -al
```
This command will create a new file named `.gas-price-testnet.json` with the gas price from `testnet` and can be verified with the command `ls -al`
### Config truffle
Install the Wallet Provider and `dotenv` dependency:
```bash=
npm install @truffle/hdwallet-provider dotenv --save
```
Create a new file named `.env` in the root directory of your project and write your mnemonic on it.
```bash=
$ cd .. # change to root directory
$ touch .env
```
```
A_MNEMONIC='your twelve words mnemonic ...'
```
Remember: Don't share or deploy your mnemonic never!
You should add the `.env` file to your `.gitignore` file.
```bash=
echo ".env" >> .gitignore
```
Open your `truffle-config.js` file and add the following lines at the beginning of the file:
```javascript=
const fs = require('fs');
const HDWalletProvider = require('@truffle/hdwallet-provider');
require('dotenv').config();
const A_MNEMONIC = process.env.A_MNEMONIC;
const gasPriceTestnetRaw = fs.readFileSync(".gas-price-testnet.json").toString().trim();
const gasPriceTestnet = parseInt(JSON.parse(gasPriceTestnetRaw).result, 16);
if (typeof gasPriceTestnet !== 'number' || isNaN(gasPriceTestnet)) {
throw new Error('unable to retrieve network gas price from .gas-price-testnet.json');
}
console.log(gasPriceTestnet);
```
### Add the testnet rsk network
In the same `truffle-config.js` in the `networks` section, add the testnet configuration:
```javascript=
testnet: {
provider: () => new HDWalletProvider(A_MNEMONIC, 'https://public-node.testnet.rsk.co/2.0.1/'),
network_id: 31,
gasPrice: Math.floor(gasPriceTestnet * 1.1),
networkCheckTimeout: 1e9
},
```
You can test your connection by running the following commands in your terminal
```bash=
$ truffle console --network testnet
65000000
truffle(testnet)>
```
### Run migrations
Add a new file named `2_deploy_contracts.js`.
```bash=
$ touch migrations/2_deploy_contracts.js
```
Then open `2_deploy_contracts.js` and write the following code:
```javascript=
var Quiz = artifacts.require('Quiz');
module.exports = function (deployer) {
deployer.deploy(Quiz)
}
```
Run the migrations:
If you are in the truffle console then just write `migrate`:
```bash=
truffle(testnet)> migrate
```
Otherwise
```bash=
$ truffle migrate --network testnet
```
### Result of migrations
You should see something similar to the following output in your terminal.
```bash=
$ truffle(testnet)> migrate
Compiling your contracts...
===========================
✔ Fetching solc version list from solc-bin. Attempt #1
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/Quiz.sol
✔ Fetching solc version list from solc-bin. Attempt #1
> Artifacts written to /home/dulce/dev/rsk/appjs2dapp-workshop-rsk-dulce/build/contracts
> Compiled successfully using:
- solc: 0.4.26+commit.4563c3fc.Emscripten.clang
Starting migrations...
======================
> Network name: 'testnet'
> Network id: 31
> Block gas limit: 6800000 (0x67c280)
1_initial_migration.js
======================
Deploying 'Migrations'
----------------------
> transaction hash: 0x544c68d0a8b13d6a2471a3baa8c5358c052475749c183a79ac04d34913b8b7a5
> Blocks: 0 Seconds: 21
> contract address: 0x9BD85b119C9F0b491ef3D1AbD8d99C4C360896f3
> block number: 957437
> block timestamp: 1592959920
> account: 0x8FCC0638F6F20cE2C468c7a7a2eA84b1cf6Cb1eE
> balance: 2.750087998627249784
> gas used: 196887 (0x30117)
> gas price: 0.0715 gwei
> value sent: 0 ETH
> total cost: 0.0000140774205 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.0000140774205 ETH
2_deploy_contracts.js
=====================
Deploying 'Quiz'
----------------
> transaction hash: 0xb8b6917afe56551b0ba2b0e937b574c21ddbf7cdc4662101100e21d6db847922
> Blocks: 1 Seconds: 39
> contract address: 0xF9F201aE6e34d8B4CC60f998413a161eF5FE65AF
> block number: 957440
> block timestamp: 1592960035
> account: 0x8FCC0638F6F20cE2C468c7a7a2eA84b1cf6Cb1eE
> balance: 2.750029175577249784
> gas used: 780714 (0xbe9aa)
> gas price: 0.0715 gwei
> value sent: 0 ETH
> total cost: 0.000055821051 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.000055821051 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.0000698984715 ETH
```
Exit from truffle console with `Ctrl + C` or typing `.exit`
## Modify the front end
### Adding web3.js
In `index.html`, before the rest of the scripts, we are going to add the following:
Generally all the scripts are included in the open and closed tag`</body>`
```htmlembedded=
<script src="js/truffle-contract.js"></script>
```
Then, include the `web3` CDN just before the `js/app.js` script tag
```htmlembedded=
<script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
<script src="js/app.js"></script>
```
The order of the scripts will be:
1. `truffle-contract.js` <- truffle contracts library
2. `vue.js` <- Vue CDN library
3. `bulma-steps.min.js` <- Bulma steps (used to render the questions in a step by step form).
4. `axios.min.js` <- The axios library. (used to get the questions data from DS).
5. `web3.min.js` <- Web3 library
6. `app.js` <- our app script
```htmlmixed=
<script src="js/truffle-contract.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/bulma-steps@2.2.1/dist/js/bulma-steps.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
<script src="js/app.js"></script>
```
### Replace mounted method content
In the `app.js` file, clear the `mounted` method and add this new lines:
```javascript=
mounted () {
this.initWeb3()
.then(() => {
console.log('App initialized')
})
},
```
### Replace the method section
Replace the `method` section
```javascript=
methods: {
// get the questions from swarm !!!
async initWeb3() {
await axios.get('http://localhost:8500/bzz-raw:/07f1112168025f4c309b652fb364b9ea126728e7b0959338b0bf9f5187d474d0')
.then(response => {
this.questions = response.data.questions
})
if (window.ethereum) {
this.web3Provider = window.ethereum
try {
// request account access
await window.ethereum.enable()
} catch (error) {
console.error("user denied account access")
}
} else if (window.web3) {
this.web3Provider = window.web3.currentProvider
} else {
this.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545')
}
// Inicialized web3 !!!
web3 = new Web3(this.web3Provider)
return this.initContract()
},
//Load the compiled contract information -> Quiz.json
initContract: function (params) {
return axios.get('../build/contracts/Quiz.json')
.then(response => {
const QuizArtifact = response.data
this.contracts.Quiz = TruffleContract(QuizArtifact)
// Set the provider for our contract
this.contracts.Quiz.setProvider(this.web3Provider)
})
},
selectOption: function (evt) {
if (evt) {
document.querySelectorAll('div.answer').forEach(element => {
element.classList.remove('answer') // disable css hover effect
})
const element = evt.target
const answer = element.dataset.i + 1
const index = parseInt(element.dataset.index) + 1
const question = this.getQuestion(index)
this.answers.push(answer)
this.counter++
if (this.counter === this.questions.length) {
// show modal and return
document.querySelector('div.modal').classList.add('is-active')
}
setTimeout(function () {
// trigger click event on <a> next button
document.querySelector('a[data-nav="next"]').click()
document.querySelectorAll('div.option-box').forEach(element => {
element.classList.add('answer') // enable css hover effect
})
}, 500) // Wait one second after answer each question.
}
},
sendQuestions: function () {
const contract = this.contracts.Quiz
web3.eth.getAccounts((error, accounts) => {
if (error) {
console.error(error)
}
const account = accounts[0]
this.contracts.Quiz.deployed()
.then(instance => {
const quizInstance = instance
return quizInstance.answerQuestions(this.answers, { from: account })
})
.then(result => {
document.querySelector('div.modal').classList.remove('is-active')
})
.catch(err => {
console.error(err)
})
})
},
//method that return the question by index
getQuestion: function (index) {
for (let i = 0; i < this.questions.length; i++) {
if (index === this.questions[i].id) {
return this.questions[i]
}
}
return false
}
}
```
# Last step!
Now reload your app in a browser, and interact with it.
## Congratulations, your app is not interacting with a smart contract, and your app is decentralised!