Smart Contract-Based Rental Agreement Solidity, JavaScript

👤 Sharing: AI
Okay, here's a Solidity smart contract for a rental agreement, along with a basic JavaScript (Node.js) script to interact with it.  I'll provide explanations throughout.

**Solidity Smart Contract (RentalAgreement.sol)**

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract RentalAgreement {

    address public landlord;
    address public tenant;
    uint256 public rentAmount;
    uint256 public depositAmount;
    uint256 public rentDueDate; // Unix timestamp for the next rent due date
    uint256 public agreementStartDate;
    uint256 public agreementEndDate;
    bool public agreementActive;

    event AgreementCreated(address indexed landlord, address indexed tenant, uint256 rentAmount, uint256 depositAmount, uint256 startDate, uint256 endDate);
    event RentPaid(address indexed tenant, uint256 amount, uint256 timestamp);
    event AgreementTerminated(address indexed landlord, address indexed tenant, uint256 timestamp);
    event DepositReturned(address indexed tenant, uint256 amount, uint256 timestamp);

    constructor(
        address _tenant,
        uint256 _rentAmount,
        uint256 _depositAmount,
        uint256 _startDate,
        uint256 _endDate
    ) {
        landlord = msg.sender;
        tenant = _tenant;
        rentAmount = _rentAmount;
        depositAmount = _depositAmount;
        agreementStartDate = _startDate;
        agreementEndDate = _endDate;
        rentDueDate = _startDate; // Initial rent due date
        agreementActive = true;

        emit AgreementCreated(landlord, tenant, rentAmount, depositAmount, _startDate, _endDate);
    }

    modifier onlyLandlord() {
        require(msg.sender == landlord, "Only landlord can call this function.");
        _;
    }

    modifier onlyTenant() {
        require(msg.sender == tenant, "Only tenant can call this function.");
        _;
    }

    modifier agreementIsActive() {
        require(agreementActive, "Agreement is not active.");
        _;
    }


    function payRent() public payable onlyTenant agreementIsActive {
        require(msg.value >= rentAmount, "Insufficient rent sent."); // Require at least the rent amount
        require(block.timestamp >= rentDueDate, "Rent is not due yet.");

        //Refund extra value back to tenant
        if (msg.value > rentAmount) {
            (bool success, ) = payable(tenant).call{value: msg.value - rentAmount}("");
            require(success, "Failed to refund excess rent.");
        }

        rentDueDate = calculateNextDueDate();
        emit RentPaid(tenant, rentAmount, block.timestamp);
    }


    function calculateNextDueDate() private view returns (uint256) {
        //Let's assume rent is due every 30 days (for simplicity)
        return rentDueDate + (30 days);
    }


    function terminateAgreement() public onlyLandlord agreementIsActive {
        agreementActive = false;
        emit AgreementTerminated(landlord, tenant, block.timestamp);
    }

    function returnDeposit() public onlyLandlord agreementIsActive {
        require(!agreementActive, "Agreement must be terminated first.");
        (bool success, ) = payable(tenant).call{value: depositAmount}("");
        require(success, "Deposit transfer failed.");
        emit DepositReturned(tenant, depositAmount, block.timestamp);
    }

    // Fallback function to receive ether
    receive() external payable {}

    // Optional:  A function to check the contract's balance
    function getContractBalance() public view onlyLandlord returns (uint256) {
        return address(this).balance;
    }


}
```

**Explanation of Solidity Code:**

*   **`pragma solidity ^0.8.0;`**: Specifies the Solidity compiler version.
*   **`contract RentalAgreement { ... }`**:  Defines the smart contract.
*   **State Variables:**
    *   `landlord`: Address of the landlord (set in the constructor).
    *   `tenant`: Address of the tenant (passed to the constructor).
    *   `rentAmount`: Rent amount in Wei (smallest unit of Ether).
    *   `depositAmount`: Deposit amount in Wei.
    *   `rentDueDate`:  A Unix timestamp representing when the rent is next due.
    *   `agreementStartDate`: A Unix timestamp representing the start date of the agreement.
    *   `agreementEndDate`: A Unix timestamp representing the end date of the agreement.
    *   `agreementActive`: A boolean that indicates whether the agreement is currently active.
*   **Events:**
    *   `AgreementCreated`:  Emitted when the contract is created.
    *   `RentPaid`: Emitted when rent is paid.
    *   `AgreementTerminated`: Emitted when the landlord terminates the agreement.
    *   `DepositReturned`: Emitted when the deposit is returned.
*   **`constructor(...)`**:  Called only once when the contract is deployed. It initializes the state variables.  Takes the tenant's address, rent amount, deposit amount, start date and end date as arguments.
*   **`modifier onlyLandlord`**, `onlyTenant`, `agreementIsActive`: Modifiers are used to restrict access to functions.  `onlyLandlord` ensures only the landlord can call the function. `onlyTenant` ensures that only the tenant can call the function. `agreementIsActive` ensures that the agreement is active before proceeding.
*   **`payRent()`**:  Allows the tenant to pay rent.  It requires the `msg.value` (amount of Ether sent) to be greater than or equal to the `rentAmount`.  It then updates the `rentDueDate`. Sends back excess amount of Ether if tenant sent more than rentAmount.
*   **`calculateNextDueDate()`**: Calculates the next rent due date (assumes 30 days for simplicity).
*   **`terminateAgreement()`**: Allows the landlord to terminate the agreement (sets `agreementActive` to `false`).
*   **`returnDeposit()`**: Allows the landlord to return the deposit to the tenant, but only after the agreement has been terminated.
*   **`receive() external payable`**:  A fallback function that allows the contract to receive Ether.  Crucial for receiving rent payments.
*   **`getContractBalance()`**: Returns the contract's current Ether balance.  Only the landlord can call this.

**JavaScript (Node.js) Interaction Script (deployAndInteract.js)**

This script uses `ethers.js` to deploy and interact with the smart contract.  You'll need Node.js installed.

```javascript
const { ethers } = require("ethers");
const fs = require("fs");
require("dotenv").config(); // Load environment variables from .env file

