In last three posts of Building a Dapp on Ethereum Series, we have seen how to set up a ethereum node and communicate with it, how to write a smart contract, compile and deploy it on ethereum blockchain, and how to integrate IPFS with smart contract. In this post we will combine all these concepts to create a decentralized application CryptFolio.

CryptFolio app is about storing your cryptocurrency portfolio on ethereum blockchain and managing it using a smart contract. App's UI is built using ReactJS and it is bootstraped with create-react-app.
Jump directly to source code of CryptFolio

Structure and Flow of App

Basic idea behind CryptFolio app is to show how we can build a complete decentralized app putting all pieces together ie metamask, ethereum smart contract and ipfs.

Basic Flow

  1. User selects a account in Metamask

  2. App will then try to retrieve ipfs hash against selected account from smart contract.

  3. If ipfs hash is not found, app will ask user to build a cryptocurrency portfolio by adding addresses of listed currencies.
    3a. on portfolio finalization by user,
    3b. app will then ask for a passphrase to encrypt portfolio.
    3c. then store encrypted content on ipfs in turn recieving hash.
    3d. then store ipfs hash on ethereum using smart contract.

  4. If ipfs hash is found, app will ask for passphrase from user to decrypt portfolio,
    4a. app will then fetch ipfs file for retrieved hash.
    4b. then decrypt file using passphrase to get portfolio.

  5. Once portfolio is available, it is shown in tabular fashion with current prices and balances.

  6. If user does modify her portfolio, step 3 can be repeated.

Basic Structure

  1. Configuration parameters
  2. API's for IPFS integration
  3. Openpgp lib for encryption and decryption
  4. React containers and components
  5. Redux store for application state

Configuration

src/config/param.js contains configuration parameters about ethereum node, ipfs node smart contract and api's to access cryptocurrency balances, prices and exchange rates.

const infura = {
  mainnet: "https://mainnet.infura.io/<api-key>",
  ropsten: "https://ropsten.infura.io/<api-key>",
  rinkeby: "https://rinkeby.infura.io/<api-key>",
  kovan: "https://kovan.infura.io/<api-key>",
  ipfs: {
    host: "ipfs.infura.io",
    port: "5001",
    protocol: "https",
    gateway: "https://ipfs.infura.io"
  }
};
const local = {
  provider: "http://127.0.0.1:8545",
  ipfs: {
    host: "127.0.0.1",
    port: "5001",
    protocol: "http",
    gateway: "http://127.0.0.1:8080/"
  }
};
const provider = infura;
const contract = {
  address: "0x56e219ab01f70fc75c570b3ca98220d594b281f2",
  abi: [
    {
      anonymous: false,
      inputs: [{ indexed: false, name: "user", type: "address" }],
      name: "HashUpdated",
      type: "event"
    },
    {
      constant: false,
      inputs: [{ name: "hash", type: "string" }],
      name: "store",
      outputs: [],
      payable: true,
      stateMutability: "payable",
      type: "function"
    },
    {
      constant: true,
      inputs: [],
      name: "getStoredHash",
      outputs: [{ name: "hash", type: "string" }],
      payable: false,
      stateMutability: "view",
      type: "function"
    }
  ]
};

Adding and fetching IPFS content

src/ipfs/IpfsStore.js contains code to add and fetch content from ipfs api's.

import ipfsAPI from "ipfs-api";
import { provider } from "../config/params.js";

const ipfs = ipfsAPI(provider.ipfs.host, provider.ipfs.port, {
  protocol: provider.ipfs.protocol
});

const IpfsStore = {
  add: function(data) {
    return new Promise((resolve, reject) => {
      ipfs.files.add(data, function(err, files) {
        if (err) reject(err);
        else resolve(files[0].hash);
      });
    });
  },
  pin: function(hash) {
    return new Promise((resolve, reject) => {
      ipfs.pin.add(hash, function(err) {
        if (err) reject(err);
      });
    });
  },
  get: function(hash) {
    const path = "/ipfs/" + hash;
    return new Promise((resolve, reject) => {
      ipfs.files.get(path, function(err, files) {
        if (err) reject(err);
        else resolve(files[0].content);
      });
    });
  }
};

export default IpfsStore;

Encryption and Decryption

src/openpgp/EncDec.js contains code to encrypt and decrypt portfolio content before adding or fetching from IPFS.

import openpgp, { message } from "openpgp";

const EncDec = {
  encrypt: function(jsonStr, password) {
    let options = {
      message: message.fromText(jsonStr),
      passwords: [password],
      armor: false
    };
    return openpgp
      .encrypt(options)
      .then(ciphertext => new Buffer(ciphertext.message.packets.write()));
  },

  decrypt: async function(ciphertext, password) {
    let options = {
      message: await message.read(ciphertext),
      passwords: [password]
    };
    return openpgp.decrypt(options).then(plaintext => plaintext.data);
  }
};

export default EncDec;

Initialize redux store with web3

src/preLoadedState.js contains initialization code for web3js and smart contract.

import Web3 from "web3";
import { contract } from "./config/params";

function initialiseWeb3() {
  if (!window.web3) return {};
  const web3js = new Web3(window.web3.currentProvider);
  const contractInstance = web3js.eth
    .contract(contract.abi)
    .at(contract.address);
  return {
    web3js: web3js,
    contractInstance: contractInstance
  };
}
const exchangeRates = { rates: {} };
const initialState = {
  selectedAccount: "",
  selectedNetwork: "",
  isFetchingHash: false,
  storedHash: "",
  initialfolio: {},
  folio: {},
  tokens: {},
  balances: {},
  prices: {},
  isFetching: false,
  messages: [],
  isUpdating: false,
  lastTxHash: "",
  errors: []
};

export const preLoadedState = {
  selectedCurrency: "INR",
  initialisedWeb3: initialiseWeb3(),
  updatedState: initialState,
  exchangeRates: exchangeRates
};

React components and Redux store

src/containers and src/components contains code to display portfolio in a table and regularly update it with balances and prices. I will not go deeper into it, check source code for more info.

Conclusion

This brings us to end of Building a Dapp on Ethereum Series. Post this series
we can integrate web3js and metamask in our applications, can create and deploy smart contracts, can leverage IPFS to augment applications. Overall now we have a good understanding of ethereum as platform for building decentralized application.

If you have liked this article, please share it with others. Happy #BUIDLING