Building a Judicial Archiving System on Celo

Building a Judicial Archiving System on Celo https://celo.academy/uploads/default/optimized/2X/1/1209d461f40e60d298afb46b68c6bd1c5b51c45f_2_1024x576.png
none 0.0 0

Introduction​

A decentralized judicial archiving system is a type of blockchain-based system that enables the secure and transparent storage of legal documents and records. By utilizing blockchain technology, the system provides a tamper-proof and decentralized database that can be accessed by authorized parties from anywhere in the world. The decentralized nature of the system ensures that all records are stored in a distributed network of nodes, preventing data loss or manipulation. With a decentralized judicial archiving system, legal professionals, judges, and other authorized parties can access legal documents quickly and easily, increasing efficiency and transparency in the judicial process. Additionally, the system provides a secure and transparent method of storing and sharing sensitive legal information, reducing the risk of fraud and corruption.
In this tutorial we shall be looking at code examples and a step by step guide to setting up a simple archiving system.

Prerequisite

  • Node.js should be installed along with a node package manager(npm)
  • A basic understanding of Celo and smart contracts
  • A Celo Wallet address:
  • To create Alfajores wallet, go to Alfajores wallet
  • To get testnest funds, go to Celo faucet
  • A text editor such as Visual Studio Code installed on your computer
  • Remix IDE
  • Metamask
  • A Pinata account

You can clone this project on github by running:

git clone https://github.com/emiridbest/Judicial_Archiving_System.git

Let’s Begin…

This is what our Dapp will look like:

  • Step 1: Write your Smart Contract and Deploy on Remix IDE
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Library {
    struct document {
        uint256 id;
        string title;
        string pinataCID; // Pinata CID for the PDF file
        uint256 timestamp;
    }

    enum Role {
        Owner,
        Admin,
        licencedUser
    }

    modifier onlyAdmin() {
        require(
            roles[msg.sender] == uint256(Role.Admin),
            "Only Admin can call this."
        );
        _;
    }

    modifier onlyOwner() {
        require(
            roles[msg.sender] == uint256(Role.Owner),
            "Only Owner can call this."
        );
        _;
    }

    mapping(address => uint256) public roles;
    mapping(address => uint256) public userCount;

    document[] private documentList;
    mapping(uint => address) public owner;
    uint256 public documentCount;

    event DocumentAdded(uint256 id, string title, string pinataCID, uint256 timestamp);
    event RoleAssigned(address user, string role);

    function assignRole(address user, string memory role) public onlyOwner {
        if (keccak256(abi.encodePacked(role)) == keccak256(abi.encodePacked("Admin"))) {
            roles[user] = uint256(Role.Admin);
        } else if (keccak256(abi.encodePacked(role)) == keccak256(abi.encodePacked("licencedUser"))) {
            roles[user] = uint256(Role.licencedUser);
        } else {
            revert("Invalid role");
        }
        emit RoleAssigned(user, role);
    }



    function addDocument(string memory title, string memory pinataCID, uint256 timestamp) public onlyAdmin {
        uint256 id = documentList.length;
        documentList.push(document(id, title, pinataCID, timestamp));
        owner[id] = msg.sender;
        emit DocumentAdded(id, title, pinataCID, timestamp);
    }

    function getAllDocuments() public view returns (document[] memory) {
        return documentList;
    }

    function getDocument(uint256 id) public view returns (document memory) {
        return documentList[id];
    }
}

Now, we compile this contract then deploy on Injected web3 provider. This pops up our already install metamask wallet, make sure to choose Alfajores containing wallet. On deploying, a link to Alfajores Explorer pops up at the top of your browser. You can now copy out your contract address and save it somewhere as web3.js needs this address to interact with this particular contract. Also, go back to remix and copy out you contract ABI save it somewhere.

Now Let’s code the frontend:

  • Step 2: Set up your new react project.

Run this on your terminal

npx create-react-app legal

This should take a few minutes to complete depending on your internet connection. Navigate to the directory created Open the directory using your code editor. In this case, it would be vscode and you would run this on your terminal;

Code legal

This should open a vscode window with the legal directory.

  • Step 3: Update package.json file.

Now, update the content of the package.json by copying and pasting this;

