Peer-to-Peer Rental Deposits

What it does: Handles deposits for rentals (bikes, tools, rooms). Refunds process automatically unless validated issues exist.

Why it matters: Eliminates disputes and slow returns.

How it works:

  • Deposit locked at rental start.

  • Condition reported to oracles and arbitrators.

  • Automatic refund if no claim filed by deadline.

  • Partial deductions require evidence.

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

/**
 * @title Peer-to-Peer Rental Deposits
 *
 * Model:
 * - Owner creates rental agreement
 * - Renter posts deposit into escrow
 * - Funds locked until rental ends
 * - Outcomes:
 *      1) Mutual settlement (owner + renter agree)
 *      2) Owner claims part/all within a claim window
 *      3) If owner does nothing -> renter refunds automatically
 *
 * This contract intentionally avoids "admin" control to remain neutral.
 */

contract RentalDeposits {
    enum RentalStatus {
        PendingDeposit,
        Active,
        Completed,
        Refunded,
        Claimed
    }

    struct Rental {
        address owner;
        address renter;
        uint256 depositAmount;
        uint256 startTime;
        uint256 endTime;
        uint256 claimDeadline;       // Time after end that owner can claim
        RentalStatus status;
    }

    uint256 public rentalCounter;

    mapping(uint256, Rental) public rentals;

    event RentalCreated(
        uint256 indexed rentalId,
        address indexed owner,
        address indexed renter,
        uint256 depositAmount,
        uint256 startTime,
        uint256 endTime,
        uint256 claimDeadline
    );

    event DepositPosted(uint256 indexed rentalId, address indexed renter);
    event MutualSettlement(
        uint256 indexed rentalId,
        uint256 toOwner,
        uint256 toRenter
    );
    event RefundToRenter(uint256 indexed rentalId, uint256 amount);
    event OwnerClaim(
        uint256 indexed rentalId,
        uint256 claimedAmount,
        uint256 returnedToRenter
    );

    modifier onlyOwner(uint256 rentalId) {
        require(msg.sender == rentals[rentalId].owner, "Not rental owner");
        _;
    }

    modifier onlyRenter(uint256 rentalId) {
        require(msg.sender == rentals[rentalId].renter, "Not renter");
        _;
    }

    modifier inStatus(uint256 rentalId, RentalStatus expected) {
        require(rentals[rentalId].status == expected, "Invalid status");
        _;
    }

    // -------------------------------------------------
    // CREATE RENTAL AGREEMENT
    // -------------------------------------------------

    function createRental(
        address renter,
        uint256 depositAmount,
        uint256 startTime,
        uint256 endTime,
        uint256 claimWindowSeconds
    ) external returns (uint256) {
        require(renter != address(0), "Invalid renter");
        require(depositAmount > 0, "Deposit must be > 0");
        require(endTime > startTime, "Invalid times");
        require(claimWindowSeconds >= 1 days, "Claim window too short");

        rentalCounter++;

        rentals[rentalCounter] = Rental({
            owner: msg.sender,
            renter: renter,
            depositAmount: depositAmount,
            startTime: startTime,
            endTime: endTime,
            claimDeadline: endTime + claimWindowSeconds,
            status: RentalStatus.PendingDeposit
        });

        emit RentalCreated(
            rentalCounter,
            msg.sender,
            renter,
            depositAmount,
            startTime,
            endTime,
            endTime + claimWindowSeconds
        );

        return rentalCounter;
    }

    // -------------------------------------------------
    // RENTER POSTS DEPOSIT
    // -------------------------------------------------

    function postDeposit(uint256 rentalId)
        external
        payable
        onlyRenter(rentalId)
        inStatus(rentalId, RentalStatus.PendingDeposit)
    {
        Rental storage r = rentals[rentalId];
        require(msg.value == r.depositAmount, "Incorrect deposit");
        require(block.timestamp = r.endTime, "Rental not ended yet");
        require(
            toOwner + toRenter == r.depositAmount,
            "Must split full deposit"
        );

        // Settle
        r.status = RentalStatus.Completed;

        if (toOwner > 0) {
            payable(r.owner).transfer(toOwner);
        }

        if (toRenter > 0) {
            payable(r.renter).transfer(toRenter);
        }

        emit MutualSettlement(rentalId, toOwner, toRenter);
    }

    // -------------------------------------------------
    // OWNER CLAIMS DAMAGES (WITH LIMIT + DEADLINE)
    // -------------------------------------------------

    function ownerClaim(uint256 rentalId, uint256 claimAmount)
        external
        onlyOwner(rentalId)
        inStatus(rentalId, RentalStatus.Active)
    {
        Rental storage r = rentals[rentalId];

        require(block.timestamp >= r.endTime, "Not ended");
        require(block.timestamp <= r.claimDeadline, "Claim window expired");
        require(claimAmount  0) {
            payable(r.owner).transfer(claimAmount);
        }

        if (toRenter > 0) {
            payable(r.renter).transfer(toRenter);
        }

        emit OwnerClaim(rentalId, claimAmount, toRenter);
    }

    // -------------------------------------------------
    // AUTOMATIC REFUND (OWNER DID NOTHING)
    // -------------------------------------------------

    function autoRefund(uint256 rentalId)
        external
        onlyRenter(rentalId)
        inStatus(rentalId, RentalStatus.Active)
    {
        Rental storage r = rentals[rentalId];

        require(block.timestamp > r.claimDeadline, "Too early");

        r.status = RentalStatus.Refunded;

        payable(r.renter).transfer(r.depositAmount);

        emit RefundToRenter(rentalId, r.depositAmount);
    }

    // -------------------------------------------------
    // VIEW HELPERS
    // -------------------------------------------------

    function getRental(uint256 rentalId)
        external
        view
        returns (
            address owner,
            address renter,
            uint256 depositAmount,
            uint256 startTime,
            uint256 endTime,
            uint256 claimDeadline,
            RentalStatus status
        )
    {
        Rental memory r = rentals[rentalId];
        return (
            r.owner,
            r.renter,
            r.depositAmount,
            r.startTime,
            r.endTime,
            r.claimDeadline,
            r.status
        );
    }
}