A Peer-to-Peer Lending Platform that Connects Borrowers and Lenders Without Intermediaries on Celo

A Peer-to-Peer Lending Platform that Connects Borrowers and Lenders Without Intermediaries on Celo https://celo.academy/uploads/default/optimized/2X/e/e2855746727e65dfed3f9e6b0ed28a574f77c9cd_2_1024x576.png
none 0.0 0

Introduction

Welcome to our peer-to-peer lending tutorial! In this tutorial, we will learn how to build a decentralized lending platform on the Ethereum blockchain using Solidity.

Our lending platform allows borrowers and lenders to connect directly, eliminating the need for intermediaries. By leveraging smart contracts, we ensure secure and transparent lending transactions.

In this tutorial, we will cover the following key features:

  • Loan Creation: Borrowers can request loans by specifying the loan amount, interest rate, and duration.
  • Loan Funding: Lenders can fund loan requests by directly sending the requested amount to the borrower’s address.
  • Loan Repayment: Borrowers can repay their loans by sending the loan amount plus interest to the contract. Once the repayment is received, the loan is marked as completed.
  • Interest Calculation: The contract automatically calculates the interest amount based on the loan parameters and remaining duration.

We will also integrate an ERC20 token, such as cUSD, for seamless transfer of loan amounts between borrowers and lenders.

By the end of this tutorial, you will have a solid understanding of building a peer-to-peer lending platform on the Ethereum blockchain. Let’s get started!

REQUIREMENTS

To follow this tutorial, you will require:

  • A code editor or text editor such as Remix.
  • An internet browser and a stable internet connection.

PREREQUISITES

To successfully complete this tutorial, it is recommended that you have:

  • Familiarity with Javascript programming language.
  • A basic understanding of Blockchain technology and its functioning.
  • Basic knowledge of the Solidity programming language used for smart contract development on the blockchain.

We will begin by using the Remix IDE to write our smart contract. Let’s get started!

The complete code:

// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

interface IERC20Token {
    function transfer(address, uint256) external returns (bool);

    function approve(address, uint256) external returns (bool);

    function transferFrom(address, address, uint256) external returns (bool);

    function totalSupply() external view returns (uint256);

    function balanceOf(address) external view returns (uint256);

    function allowance(address, address) external view returns (uint256);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

contract PeerToPeerLending {
    using SafeMath for uint256;

    address private cUsdTokenAddress = 0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1;

    struct Loan {
        address payable borrower;
        uint256 amount;
        uint256 interestRate;
        uint256 duration;
        uint256 endTime;
        bool isActive;
        bool isCompleted;
    }

    uint256 private loanIdCounter = 0;
    mapping(uint256 => Loan) private loans;

    modifier onlyActiveLoan(uint256 loanId) {
        require(loans[loanId].isActive, "Loan is not active");
        _;
    }

    function createLoan(uint256 amount, uint256 interestRate, uint256 duration) external {
        require(amount > 0, "Loan amount must be greater than zero");
        require(interestRate > 0, "Interest rate must be greater than zero");
        require(duration > 0, "Loan duration must be greater than zero");

        Loan storage newLoan = loans[loanIdCounter];
        newLoan.borrower = payable(msg.sender);
        newLoan.amount = amount;
        newLoan.interestRate = interestRate;
        newLoan.duration = duration;
        newLoan.endTime = block.timestamp.add(duration);
        newLoan.isActive = true;
        newLoan.isCompleted = false;

        loanIdCounter++;
    }

    function getLoan(uint256 loanId) public view returns (address payable, uint256, uint256, uint256, uint256, bool, bool) {
        Loan storage loan = loans[loanId];
        return (
            loan.borrower,
            loan.amount,
            loan.interestRate,
            loan.duration,
            loan.endTime,
            loan.isActive,
            loan.isCompleted
        );
    }

    function fundLoan(uint256 loanId) external payable onlyActiveLoan(loanId) {
        Loan storage loan = loans[loanId];
        require(msg.value == loan.amount, "Incorrect loan amount");

        IERC20Token(cUsdTokenAddress).transferFrom(msg.sender, loan.borrower, loan.amount);

        loan.isActive = false;
    }
 function interestAmount(Loan storage loan) internal view returns (uint256) {
    uint256 calculatedInterestAmount = loan.amount.mul(loan.interestRate).div(100);
    uint256 remainingDays = loan.endTime.sub(block.timestamp).div(1 days);
    return calculatedInterestAmount.mul(remainingDays).div(365);
}


     
}

Code Analysis:

Step 1: Setting up the Smart Contract and Importing Dependencies

// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

In this step, we will set up the initial structure of our smart contract, Thriftbooks, and import the necessary dependencies. We will be using the Solidity programming language and the OpenZeppelin library.

To begin, we specify the SPDX-License-Identifier to indicate the license under which our contract is released. In this case, we use the MIT license.

We then define our contract, Thriftbooks, which is marked with the pragma directive specifying the Solidity version to be used. We indicate that the version should be greater than or equal to 0.7.0 and less than 0.9.0.

Next, we import the SafeMath library from the OpenZeppelin library. SafeMath provides arithmetic operations that prevent common vulnerabilities such as integer overflow or underflow.

By including these dependencies, we ensure that our contract has access to safe mathematical operations and is compliant with the specified version of Solidity.

Setting up the contract and importing necessary dependencies are the first steps in building our Thriftbooks marketplace. These initial preparations lay the foundation for implementing the functionalities that will be added in subsequent steps.

Step 2: Defining the ERC20 Token Interface

interface IERC20Token {
    function transfer(address, uint256) external returns (bool);