{
  "name": "legal",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@celo-tools/use-contractkit": "^3.1.0",
    "@celo/contractkit": "^1.5.1",
    "@testing-library/jest-dom": "^5.16.1",
    "@testing-library/react": "^12.1.2",
    "@testing-library/user-event": "^13.5.0",
    "@web3uikit/core": "^0.2.45",
    "@web3uikit/web3": "^0.2.2",
    "react": "^18.2.0",
    "react-bootstrap": "^2.5.0",
    "react-dom": "^18.2.0",
    "react-error-overlay": "6.0.9",
    "react-scripts": "4.0.3",
    "web-vitals": "^2.1.4",
    "web3": "^1.7.0",
    "web3uikit": "0.1.159"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": ["react-app", "react-app/jest"]
  },
  "browserslist": {
    "production": [">0.2%", "not dead", "not op_mini all"],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "resolutions": {
    "react-error-overlay": "6.0.9"
  }
}

Step 4: Install all dependencies

npm install

Step 5: Start development server

npm start

Update the App.js file

This is the file that will contain codes that interact with our smart contract and also house all the components of the frontend. The App.js file should look like this:

import React, { useState, useEffect, useCallback } from "react";
import Web3 from "web3";
import { newKitFromWeb3 } from "@celo/contractkit";
import "./App.css";
import { contractABI, contractAddress } from "./utils/const";
import {
  AddDocument,
  Archive,
  AssignRole,
  Navbar,
  Welcome,
} from "./components/Index";
import "./App.css";

function App() {
  const [currentAccount, setCurrentAccount] = useState("");
  const [contract, setContractInstance] = useState(null);
  const [kit, setKit] = useState(null);
  const [formData, setFormData] = useState({ address: "", role: "" });
  const [role, setRole] = useState("");

  const [documents, setDocuments] = useState([]);

  const initContract = useCallback(async () => {
    try {
      if (!window.ethereum) {
        console.error("Celo Wallet extension not detected");
        return;
      }

      const web3 = new Web3(window.ethereum);
      const kit = newKitFromWeb3(web3);
      await window.ethereum.request({ method: "eth_requestAccounts" });
      const contract = new kit.web3.eth.Contract(contractABI, contractAddress);

      setCurrentAccount((await kit.web3.eth.getAccounts())[0]);
      setKit(kit);
      setContractInstance(contract);
    } catch (error) {
      console.log(error);
    }
  }, [contractAddress]);

  useEffect(() => {
    if (currentAccount) {
      initContract();
    }
  }, [currentAccount, initContract]);

  //connect wallet
  async function connectToWallet() {
    try {
      if (!window.ethereum) throw new Error("Wallet extension not detected");

      await window.ethereum.request({ method: "eth_requestAccounts" });
      const accounts = await window.ethereum.request({
        method: "eth_requestAccounts",
      });
      setCurrentAccount(accounts[0]);
    } catch (error) {
      console.log(error);
      throw new Error("Please install Celo Wallet extension");
    }
  }

  const handleChange = (e, name) => {
    setFormData((prevState) => ({ ...prevState, [name]: e.target.value }));
  };

  //assign role
  async function assignRole(address, role) {
    try {
      await contract.methods
        .assignRole(address, role)
        .send({ from: currentAccount, gasLimit: 3000000 });
      setRole(role);

      alert(`Role assigned to ${address} successfully!`);
    } catch (error) {
      console.error(error);
    }
  }

  //add document
  async function addDocument(title, pinataCID) {
    try {
      const timestamp = Math.floor(Date.now() / 1000);
      const tx = await contract.methods
        .addDocument(title, pinataCID, timestamp)
        .send({ from: currentAccount });
      console.log(tx);
    } catch (error) {
      console.error("Error details:", error);
    }
  }

  //get document count
  async function getDocumentCount() {
    try {
      const count = await contract.methods.getDocumentCount().call();
      console.log(count);
    } catch (error) {
      console.log(error);
    }
  }

  //get document
  async function getDocument(id) {
    try {
      const document = await contract.methods.getDocument(id).call();
      console.log(document);
      const url = "https://ipfs.io/ipfs/" + document.pinataCID;
      window.open(url, "_blank");
    } catch (error) {
      console.log(error);
    }
  }

  //get documents
  async function getAllDocuments() {
    try {
      const documents = await contract.methods.getAllDocuments().call();
      console.log(documents);
      setDocuments(documents); // Update the state here
    } catch (error) {
      console.log(error);
    }
  }

  return (
    <div>
      <div>
        <Navbar />
      </div>
      <div className="">
        <Welcome
          connectToWallet={connectToWallet}
          currentAccount={currentAccount}
        />
      </div>
      <div className="components">
        <AssignRole
          formData={formData}
          assignRole={assignRole}
          handleChange={handleChange}
        />
        <AddDocument
          formData={formData}
          addDocument={addDocument}
          handleChange={handleChange}
        />
        <Archive
          documents={documents}
          getDocumentCount={getDocumentCount}
          getAllDocuments={getAllDocuments}
          getDocument={getDocument}
        />
      </div>
    </div>
  );
}

