Developing a Simple Decentralized Microlending Platform using Solidity on Celo

Developing a Simple Decentralized Microlending Platform using Solidity on Celo https://celo.academy/uploads/default/optimized/2X/f/f7be3be6cd3af67e2a9d0877bae31dd5bc01886c_2_1024x576.png
none 5.0 1

Introduction

Decentralized microlending platforms are an innovative use case for blockchain technology, enabling lenders and borrowers to interact directly without the need for traditional financial intermediaries. In this tutorial, we’ll explore how to design and implement a decentralized microlending platform using Solidity. By the end of this tutorial, you’ll have a solid understanding of how to develop a smart contract-based microlending platform using Solidity, and how to deploy it on the Celo Blockchain

Here’s the github repo of our code. source code

Prerequisites

To follow this tutorial, you will need the following:

  • Solidity programming language.
  • Remix IDE.
  • The celo Extension Wallet.

SmartContract

Complete contract.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;

// SafeMath library to perform safe arithmetic operations
library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "Addition overflow");

        return c;
    }

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "Multiplication overflow");

        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0, "Division by zero");
        uint256 c = a / b;

        return c;
    }
}

contract MicroLending {
    using SafeMath for uint256;

    struct Loan {
        uint amount;
        uint collateral;
        address payable borrower;
        address payable lender;
        uint dueDate;
        uint lowestInterestRate;
        bool isPaid;
    }

    struct LoanProposal {
        uint loanId;
        address payable lender;
        uint interestRate;
    }

    Loan[] public loans;
    LoanProposal[] public loanProposals;

    function requestLoan(uint amount, uint collateral, uint dueDate) public payable {
        require(msg.value == collateral, "Send the collateral amount");
        loans.push(Loan(amount, collateral, payable(msg.sender), payable(address(0)), dueDate, 100, false));
    }

    function proposeLoan(uint loanId, uint interestRate) public {
        Loan storage loan = loans[loanId];
        require(loan.lender == payable(address(0)), "Loan already approved");
        require(interestRate < loan.lowestInterestRate, "Propose a lower interest rate");
        loan.lowestInterestRate = interestRate;
        loanProposals.push(LoanProposal(loanId, payable(msg.sender), interestRate));
    }

    function approveLoan(uint loanProposalId) public payable {
        LoanProposal storage loanProposal = loanProposals[loanProposalId];
        Loan storage loan = loans[loanProposal.loanId];
        require(loan.lender == payable(address(0)), "Loan already approved");
        require(msg.sender == loan.borrower, "Only borrower can approve loan");
        require(msg.value == loan.amount, "Send the loan amount");
        loan.lender = loanProposal.lender;
    }

    function repay(uint loanId) public payable {
        Loan storage loan = loans[loanId];
        require(msg.sender == loan.borrower, "Only borrower can repay");

        uint repaymentAmount = loan.amount.add(loan.amount.mul(loan.lowestInterestRate).div(100));
        if (block.timestamp > loan.dueDate) {
            repaymentAmount = repaymentAmount.add(repaymentAmount.mul(10).div(100)); // add 10% late penalty
        }
        require(msg.value == repaymentAmount, "Send the full repayment amount");
        require(loan.isPaid == false, "Loan is already repaid");
        loan.isPaid = true;
        loan.lender.transfer(msg.value);
        loan.borrower.transfer(loan.collateral);
    }

    function seizeCollateral(uint loanId) public {
        Loan storage loan = loans[loanId];
        require(msg.sender == loan.lender, "Only lender can seize collateral");
        require(block.timestamp > loan.dueDate, "Can only seize collateral after due date");
        require(loan.isPaid == false, "Loan is already repaid");
        loan.lender.transfer(loan.collateral);
        loan.isPaid = true; // set to true to prevent multiple seizures
    }

    function getLoansCount() public view returns (uint) {
        return loans.length;
    }

    function getLoanProposalsCount() public view returns (uint) {
        return loanProposals.length;
    }
}

SafeMath Library