    function approve(address, uint256) external returns (bool);

    function transferFrom(address, address, uint256) external returns (bool);

    function totalSupply() external view returns (uint256);

    function balanceOf(address) external view returns (uint256);

    function allowance(address, address) external view returns (uint256);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

In this step, we define the ERC20Token interface, which allows our smart contract to interact with ERC20-compliant tokens. ERC20 is a widely adopted standard for fungible tokens on the Celo blockchain.

The interface lists a set of functions and events that a token contract should implement to be considered ERC20 compliant.

  • The transfer function allows the token contract to transfer a certain amount of tokens from one address to another. It takes two parameters: the recipient’s address and the amount of tokens to be transferred.

  • The approve function allows an address to approve another address to spend a specified amount of tokens on its behalf. It takes two parameters: the spender’s address and the amount of tokens approved.

  • The transferFrom function allows an address to transfer tokens on behalf of another address, given that the spender has been approved to spend the tokens. It takes three parameters: the owner's address, the recipient's address, and the amount of tokens to be transferred.

  • The totalSupply function returns the total supply of tokens in circulation.

  • The balanceOf function returns the balance of tokens owned by a specific address.

  • The allowance function returns the amount of tokens that the owner has approved the spender to spend.

  • The Transfer event is emitted when tokens are transferred from one address to another. It includes the sender’s address, the recipient’s address, and the amount of tokens transferred.