export default App;

Now let’s break this down;

  • Step 6: Import all dependencies
import React, { useState, useEffect, useCallback } from "react";
import Web3 from "web3";
import { newKitFromWeb3 } from "@celo/contractkit";
import "./App.css";
import { contractABI, contractAddress } from "./utils/const";
import {
  AddDocument,
  Archive,
  AssignRole,
  Navbar,
  Welcome,
} from "./components/Index";
import "./App.css";
...

Here, we imported all the dependencies and then also imported our contractABI and contractAddress which we save earlier when we deployed our contract. This serves as the link between our frontend and the already deployed contract.

We also imported the react components which we will be seeing more of in this tutorial.

  • Step 7: Let’s use useState to keep track of some variables throughout the dapp.
...
  const [currentAccount, setCurrentAccount] = useState("");
  const [contract, setContractInstance] = useState(null);
  const [kit, setKit] = useState(null);
  const [formData, setFormData] = useState({ address: "", role: "" });
  const [role, setRole] = useState("");
  const [documents, setDocuments] = useState([]);
  ...
  • Step 8: Create functions for connecting to the network
...
const initContract = useCallback(async () => {
    try {
      if (!window.ethereum) {
        console.error("Celo Wallet extension not detected");
        return;
      }

      const web3 = new Web3(window.ethereum);
      const kit = newKitFromWeb3(web3);
      await window.ethereum.request({ method: "eth_requestAccounts" });
      const contract = new kit.web3.eth.Contract(contractABI, contractAddress);

      setCurrentAccount((await kit.web3.eth.getAccounts())[0]);
      setKit(kit);
      setContractInstance(contract);
    } catch (error) {
      console.log(error);
    }
  }, [contractAddress]);

  useEffect(() => {
    if (currentAccount) {
      initContract();
    }
  }, [currentAccount, initContract]);

  //connect wallet
  async function connectToWallet() {
    try {
      if (!window.ethereum) throw new Error("Wallet extension not detected");

      await window.ethereum.request({ method: "eth_requestAccounts" });
      const accounts = await window.ethereum.request({
        method: "eth_requestAccounts",
      });
      setCurrentAccount(accounts[0]);
    } catch (error) {
      console.log(error);
      throw new Error("Please install Celo Wallet extension");
    }
  }
...

The initContract is a function that initializes a contract instance and sets the currentAccount and kit state variables. It uses the useCallback hook to memoize the function and the async/await syntax to handle asynchronous calls to the Celo blockchain. initContract takes no arguments, but it depends on the contractAddress state variable.

connectToWallet is a function that connects to the Celo Wallet extension using the window.ethereum object. It sets the currentAccount state variable if the connection is successful. If the extension is not detected or if an error occurs, an error message is logged and an error is thrown.

  • Step 9: Create functions to call our smart contract functions

The assignRole function

 async function assignRole(address, role) {
    try {
      await contract.methods
        .assignRole(address, role)
        .send({ from: currentAccount, gasLimit: 3000000 });
      setRole(role);

      alert(`Role assigned to ${address} successfully!`);
    } catch (error) {
      console.error(error);
    }
  }
  ...

It takes two inputs: address and role.

The function asks the blockchain to update the user’s role, using the assignRole command.

The addDocument function

...
 async function addDocument(title, pinataCID) {
    try {
      const timestamp = Math.floor(Date.now() / 1000);
      const tx = await contract.methods
        .addDocument(title, pinataCID, timestamp)
        .send({ from: currentAccount });
      console.log(tx);
    } catch (error) {
      console.error("Error details:", error);
    }
  }
...

This function takes two inputs: title and pinataCIDfor storing files on IPFS).

It generates a timestamp representing the current time.

Then, it sends a transaction to the blockchain using a contract method named addDocument. This transaction requests the blockchain to add the provided document details.

  • Other Functions