Before we dive into the MicroLending contract, let’s take a look at the SafeMath library that it uses.

// SafeMath library to perform safe arithmetic operations
library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "Addition overflow");

        return c;
    }

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "Multiplication overflow");

        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0, "Division by zero");
        uint256 c = a / b;

        return c;
    }
}

The SafeMath library is a set of functions designed to perform mathematical operations in a safe way, without running into integer overflows or underflows. It includes the following functions:

  • add: This function adds two unsigned integers and throws an error on overflow.
  • mul: This function multiplies two unsigned integers and throws an error on overflow.
  • div: This function divides two unsigned integers and throws an error on division by zero.

The SafeMath library is then used in the MicroLending contract using the using keyword. This allows the contract to use the functions defined in SafeMath as if they were methods of the uint256 data type.

MicroLending Contract Breakdown

Data Structures

The MicroLending contract contains two struct types: Loan and LoanProposal . It also contains two public array state variables loans and loanProposals which store all loan objects and loan proposal objects respectively.

   struct Loan {
        uint amount;
        uint collateral;
        address payable borrower;
        address payable lender;
        uint dueDate;
        uint lowestInterestRate;
        bool isPaid;
    }

    struct LoanProposal {
        uint loanId;
        address payable lender;
        uint interestRate;
    }

The Loan struct has the following fields:

  • amount: The amount of the loan requested by the borrower.
  • collateral: The amount of collateral that the borrower is willing to provide for the loan.
  • borrower: The address of the borrower.
  • lender: The address of the lender who funds the loan.
  • dueDate: The date by which the loan needs to be repaid.
  • lowestInterestRate: The lowest interest rate offered by any lender for this loan.
  • isPaid: A boolean flag to indicate whether the loan has been repaid or not.

The LoanProposal struct has the following fields:

  • loanId: The ID of the loan that the proposal corresponds to.
  • lender: The address of the lender who is proposing to fund the loan.
  • interestRate: The interest rate proposed by the lender.

Functions

The MicroLending contract has several public functions that allow interaction with the contract.

requestLoan

function requestLoan(uint amount, uint collateral, uint dueDate) public payable {
        require(msg.value == collateral, "Send the collateral amount");
        loans.push(Loan(amount, collateral, payable(msg.sender), payable(address(0)), dueDate, 100, false));
    }

requestLoan : This function allows a borrower to request a loan. The borrower specifies the amount, the collateral they are willing to provide, and the due date for theloan. The function also checks that the value sent with the function call equals the collateral amount specified.

proposeLoan


    function proposeLoan(uint loanId, uint interestRate) public {
        Loan storage loan = loans[loanId];
        require(loan.lender == payable(address(0)), "Loan already approved");
        require(interestRate < loan.lowestInterestRate, "Propose a lower interest rate");
        loan.lowestInterestRate = interestRate;
        loanProposals.push(LoanProposal(loanId, payable(msg.sender), interestRate));
    }

proposeLoan : This function allows a lender to propose to fund a loan. The lender specifies the loan ID and the interest rate they are willing to offer. The function checks that the loan has not already been approved by another lender and that the proposed interest rate is lower than the current lowest interest rate for the loan.

approveLoan

 function approveLoan(uint loanProposalId) public payable {
        LoanProposal storage loanProposal = loanProposals[loanProposalId];
        Loan storage loan = loans[loanProposal.loanId];
        require(loan.lender == payable(address(0)), "Loan already approved");
        require(msg.sender == loan.borrower, "Only borrower can approve loan");
        require(msg.value == loan.amount, "Send the loan amount");
        loan.lender = loanProposal.lender;
    }

approveLoan : This function allows the borrower to approve a loan proposal. The borrower specifies the loan proposal ID. The function checks that the loan has not already been approved by another lender, that the sender of the function call is the borrower, and that the value sent with the function call equals the loan amount. If these checks pass, the loan is approved and the lender of the loan is set to the lender of the loan proposal.

