Trustless Peer-to-Peer Lending

What it does:
A fully trustless smart contract that allows users to lend and borrow funds directly from each other without intermediaries, collateral custodians, or centralized platforms.

Why it matters:
Traditional P2P lending platforms require trust in operators, custody of funds, and opaque rules. This contract replaces trust with code, ensuring fairness, transparency, and automatic enforcement of loan terms.

How it works:

  • A borrower creates a loan request with terms (amount, interest, duration).

  • A lender funds the loan directly on-chain.

  • Funds are released automatically to the borrower.

  • The borrower repays principal + interest before the deadline.

  • If repayment fails, predefined penalties or collateral logic executes.

  • All states, payments, and outcomes are recorded immutably on-chain

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

/**
 * @title TrustlessP2PLending
 * @author Nam
 * @notice Peer-to-peer lending without intermediaries or custodians
 */
contract TrustlessP2PLending {

    enum LoanStatus {
        Requested,
        Funded,
        Repaid,
        Defaulted
    }

    struct Loan {
        address borrower;
        address lender;
        uint256 principal;      // Loan amount
        uint256 interest;       // Fixed interest
        uint256 collateral;     // ETH collateral
        uint256 startTime;
        uint256 duration;       // Loan duration in seconds
        LoanStatus status;
    }

    uint256 public loanCounter;
    mapping(uint256 => Loan) public loans;

    // -------------------- EVENTS --------------------

    event LoanRequested(
        uint256 indexed loanId,
        address indexed borrower,
        uint256 principal,
        uint256 interest,
        uint256 collateral,
        uint256 duration
    );

    event LoanFunded(
        uint256 indexed loanId,
        address indexed lender
    );

    event LoanRepaid(
        uint256 indexed loanId,
        address indexed borrower,
        uint256 amount
    );

    event LoanDefaulted(
        uint256 indexed loanId
    );

    // -------------------- MODIFIERS --------------------

    modifier onlyBorrower(uint256 _loanId) {
        require(msg.sender == loans[_loanId].borrower, "Not borrower");
        _;
    }

    modifier onlyLender(uint256 _loanId) {
        require(msg.sender == loans[_loanId].lender, "Not lender");
        _;
    }

    modifier inStatus(uint256 _loanId, LoanStatus _status) {
        require(loans[_loanId].status == _status, "Invalid loan status");
        _;
    }

    // -------------------- CORE LOGIC --------------------

    /**
     * @notice Borrower creates a loan request
     * @param _principal Loan amount requested
     * @param _interest Fixed interest paid to lender
     * @param _duration Loan duration (seconds)
     */
    function requestLoan(
        uint256 _principal,
        uint256 _interest,
        uint256 _duration
    ) external payable {
        require(_principal > 0, "Invalid principal");
        require(_duration > 0, "Invalid duration");
        require(msg.value > 0, "Collateral required");

        loanCounter++;

        loans[loanCounter] = Loan({
            borrower: msg.sender,
            lender: address(0),
            principal: _principal,
            interest: _interest,
            collateral: msg.value,
            startTime: 0,
            duration: _duration,
            status: LoanStatus.Requested
        });

        emit LoanRequested(
            loanCounter,
            msg.sender,
            _principal,
            _interest,
            msg.value,
            _duration
        );
    }

    /**
     * @notice Lender funds a requested loan
     * @param _loanId Loan identifier
     */
    function fundLoan(uint256 _loanId)
        external
        payable
        inStatus(_loanId, LoanStatus.Requested)
    {
        Loan storage loan = loans[_loanId];
        require(msg.value == loan.principal, "Incorrect funding amount");

        loan.lender = msg.sender;
        loan.startTime = block.timestamp;
        loan.status = LoanStatus.Funded;

        // Transfer principal to borrower
        payable(loan.borrower).transfer(msg.value);

        emit LoanFunded(_loanId, msg.sender);
    }

    /**
     * @notice Borrower repays loan with interest
     * @param _loanId Loan identifier
     */
    function repayLoan(uint256 _loanId)
        external
        payable
        onlyBorrower(_loanId)
        inStatus(_loanId, LoanStatus.Funded)
    {
        Loan storage loan = loans[_loanId];
        uint256 totalOwed = loan.principal + loan.interest;

        require(msg.value == totalOwed, "Incorrect repayment amount");

        loan.status = LoanStatus.Repaid;

        // Pay lender principal + interest
        payable(loan.lender).transfer(msg.value);

        // Return collateral to borrower
        payable(loan.borrower).transfer(loan.collateral);

        emit LoanRepaid(_loanId, msg.sender, msg.value);
    }

    /**
     * @notice Lender claims collateral if borrower defaults
     * @param _loanId Loan identifier
     */
    function claimDefault(uint256 _loanId)
        external
        onlyLender(_loanId)
        inStatus(_loanId, LoanStatus.Funded)
    {
        Loan storage loan = loans[_loanId];
        require(
            block.timestamp > loan.startTime + loan.duration,
            "Loan not expired"
        );

        loan.status = LoanStatus.Defaulted;

        // Transfer collateral to lender
        payable(loan.lender).transfer(loan.collateral);

        emit LoanDefaulted(_loanId);
    }

    // -------------------- VIEW FUNCTIONS --------------------

    /**
     * @notice Get full loan details
     */
    function getLoan(uint256 _loanId)
        external
        view
        returns (
            address borrower,
            address lender,
            uint256 principal,
            uint256 interest,
            uint256 collateral,
            uint256 startTime,
            uint256 duration,
            LoanStatus status
        )
    {
        Loan memory loan = loans[_loanId];
        return (
            loan.borrower,
            loan.lender,
            loan.principal,
            loan.interest,
            loan.collateral,
            loan.startTime,
            loan.duration,
            loan.status
        );
    }
}