...
  //get document count
  async function getDocumentCount() {
    try {
      const count = await contract.methods.getDocumentCount().call();
      console.log(count);
    } catch (error) {
      console.log(error);
    }
  }

  //get a document
  async function getDocument(id) {
    try {
      const document = await contract.methods.getDocument(id).call();
      console.log(document);
      const url = "https://ipfs.io/ipfs/" + document.pinataCID;
      window.open(url, "_blank");
    } catch (error) {
      console.log(error);
    }
  }

  //get a list of all the documents
  async function getAllDocuments() {
    try {
      const documents = await contract.methods.getAllDocuments().call();
      console.log(documents);
      setDocuments(documents); // Update the state here
    } catch (error) {
      console.log(error);
    }
  }
  ...
  • Step 10: Renders a user interface with various sub-components
...
 return (
    <div>
      <div>
        <Navbar />
      </div>
      <div className="">
        <Welcome
          connectToWallet={connectToWallet}
          currentAccount={currentAccount}
        />
      </div>
      <div className="components">
        <AssignRole
          formData={formData}
          assignRole={assignRole}
          handleChange={handleChange}
        />
        <AddDocument
          formData={formData}
          addDocument={addDocument}
          handleChange={handleChange}
        />
        <Archive
          documents={documents}
          getDocumentCount={getDocumentCount}
          getAllDocuments={getAllDocuments}
          getDocument={getDocument}
        />
      </div>
    </div>
  );
}

export default App;
  • Step 11: Create the Components and the import them into App.js. To do this we have to create a new folder called components in the src folder.

AssignRole.js

import React from "react";

const Input = ({ placeholder, name, type, handleChange }) => (
  <input
    placeholder={placeholder}
    type={type}
    name={name} // Add the 'name' attribute here
    onChange={(e) => handleChange(e, name)}
    className=""
  />
);

const AssignRole = ({ formData, assignRole, handleChange }) => {
  const handleSubmit = (e) => {
    const { address, role } = formData;
    e.preventDefault();
    if (!address || !role) return;
    console.log("Submitting:", formData); // Add a console log here to see the formData
    assignRole(address, role);
  };

  return (
    <div className="add">
      <label className="">Roles</label>

      <div className="">
        <Input
          placeholder="Address"
          name="address"
          type="text"
          handleChange={handleChange}
        />
        <select
          name="role"
          onChange={(e) => handleChange(e, "role")}
          className="role"
        >
          <option value="Admin">Admin</option>
          <option value="licencedUser">licencedUser</option>
        </select>
      </div>
      <div />
      <button type="button" onClick={handleSubmit}>
        Assign Role
      </button>
    </div>
  );
};

export default AssignRole;

AddDocument.js

import React, { useState } from "react";

const pinataApiKey = process.env.REACT_APP_PINATA_API_KEY;
const pinataSecretApiKey = process.env.REACT_APP_PINATA_API_SECRET;

const Input = ({ placeholder, name, type, value, handleChange, id }) => (
  <input
    id={id}
    name={name}
    placeholder={placeholder}
    type={type}
    value={value}
    onChange={(e) => handleChange(e)}
    className=""
  />
);

const AddDocument = ({ addDocument }) => {
  const [title, setTitle] = useState("");
  const [pinataCid, setPinataCid] = useState("");

  const handleChange = (e) => {
    const { name, value } = e.target;
    switch (name) {
      case "title":
        setTitle(value);
        break;
      default:
        console.error(`Invalid input field name: ${name}`);
    }
  };

  const handleFileChange = async (event) => {
    const file = event.target.files[0];

    const formData = new FormData();
    formData.append("file", file);
    formData.append("pinataMetadata", JSON.stringify({ name: file.name }));
    formData.append("pinataOptions", JSON.stringify({ cidVersion: 0 }));

    const response = await fetch(
      "https://api.pinata.cloud/pinning/pinFileToIPFS",
      {
        method: "POST",
        headers: {
          pinata_api_key: pinataApiKey,
          pinata_secret_api_key: pinataSecretApiKey,
        },
        body: formData,
      }
    );

    if (response.ok) {
      const result = await response.json();
      console.log("File added to IPFS with CID:", result.IpfsHash);
      setPinataCid(result.IpfsHash);
    } else {
      console.error("Error uploading file to IPFS:", response.status);
    }
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!title || !pinataCid) return;

    addDocument(title, pinataCid);
  };

  return (
    <div className="add">
      <label className="">Add Document</label>

      <div className="">
        <Input
          placeholder="Documment Title"
          name="title"
          type="text"
          value={title}
          handleChange={handleChange}
        />
        <input
          placeholder="pdf here"
          type="file"
          id="pdfFileInput"
          accept="application/pdf"
          onChange={handleFileChange}
          className="white-glassmorphism"
        />
      </div>
      <div />
      <button type="button" onClick={handleSubmit}>
        Add Document
      </button>
    </div>
  );
};