//Fill in env file. You'll need to set your RPC_URL and PRIVATE_KEY
//You can use hardhat or ganache for the RPC_URL
//RPC_URL=YOUR_RPC_URL
//PRIVATE_KEY=YOUR_PRIVATE_KEY
//TENANT_ADDRESS=TENANT_ADDRESS

async function main() {
    // 1. Set up provider and signer
    const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL);
    const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

    // 2. Compile the contract (if necessary - you might have already compiled)
    // (Assumes you have a separate compilation step using `solc`)
    // You can compile using `solc RentalAgreement.sol --bin --abi -o .`

    // 3. Load the contract ABI and bytecode
    const abi = JSON.parse(fs.readFileSync("RentalAgreement_sol_RentalAgreement.abi", "utf8"));
    const bytecode = fs.readFileSync("RentalAgreement_sol_RentalAgreement.bin", "utf8");

    // 4. Create a contract factory
    const factory = new ethers.ContractFactory(abi, bytecode, signer);

    //Define tenant address and agreement details
    const tenantAddress = process.env.TENANT_ADDRESS;
    const rentAmount = ethers.utils.parseEther("1"); // 1 Ether
    const depositAmount = ethers.utils.parseEther("2"); // 2 Ether
    const startDate = Math.floor(Date.now() / 1000); // Current time as Unix timestamp
    const endDate = startDate + (365 * 24 * 60 * 60); // One year from now

    // 5. Deploy the contract
    console.log("Deploying contract...");
    const contract = await factory.deploy(tenantAddress, rentAmount, depositAmount, startDate, endDate);
    await contract.deployed();
    console.log("Contract deployed to:", contract.address);

    // 6. Interact with the contract
    // Get the contract address
    const contractAddress = contract.address;

    // Create a contract instance
    const rentalAgreement = new ethers.Contract(contractAddress, abi, signer);

    //Pay rent
    console.log("Paying rent...");
    const payRentTx = await rentalAgreement.payRent({
        value: ethers.utils.parseEther("1.1"), // Sending 1.1 Ether to cover gas
        gasLimit: 300000
    });
    await payRentTx.wait();
    console.log("Rent paid. Transaction hash:", payRentTx.hash);

    // Terminate the agreement
    console.log("Terminating agreement...");
    const terminateTx = await rentalAgreement.terminateAgreement({gasLimit: 300000});
    await terminateTx.wait();
    console.log("Agreement terminated. Transaction hash:", terminateTx.hash);

    // Return the deposit
    console.log("Returning deposit...");
    const returnDepositTx = await rentalAgreement.returnDeposit({gasLimit: 300000});
    await returnDepositTx.wait();
    console.log("Deposit returned. Transaction hash:", returnDepositTx.hash);

    // Get the contract balance
    const contractBalance = await rentalAgreement.getContractBalance();
    console.log("Contract balance:", ethers.utils.formatEther(contractBalance), "ETH");

    console.log("Interaction complete!");

}