  • The Approval event is emitted when an address approves another address to spend a certain amount of tokens on its behalf. It includes the owner’s address, the spender’s address, and the amount of tokens approved.

By defining the ERC20Token interface, we establish a common set of functions and events that our smart contract can utilize to interact with ERC20 tokens. This allows for seamless integration with existing ERC20 token contracts and ensures compatibility with the broader Celo ecosystem.

Step 3: Creating the PeerToPeerLending Contract

contract PeerToPeerLending {
    using SafeMath for uint256;

    address private cUsdTokenAddress = 0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1;

    struct Loan {
        address payable borrower;
        uint256 amount;
        uint256 interestRate;
        uint256 duration;
        uint256 endTime;
        bool isActive;
        bool isCompleted;
    }

    uint256 private loanIdCounter = 0;
    mapping(uint256 => Loan) private loans;

    modifier onlyActiveLoan(uint256 loanId) {
        require(loans[loanId].isActive, "Loan is not active");
        _;
    }

In this step, we create the PeerToPeerLending contract, which serves as the core smart contract for our peer-to-peer lending platform.

The contract begins by importing the SafeMath library from the OpenZeppelin library. SafeMath provides arithmetic operations with safety checks to prevent overflow and underflow vulnerabilities.

Inside the contract, we define a private variable called cUsdTokenAddress, which represents the address of the cUSD token. This token is used as the payment currency for the loans in our lending platform.

Next, we define a struct called Loan. This struct represents a single loan request made by a borrower. It contains the following fields: borrower (an address payable), amount (the loan amount in cUSD), interestRate (the interest rate for the loan), duration (the duration of the loan in seconds), endTime (the timestamp when the loan ends), isActive (a boolean indicating if the loan is active), and isCompleted (a boolean indicating if the loan is completed).

We also declare a private variable called loanIdCounter, which will be used to assign unique identifiers to each loan request. This variable is initialized to 0.

To store and manage loan requests, we create a mapping called loans. The mapping maps loan identifiers (uint256) to Loan structs. By using the loanIdCounter, we can easily access loan details based on their unique identifiers.

To ensure certain conditions are met, we define a modifier called onlyActiveLoan. This modifier can be applied to functions and requires that the loan specified by the loanId is active. If the condition is not met, the function call will be reverted with an error message.

With the PeerToPeerLending contract and its necessary components in place, we are now ready to proceed with implementing the functionalities of our lending platform.

Step 4: Creating a Loan Request

  function createLoan(uint256 amount, uint256 interestRate, uint256 duration) external {
        require(amount > 0, "Loan amount must be greater than zero");
        require(interestRate > 0, "Interest rate must be greater than zero");
        require(duration > 0, "Loan duration must be greater than zero");

        Loan storage newLoan = loans[loanIdCounter];
        newLoan.borrower = payable(msg.sender);
        newLoan.amount = amount;
        newLoan.interestRate = interestRate;
        newLoan.duration = duration;
        newLoan.endTime = block.timestamp.add(duration);
        newLoan.isActive = true;
        newLoan.isCompleted = false;

        loanIdCounter++;
    }

In this step, we implement the createLoan function in the PeerToPeerLending contract. This function allows borrowers to create loan requests on the platform.

The createLoan function is declared as external, meaning it can be called by external accounts. It takes three parameters: amount (the loan amount in cUSD), interestRate (the interest rate for the loan), and duration (the duration of the loan in seconds).

Before creating the loan request, we add several require statements to validate the input parameters. We check that the loan amount, interest rate, and duration are all greater than zero. If any of these conditions fail, the function call will be reverted with an appropriate error message.

If the validation passes, we create a new Loan struct called newLoan and assign it to the loans mapping using the loanIdCounter as the key. We use the loanIdCounter to generate a unique identifier for the loan request.

Inside the newLoan struct, we set the borrower to the address of the caller (msg.sender), the amount to the provided amount, the interestRate to the provided interestRate, the duration to the provided duration, and the endTime to the current block timestamp plus the duration. We also set isActive to true to indicate that the loan is active and isCompleted to false since the loan is not yet completed.

Finally, we increment the loanIdCounter to prepare for the next loan request.

With the createLoan function implemented, borrowers can now create loan requests by providing the loan amount, interest rate, and duration. These loan requests will be stored in the loans mapping with unique identifiers, and the necessary validations are in place to ensure the input parameters are valid.

Step 5: Retrieving Loan Details

 function getLoan(uint256 loanId) public view returns (address payable, uint256, uint256, uint256, uint256, bool, bool) {
        Loan storage loan = loans[loanId];
        return (
            loan.borrower,
            loan.amount,
            loan.interestRate,
            loan.duration,
            loan.endTime,
            loan.isActive,
            loan.isCompleted
        );
    }

In this step, we implement the getLoan function in the PeerToPeerLending contract. This function allows users to retrieve the details of a specific loan by providing the loan ID.

The getLoan function is declared as public and view, meaning it can be called by any external account and does not modify the contract’s state.

The function takes one parameter, loanId, which represents the unique identifier of the loan we want to retrieve.

Inside the function, we retrieve the Loan struct associated with the given loanId from the loans mapping and assign it to the loan variable using the storage keyword.

Then, we return a tuple containing the following loan details in the specified order:

  • borrower: the address of the borrower (payable)
  • amount: the loan amount
  • interestRate: the interest rate for the loan
  • duration: the duration of the loan
  • endTime: the timestamp indicating when the loan ends
  • isActive: a boolean indicating whether the loan is active
  • isCompleted: a boolean indicating whether the loan is completed

By using the getLoan function, users can easily fetch the details of a specific loan by providing its unique loan ID. This provides transparency and allows interested parties to access relevant information about a loan, such as the borrower’s address, loan amount, interest rate, duration, and status.

Step 6: Funding a Loan and Calculating Interest

In this step, we will cover two functions: fundLoan and interestAmount in the PeerToPeerLending contract.

The fundLoan function allows lenders to fund a specific loan by providing the loan ID and sending the loan amount as Celo along with the transaction. It is declared as external and payable, indicating that it can be called externally and requires the caller to send Celo to fund the loan.

Inside the function, we first retrieve the Loan struct associated with the given loanId from the loans mapping and assign it to the loan variable using the storage keyword.

We then use a require statement to verify that the amount sent with the transaction matches the loan amount. This ensures that the correct loan amount is funded.

Next, we call the transferFrom function of the IERC20Token interface, using the cUsdTokenAddress, to transfer the loan amount from the lender (msg.sender) to the borrower (loan.borrower). This assumes that the contract has been granted the necessary approval to transfer funds on behalf of the lender.

Finally, we set the isActive flag of the loan to false, indicating that the loan is no longer active.

The interestAmount function is an internal view function that calculates the interest amount for a given loan. It takes a Loan struct as a parameter and returns the calculated interest amount as a uint256 value.

Inside the function, we calculate the interest amount by multiplying the loan amount with the interest rate and dividing the result by 100. This gives us the interest amount based on the loan amount.

We then calculate the remaining number of days until the loan’s endTime by subtracting the current block timestamp from the loan’s endTime and dividing it by the number of seconds in a day (1 days).

Finally, we multiply the calculated interest amount by the remaining number of days and divide it by 365 to get the proportional interest amount based on the remaining loan duration.

These two functions allow lenders to fund loans and borrowers to calculate the interest amount owed on their loans. This completes the implementation of the core functionalities of our peer-to-peer lending platform.

Click here to get the complete code for this session

CONTRACT DEPLOYMENT

To deploy the peer-to-peer lending platform smart contract on the Celo blockchain, follow the steps below:

  • Install Celo Extension Wallet: Download and install the Celo Extension Wallet from the Google Chrome store. Create a wallet and securely store your key phrase.

  • Fund your wallet: Copy your wallet address and paste it into the Celo Faucet. Confirm the transaction to receive Celo tokens in your wallet.

  • Open Remix and create a new Solidity file: Paste the insurance contract code into the file. Ensure that the Solidity compiler is set to version 0.8.7 or later.

  • Compile the contract: Click the loan.sol button in the Solidity Compiler tab in Remix.

  • Deploy the contract: In the Deploy & Run Transactions tab, select the Celo network from the dropdown menu. Connect your wallet to Remix by clicking Connect to wallet. Select insurance from the Contract dropdown menu. Click the Deploy button, confirm the transaction in your wallet, and wait for the transaction to be confirmed on the Celo blockchain.

  • Interact with the contract: Once the transaction is confirmed, the laon contract will be deployed on the Celo blockchain. You can interact with it using Remix.

CONCLUSION

In this tutorial, we explored the implementation of a peer-to-peer lending platform using Solidity on the Celo blockchain. We covered key steps such as defining the loan structure, creating loans, retrieving loan details, funding loans, calculating interest amounts, and more.

By leveraging smart contracts and blockchain technology, we eliminated the need for intermediaries, enabling direct lending between borrowers and lenders. This offers a transparent, secure, and efficient alternative to traditional lending systems.

The PeerToPeerLending contract showcased the power of Solidity and demonstrated how various functions and modifiers can be used to facilitate lending transactions, track loan details, and ensure the integrity of the platform.

Through this tutorial, you gained insights into building a decentralized lending platform and learned about the essential concepts and code snippets involved in the process. This knowledge can serve as a foundation for further exploration and enhancement of peer-to-peer lending applications on the blockchain.

NEXT STEPS

Great job! It’s always helpful to provide additional resources for further learning. Don’t hesitate to reach out if you have any more questions or if you need further assistance. Happy learning!

About the author

My name is Ogoyi Thompson, and I’m a web3 developer based in Nigeria, you can connect with me on twitter. I am enthusiastic about working with blockchain technology.

5 Likes

Approved for you to get started. You can manage the tutorial here by changing the category to Proposals > In Progress then Proposals > Review as you complete the tutorial. Thanks!

1 Like

I would be reviewing this

1 Like

@thompsonogoyi1t please set as requirements not requirement and prerequisites not prerequisite and your step 6 is interest. Add the R. And the next step is next steps

2 Likes

Also let your Next steps, Contract Deployment and about the author be in caps lock to ensure uniformity with the introduction and requirements header

3 Likes

done making the changes sir

2 Likes

You didn’t change the spelling error of interest in step 6 and also its “next steps”

2 Likes

That’s great! You can move to publish

1 Like

Please attach the source code link @thompsonogoyi1t

2 Likes

the code repo is in the tutorial

2 Likes

Hey @thompsonogoyi1t - small note: could you use lowercase for your headings? It’ll help it look more similar to the other tutorials. Ex. Introduction rather than INTRODUCTION. I change the first heading here but not the others. Thanks!

2 Likes

Well written and detailed, well done.

A nice piece I must say.

3 Likes

Thanks for contributing this @thompsonogoyi1t

3 Likes

Interesting read

1 Like

Nice work but is there anyway I can run this locally without connecting to the internet?

Nice work but is there anyway I can run this locally without connecting to the internet?