export default AddDocument;

Archive.js

import React from "react";

const Archive = ({ documents, getAllDocuments, getDocument }) => {
  const handleGetDocuments = async () => {
    try {
      await getAllDocuments();
    } catch (error) {
      console.error("Error retrieving document list:", error);
    }
  };

  return (
    <div className="docs">
      <h2>Document List</h2>
      <button onClick={handleGetDocuments}>Refresh List</button>
      <table>
        <thead>
          <tr>
            <th>Document ID</th>
            <th>Title</th>
            <th>Document Identifier</th>
            <th>Time Modified</th>
          </tr>
        </thead>
        <tbody>
          {documents &&
            Array.isArray(documents) &&
            documents.map((document, index) => (
              <tr key={index}>
                <td>{document.id}</td>
                <td>{document.title}</td>
                <td>{document.pinataCID}</td>
                <td>{new Date(document.timestamp * 1000).toLocaleString()}</td>
                <td>
                  <button onClick={() => getDocument(document.id)}>
                    Retrieve document
                  </button>
                </td>
              </tr>
            ))}
        </tbody>
      </table>
    </div>
  );
};

export default Archive;

Welcome.js

import React from "react";

const Welcome = ({ connectToWallet, currentAccount }) => {
  return (
    <div>
      <div className="welcome">
        <h1 className="">
          Retrieve all the Documents <br />
          You Ever Wanted
        </h1>
        <p className="">Explore The World Of Decentralized Judicial Archives</p>
      </div>
      <div className="connect">
        {!currentAccount && (
          <button type="button" onClick={connectToWallet}>
            <p className="button"> Connect Wallet</p>
          </button>
        )}
      </div>
    </div>
  );
};

export default Welcome;

Navbar.js

import React from "react";

import logo from "./logo512.png";

const NavbarItem = ({ title, classprops }) => <li>{title}</li>;

const Navbar = () => {
  return (
    <div className="nav">
      <nav className="navbar">
        <ul>
          <img src={logo} alt="logo" className="logo" />
          {["About Us", "Mission", "FAQ", "Contact"].map((item, index) => (
            <NavbarItem key={item + index} title={item} />
          ))}
        </ul>
      </nav>
    </div>
  );
};

export default Navbar;

Now, let’s bundle all the components into one file in the component folder so we can batch export them as one;

Index.js

export { default as Welcome } from "./Welcome";
export { default as Navbar } from "./Navbar";
export { default as AssignRole } from "./AssignRole";
export { default as AddDocument } from "./AddDocument";
export { default as Archive } from "./Archive";

There you go, our Dapp is ready.

Here is Source Code

Conclusion​

In conclusion, this tutorial has shown how to use blockchain to implement a judicial archiving system. Mastering these skills will help you become adept at building decentralized applications, making the most of blockchain’s security, transparency and reliability.

Next Steps​

  • One possible next step for the platform could be to introduce a subscription model or payable functions that would allow users to pay for retrieving documents.

About the Author​

Emiri Udogwu, a licensed medical doctor with a burning passion for technology and gifted with skills for spreading knowledge and propagating ideas. A web3 and frontend developer.

References​

4 Likes

Hi @EmiriDbest has this piece been approved for writing before you updated your proposal?

1 Like

Yes. It was been approved several weeks ago.

Its been completed and merged.

I had to flag it here since it was migrated into the academy as a new request instead of a published work.

Ouuu I see there wasn’t any need for that though, but its cool, I thought I saw the tags for a new proposal and it was being completed… :ok_hand:

1 Like

Following the flag report. It was resolved but i had to fix a typo i noticed while going through it.

I actually love this! A friend will love to learn this, and I can’t wait to have him under my tutelage. Thank you for this opportunity.

You have done an excellent job in providing a comprehensive technical tutorial that enables developers to build a Judicial Archiving System on Celo. The content is well-structured, informative, and empowers readers to implement the system successfully. Kudos to the writer for a job well done!

1 Like

Thanks man…i truly appreciate the compliments.