/isom3000h-accredichain

Web3.0 Accreditation System (ISOM3000H Project)

Primary LanguageTypeScript

ISOM3000H (aka MSBD5017) Group Project

This repository contains the source code of a dApp designed for this HKUST blockchain course project (Spring 2023).
Contributors (in alphabetical order):

CHUI, On Lam
HEUNG, Kai Him
LEE, Ho Wan Owen

Note that this course is actually MSBD5017, an MSc Big Data Technology masters level course, in disguise.

Accreditation System

The goal of this project is to create a decentralized Accreditation system for different organizations (Issuers) to issue Certificates to Applicants.

The specific objectives are as follows:

  • To allow Accreditation Issuers to mint certificate NFTs for applicants
  • To allow Issuers to view (and alter) status of Certificates and Accreditations
  • To allow outsiders (e.g. potential employers) to check Certificates obtained by each Applicant
  • To provide a UI for such interactions

Note that all Certificates and Accreditations issued are public (obviously because it's on a blockchain, duh).

UI Screenshots

home page

issuer details

Terminology

This accreditation system is rather complex for a beginner, and many terms seemingly convey similar concepts. As such, the terminology used in this project shall be explained in this section:

  • Issuer: An organization that defines and announces Accreditations, and issues Certificates to Applicants
  • Applicant: An individual/entity that applies for an Accreditation and can receive Certificates from Issuers
  • Accreditation: a type of qualification that Applicants can apply for, e.g. public examinations such as HKDSE/IB/GCE and product quality standards such as ISO-XXXXX, an NFT is minted with the Issuer as owner
  • Certificate: issued when an Applicant successfully qualifies for an Accreditation, equivalent to certificates issued on paper, e.g. public examination transcript and certificate of a product achieving a quality standard, an NFT is minted with the Applicant as owner

Methodology

The scope of this project is developing a simple yet production-ready dApp that consist of smart contracts deployed to a provided test blockchain network and a UI working on a web browser with MetaMask extension activated. All blockchain interactions shall be conducted via MetaMask.

The entire project is split into 3 parts: Smart Contract Programming, Front-end Interaction, and Final Group Presentation. Our team recommends that readers examine project code by following the order described in this documentation.

Steps of project setup are detailed in the Instructions section.

Part 1: Smart Contract Programming

In this part, the open-source Hardhat framework is adopted due to its production-readiness and flexibility.

The directory for this section is contract/.

This hardhat project was initialized with the command npx hardhat.

Part 1.1: Solidity Contracts

The Solidity language is used to draft smart contracts, due to its widespread popularity and availability of powerful open-source libraries such as base class contracts and tokens developed by OpenZeppelin.

The directory for this subsection is contracts/.

The smart contracts in this project adopt a modular design, meaning that each contract shall only serve one specific purpose, making it more scalable and easier to maintain and debug. Each "microservice" apart from the endpoint contracts have implemented access control such that only explicitly authorized contract addresses can call functions from and interact with each contract.

Part 1.1.1: Types

Types are defined in and imported from the contracts/types/ directory. 4 types are defined and used both internally, between contracts, and externally:

An additional type CompleteCert has been implemented to facilitate end users to obtain complete Certificate information, along with the corresponding Accreditation info. Note that this type is only used to return data to end users, and is not used within Contracts.

Part 1.1.2: Storage

Storage of data is a vital part of the application. The data management system formed by the following 4 storage Contracts acts as a database. These Contracts should only return data if the requesting address is an authorized Contract address.

Each internally used type has its own Storage system:

The defined data structure is designed to mirror relational database paradigm to reduce storage costs and maintain data integrity.

Part 1.1.3: NFT

Since every Certificate and Accreditation is issued or established in form of an NFT, the NFT Contracts will be implemented based on the ERC721 token standard. Specifically, 2 smart Contracts will inherit the ERC721 token implemented by OpenZeppelin:

Note that despite both Certificates and Accreditations have NFT issued to their corresponding owners to signify ownership, Certificate NFTs are much more significant than Accreditation NFTs because the degree of accountability required for Certificate owners and handlers is much higher.

Part 1.1.4: Endpoints

To clearly segregate the responsibilities of each Contract, each Contract should only have one functionality. Hence, an endpoint Contract is created for each major use case. Functions in Contracts documented in above sections will not be callable by any addresses other than the explicitly authorized Contracts addresses. Instead, functions in these endpoint Contracts will serve as API endpoints or entry points to our system. The endpoint Contracts are as follows:

Part 1.1.5: Compilation

After writing the smart Contracts, the Contracts can be compiled to EVM bytecode using the command npm start. The unit test command npm test, coverage command npm run coverage and deployment command npm run deploy:<insert environment> will also compile the Contracts before performing the corresponding tasks.

After compilation, 3 directories contract/artifacts/, contract/cache/ and frontend/src/types/typechain-types/ will be automatically generated. Do not manually modify these files.

Part 1.2: Unit Testing with TypeScript

TypeScript is used for unit testing due to compatibility with hardhat and the fact that similar code can be preliminarily tested and later reused for front-end development.

The directory for this subsection is test/.

Unit tests are written for each Storage, NFT, and Endpoint Contract, testing the correctness of functions defined in the smart Contracts.

After writing the unit tests, they can be executed using the command npm test. A complete log of the testing process will be available in the terminal.

Alternatively, instead of directly running unit tests, coverage can be obtained with the command npm run coverage, and the generated files can be removed with the command npm run clean:coverage. Note that the course TA expects >80% coverage for our project.

Following the test-driven development pattern, test cases are written alongside Contract implementation to make sure of Contract feature correctness.

Part 1.3: Contract Deployment

A deployment script to a blockchain network is available at /scripts/deploy.ts. For the purposes of our project, there are 3 possible networks to deploy to, each with description and corresponding commands below:

  • etherdata: The test net provided by the course TA. Expected to be the "Production" environment for presentation demo of the group project
    • Deployment command: npm run deploy:prod
  • polygon: Since it was difficult to request for tokens on the provided etherdata network, our group searched for another test net to perform development on, and we found that Polygon Mumbai provides such a service
    • Deployment command: npm run deploy:testnet
  • local: A local test net created using Ganache, a GUI software that can create one-click blockchain networks
    • Deployment command: npm run deploy:local

Network details are configured in /hardhat.config.ts. See Instructions section for more details.

In case of errors or overwritten contracts, clean up of compiled files can be performed by the command npm run clean:build.

During deployment, logs are generated, printed to console and saved into a log file at /scripts/logs/. The command npm run clean:logs can be used to delete all log files.

A very simple "checking" script at /scripts/check.ts is created for easy and rudimentary testing on whether or not the smart contracts can be called and interacted with correctly from clients. The script simulates a simple live demo by calling Endpoint contract functions, and can be used as a seeding script with further fine-tuning. The command npm run check:<insert environment> can be used to execute this script.

The command npm run clean can be used to clean up all generated files.

Part 2: Front-end Interaction

This Next.js project with TypeScript is initialized with the command npx create-next-app@latest, and is styled with the Material UI package.

The page contains several pages for registering, launching and viewing accreditations, and issuing and viewing certificates. The typical workflow is as follows:

  • Issuer:
    1. Connect to MetaMask to login
    2. Register as an issuer
    3. Launch an accreditation
    4. Issue a certificate to an applicant
  • Applicant:
    1. Connect to MetaMask to login
    2. Register as an applicant
    3. View certificates that he/she has received
  • Outsider (Guest):
    • Search an accreditation, issuer, certificate, or applicant

An API endpoint is also designed to dynamically generate certificate image given a certificate ID.

Part 3: Final Group Presentation

The deliverables include a set of presentation slides, a live group presentation where each member has 5 minutes, and a final project report. The materials have been uploaded to presentation/.

Repository structure

Instructions

  1. Install dependencies and other setup scripts:

    cd contract && npm install && npm start
    cd ../frontend && npm install
  2. In contract/:

    1. Set up the provided etherdata network in MetaMask browser extension:

      1. Add the network on MetaMask according to the instructions in this tutorial
      2. Obtain the account private key and place it in contract/.env file in the format PROD_PK=0x<Obtained Private Key>

        Note: Remember to add 0x in front of the private key

    2. [Optional] Set up a test network in MetaMask

      1. Add the network on MetaMask by scrolling to the bottom, finding the "Add Mumbai Network" button at the bottom right corner and clicking it
      2. Obtain the account private key and place it in contract/.env file in the format TEST_PK=0x<Obtained Private Key>
      3. To obtain test tokens, visit Polygon Faucet.
    3. [Optional] Set up a local test network on Ganache

      1. Create a quick local network on Ganache
      2. Obtain an account private key and place it in contract/.env file in the format LOCAL_PK=0x<Obtained Private Key>
      3. If the port number on Ganache is inconsistent with the default in the contract/.env file, replace the field with the number shown on Ganache
    4. In /hardhat.config.ts, set up the target deployment network:

      const config: HardhatUserConfig = {
        solidity: "0.8.9",
        networks: {
          etherdata: {
            url: "http://rpc.debugchain.net",
            accounts: process.env.PROD_PK !== undefined ? [process.env.PROD_PK] : [],
          },
        },
      };
      • etherdata is the alias of the network to be used in the deployment command (see next step)
        • Can be renamed (preferrably to your network name)
        • Other networks are set up similarly
      • url field stores the RPC endpoint
      • accounts field scrapes the account private key from the .env file and passes it as part of the deployment information
      • More networks are configured for project use
    5. In /package.json, configure the deployment command:

      "scripts": {
        "deploy:prod": "hardhat run scripts/deploy.ts --network etherdata",
        "deploy:testnet": "hardhat run scripts/deploy.ts --network polygon",
        "deploy:local": "hardhat run scripts/deploy.ts --network local"
      }
      • As mentioned in the previous step, etherdata is the network alias configured in /hardhat.config.ts
      • For our project, the test networks polygon and local are also configured
      • To deploy to etherdata, use npm run deploy:prod
      • To deploy to polygon, use npm run deploy:testnet
      • To deploy to local, use npm run deploy:local
    6. In /scripts/deploy.ts, configure the smart contracts to be deployed:

      const Contract = await ethers.getContractFactory("ContractName");
      const contract = await Contract.deploy();
      await contract.deployed();
      
      console.log("Contract deployed to:", contract.address);
      • The ethers.getContractFactory() method takes a string argument of the contract name (i.e. the title of a Solidity contract)
        • e.g. if a SampleContract.sol containing contract SampleContract {} is to be deployed, pass "SampleContract" into this method
      • Add the names of all contracts to be deployed to this file
      • If a contract A calls functions from another contract B, first deploy contract B, and pass address of contract B as an argument when deploying contract A with the .deploy() function
        • On Solidity, add contract B address as a parameter in constructor of contract A
        • If contracts are codependent, create another setup function in contract A to store deployed address of contract B
    7. Deploy the smart contracts to a configured network using one of the npm commands specified in Step 5

    8. After deployment, the smart contract addresses will be printed in the terminal output, and in a log file at /scripts/logs/. Contract addresses can be obtained for further use

      • Note that the logs will not be committed to GitHub
    9. Details of deployed contracts can be viewed on:

  3. In frontend/:

    1. Fill in the blockchain network and deployed contract information in contracts.config.ts
    2. Copy the abi JSON files for Endpoint contracts from contract/artitacts/contracts/* to frontend/src/blockchain/abi/
    3. Start the frontend by the command npm run dev or npm start and go to the dev URL

Useful URLs

References