main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });
```

**Explanation of JavaScript Code:**

1.  **Dependencies:**
    *   `ethers`:  A JavaScript library for interacting with Ethereum.  Install with `npm install ethers`.
    *   `fs`:  Node.js file system module for reading files.
    *   `dotenv`: Loads environment variables from `.env` file. Install with `npm install dotenv`.

2.  **Setup:**
    *   `require(...)`:  Imports the necessary modules.
    *   `dotenv.config()`: Loads environment variables from a `.env` file.  This is where you'll store your private key and RPC URL (see below).
    *   `RPC_URL`: The URL of an Ethereum node (e.g., Ganache, Hardhat node, Infura, Alchemy).
    *   `PRIVATE_KEY`:  The private key of the account that will deploy and interact with the contract.  **Never commit your private key to a public repository!** Store it securely.
    *   `TENANT_ADDRESS`: The address of the tenant.

3.  **Provider and Signer:**
    *   `provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL)`:  Creates a connection to the Ethereum network using the provided RPC URL.
    *   `signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider)`: Creates a `Wallet` object, which represents an Ethereum account and its private key.  The `signer` is used to sign transactions.

4.  **Contract Artifacts (ABI and Bytecode):**
    *   The ABI (Application Binary Interface) is a JSON file that describes the contract's functions, events, and data structures.  It allows JavaScript code to understand how to interact with the contract.
    *   The bytecode is the compiled code of the smart contract, which is what gets deployed to the blockchain.
    *   **Compilation:** You'll need to compile your Solidity code using a Solidity compiler (e.g., `solc`).  The command to compile is something like:

    ```bash
    solc RentalAgreement.sol --bin --abi -o .
    ```

    This will generate two files: `RentalAgreement_sol_RentalAgreement.abi` and `RentalAgreement_sol_RentalAgreement.bin`.

5.  **Contract Factory:**
    *   `factory = new ethers.ContractFactory(abi, bytecode, signer)`: Creates a `ContractFactory` object, which is used to deploy new instances of the contract.

6.  **Deployment:**
    *   `contract = await factory.deploy(...)`: Deploys a new instance of the contract. The constructor arguments are passed here: tenant address, rent amount, deposit amount, start date and end date.
    *   `await contract.deployed()`:  Waits for the contract to be successfully deployed to the blockchain.
    *   `console.log("Contract deployed to:", contract.address)`: Prints the address of the deployed contract.

7.  **Interaction:**
    *   `rentalAgreement = new ethers.Contract(contractAddress, abi, signer)`: Creates a `Contract` object, which represents an existing contract on the blockchain.  It takes the contract address, ABI, and signer as arguments.  This allows you to call the contract's functions.
    *   `payRentTx = await rentalAgreement.payRent({ value: ethers.utils.parseEther("1.1"), gasLimit: 300000 })`: Calls the `payRent()` function on the contract.  We send 1.1 Ether to cover the rent (1 Ether) and potential gas costs.  The `gasLimit` is the maximum amount of gas the transaction can use.
    *   `await payRentTx.wait()`: Waits for the transaction to be mined and confirmed on the blockchain.
    *   Similar steps are performed for `terminateAgreement()` and `returnDeposit()`.
    *   `contractBalance = await rentalAgreement.getContractBalance()`: Calls the `getContractBalance()` function to retrieve the contract's balance.

**To Run the Example:**

1.  **Install Node.js:**  Make sure you have Node.js installed.

2.  **Install Dependencies:**
    ```bash
    npm install ethers fs dotenv
    ```

3.  **Create `.env` File:**  Create a file named `.env` in the same directory as your JavaScript file and add the following:

    ```
    RPC_URL=YOUR_RPC_URL  (e.g., http://127.0.0.1:8545 for Ganache)
    PRIVATE_KEY=YOUR_PRIVATE_KEY
    TENANT_ADDRESS=YOUR_TENANT_ADDRESS
    ```

    Replace `YOUR_RPC_URL`, `YOUR_PRIVATE_KEY`, and `YOUR_TENANT_ADDRESS` with your actual values.

4.  **Compile the Smart Contract:**
    ```bash
    solc RentalAgreement.sol --bin --abi -o .
    ```

5.  **Deploy and Interact:**
    ```bash
    node deployAndInteract.js
    ```

**Important Considerations:**

*   **Security:**  This is a simplified example.  In a real-world rental agreement, you would need to address many more security concerns, such as preventing rent from being paid too early, handling disputes, and more robust error handling.  Consider using OpenZeppelin contracts for standard security patterns.
*   **Time:** Solidity's `block.timestamp` is not perfectly reliable for precise timekeeping.  Consider using an oracle if you need more accurate time data.
*   **Gas Costs:**  Smart contract operations cost gas.  Optimize your code to minimize gas consumption.
*   **User Interface:** This example uses a command-line script.  You'll typically want to build a user interface (using React, Vue.js, etc.) to make it easier for landlords and tenants to interact with the contract.
*   **Oracles:** If the rental agreement needs to interact with off-chain data (e.g., property values, interest rates), you'll need to use an oracle.
*   **Auditing:**  Before deploying a smart contract to a live blockchain, it's crucial to have it audited by security professionals.
*   **Error Handling:** Add more robust error handling to your contract and JavaScript code to handle unexpected situations.
*   **Upgradability:**  Smart contracts are generally immutable. If you need to update the contract in the future, you'll need to use an upgradable contract pattern (which adds complexity).
*   **Date Handling**: Consider using a library to handle date calculations for different time zones and leap years.  The simple 30-day calculation is very basic.

This detailed example should give you a good foundation for building a smart contract-based rental agreement. Remember to always prioritize security and thoroughly test your code before deploying it.
👁️ Viewed: 10

Comments