repay

   function repay(uint loanId) public payable {
        Loan storage loan = loans[loanId];
        require(msg.sender == loan.borrower, "Only borrower can repay");

        uint repaymentAmount = loan.amount.add(loan.amount.mul(loan.lowestInterestRate).div(100));
        if (block.timestamp > loan.dueDate) {
            repaymentAmount = repaymentAmount.add(repaymentAmount.mul(10).div(100)); // add 10% late penalty
        }
        require(msg.value == repaymentAmount, "Send the full repayment amount");
        require(loan.isPaid == false, "Loan is already repaid");
        loan.isPaid = true;
        loan.lender.transfer(msg.value);
        loan.borrower.transfer(loan.collateral);
    }

repay : This function allows the borrower to repay the loan. The borrower specifies the loan ID. The function calculates the repayment amount, which includes the loan amount and the interest, and potentially a late payment penalty if the loan is being repaid after the due date. The function checks that the sender of the function call is the borrower, that the value sent with the function call equals the repayment amount, and that the loan has not already been repaid. If these checks pass, the loan is marked as paid and the repayment amount is transferred to the lender…

seizeCollateral

 function seizeCollateral(uint loanId) public {
        Loan storage loan = loans[loanId];
        require(msg.sender == loan.lender, "Only lender can seize collateral");
        require(block.timestamp > loan.dueDate, "Can only seize collateral after due date");
        require(loan.isPaid == false, "Loan is already repaid");
        loan.lender.transfer(loan.collateral);
        loan.isPaid = true; // set to true to prevent multiple seizures
    }

seizeCollateral : This function allows the lender to seize the collateral if the loan is not repaid by the due date. The lender specifies the loan ID. The function checks that the sender of the function call is the lender, that the current time is after the due date, and that the loan has not already been repaid. If these checks pass, the collateral is transferred to the lender and the loan is marked as repaid.

getLoansCount and getLoanProposalsCount

 function getLoansCount() public view returns (uint) {
        return loans.length;
    }

    function getLoanProposalsCount() public view returns (uint) {
        return loanProposals.length;
    }

getLoansCount and getLoanProposalsCount : These functions return the number of loans and loan proposals respectively.

Deployment

Install the Celo Plugin

First, you’ll need to install the Celo Plugin for Remix. To do this, open Remix and click on the Plugin Manager icon on the left-hand side. Search for Celo and click the Install button next to the Celo Plugin. Once the installation is complete, you’ll see a new Celo tab appear in the sidebar.

Connect to the Celo Alfajores Testnet

To deploy our smart contract successfully, we need the celo extention wallet which can be downloaded from here

Next, we need to fund our newly created wallet which can done using the celo alfojares faucet Here

Next, you’ll need to connect Remix to the Celo Testnet. Click on the Celo tab in the sidebar and then click on the Connect to Network button.

Compile Contract

Open the contract file in Remix and click on the Solidity Compiler tab in the sidebar. Click the `Compile button to compile the contract.

Deploy the Contract

Click on the Deploy & Run Transactions tab in the sidebar. In the Contract dropdown menu, select the contract.

Once the contract is deployed, you can interact with it using the functions in the Deployed Contracts section of the Deploy & Run Transactions tab.

Conclusion

This tutorial covered a Solidity smart contract named MicroLending which implements a decentralized micro-lending platform. The platform uses the SafeMath library for safe arithmetic operations, reducing the likelihood of bugs.

The MicroLending contract allows borrowers to request loans, lenders to propose loan terms, and borrowers to approve these proposals. It also manages loan repayments, late payment penalties, and the seizing of collateral in case of non-payment.

Extra Resources

Learn more by following these links.

About the author

I’ am a passionate web3 developer who loves to write and learn new skills

7 Likes

Fantastic news! Your proposal has landed in this week’s top voted list. As you begin your project journey, remember to align with our community and technical guidelines, ensuring a high quality platform for our developers. Congratulations! :mortar_board: :seedling:

4 Likes

i’ll be reviewing this @kingokonn

4 Likes

Amazing article right here , very detailed and functions are well explained. kudos brother

7 Likes