Smart Contract-Based Real Estate Transactions Solidity, JavaScript

👤 Sharing: AI
Okay, here's a basic example of a smart contract for real estate transactions, along with a simple JavaScript interface to interact with it.  This is a simplified version for demonstration purposes and would need significant additions for real-world use (like security considerations, proper ownership transfer, etc.).

**Solidity Smart Contract (RealEstate.sol):**

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

contract RealEstate {

    // Structure to represent a property
    struct Property {
        uint256 price;
        address owner;
        string details; // Example: Description, address summary
        bool forSale;
    }

    // Mapping from property ID to Property struct
    mapping(uint256 => Property) public properties;

    // Counter for generating unique property IDs
    uint256 public propertyCount;

    // Event emitted when a new property is listed
    event PropertyListed(uint256 propertyId, address owner, uint256 price);

    // Event emitted when a property is sold
    event PropertySold(uint256 propertyId, address buyer, uint256 price);

    // Event emitted when a property is price changed
    event PropertyPriceChanged(uint256 propertyId, uint256 newPrice);


    constructor() {
        propertyCount = 0; // Initialize the property counter
    }

    // Function to list a new property
    function listProperty(uint256 _price, string memory _details) public {
        propertyCount++;  // Increment property ID
        uint256 propertyId = propertyCount;

        properties[propertyId] = Property(_price, msg.sender, _details, true);

        emit PropertyListed(propertyId, msg.sender, _price);
    }

    // Function to buy a property
    function buyProperty(uint256 _propertyId) public payable {
        require(_propertyId > 0 && _propertyId <= propertyCount, "Invalid property ID");
        require(properties[_propertyId].forSale, "Property is not for sale");
        require(msg.value >= properties[_propertyId].price, "Insufficient funds");

        address seller = properties[_propertyId].owner;
        uint256 price = properties[_propertyId].price;

        // Transfer ownership
        properties[_propertyId].owner = msg.sender;
        properties[_propertyId].forSale = false;

        // Transfer funds to the seller
        (bool success, ) = seller.call{value: price}("");  // Using call for fund transfer
        require(success, "Transfer failed.");


        emit PropertySold(_propertyId, msg.sender, price);

        // Optionally, return excess funds to the buyer (not strictly necessary, but good practice)
        if (msg.value > price) {
            (success, ) = msg.sender.call{value: msg.value - price}("");
            require(success, "Refund failed.");
        }
    }

    // Function to change the price of a property
    function changePrice(uint256 _propertyId, uint256 _newPrice) public {
        require(_propertyId > 0 && _propertyId <= propertyCount, "Invalid property ID");
        require(properties[_propertyId].owner == msg.sender, "Only the owner can change the price");
        require(properties[_propertyId].forSale, "Property must be for sale");

        properties[_propertyId].price = _newPrice;
        emit PropertyPriceChanged(_propertyId, _newPrice);
    }

    // Function to get property details
    function getPropertyDetails(uint256 _propertyId) public view returns (uint256 price, address owner, string memory details, bool forSale) {
        require(_propertyId > 0 && _propertyId <= propertyCount, "Invalid property ID");
        return (
            properties[_propertyId].price,
            properties[_propertyId].owner,
            properties[_propertyId].details,
            properties[_propertyId].forSale
        );
    }


    // Function to allow the owner to put the property back for sale
    function putPropertyForSale(uint256 _propertyId) public {
        require(_propertyId > 0 && _propertyId <= propertyCount, "Invalid property ID");
        require(properties[_propertyId].owner == msg.sender, "Only the owner can put it for sale");
        require(!properties[_propertyId].forSale, "Property is already for sale");

        properties[_propertyId].forSale = true;
    }
}
```

**Explanation of Solidity Code:**

*   **`pragma solidity ^0.8.0;`**: Specifies the Solidity compiler version.
*   **`contract RealEstate { ... }`**: Defines the smart contract.
*   **`struct Property { ... }`**: Defines a structure to hold property information (price, owner, details, sale status).
*   **`mapping(uint256 => Property) public properties;`**: A mapping that associates a property ID (uint256) with its `Property` struct.  `public` makes it accessible directly from outside the contract (read-only).
*   **`uint256 public propertyCount;`**: Keeps track of the total number of properties listed. `public` makes it accessible directly from outside the contract (read-only).
*   **`event PropertyListed(...)`**:  Events are used to log actions that happen in the contract.  They are useful for front-end applications to react to changes on the blockchain.
*   **`constructor() { ... }`**: The constructor is executed only once, when the contract is deployed. It initializes `propertyCount`.
*   **`listProperty(uint256 _price, string memory _details) public { ... }`**: Allows an owner to list a new property for sale.
    *   `propertyCount++`:  Increments the property ID.
    *   `properties[propertyId] = Property(...)`:  Creates a new `Property` struct and stores it in the `properties` mapping. `msg.sender` is the address of the person (or contract) calling the function.
    *   `emit PropertyListed(...)`: Emits the `PropertyListed` event.
*   **`buyProperty(uint256 _propertyId) public payable { ... }`**:  Allows someone to buy a property.
    *   `require(...)`:  Checks conditions and throws an error if they are not met.  This is crucial for security.
    *   `msg.value`: The amount of Ether sent with the transaction.
    *   `properties[_propertyId].owner = msg.sender;`:  Changes the property's owner.
    *   `(bool success, ) = seller.call{value: price}("");`:  Sends Ether to the seller's address.  The `call` function is used for sending Ether.  It's important to check the `success` value to ensure the transfer worked.
    *  Emits the `PropertySold` event.
    *   Refunds any excess funds.
*   **`changePrice(uint256 _propertyId, uint256 _newPrice) public { ... }`**: Allows the owner to change the price of a property.
*   **`getPropertyDetails(uint256 _propertyId) public view returns (...) { ... }`**: Allows anyone to view the details of a property. The `view` keyword means this function doesn't modify the contract's state, so it doesn't cost gas to call.
*   **`putPropertyForSale(uint256 _propertyId) public { ... }`**: Allows the owner to put the property back for sale.

**JavaScript Interface (interaction.js):**

```javascript
const Web3 = require('web3');
const contractABI = [   // Replace with your actual ABI. Generated when you compile RealEstate.sol
  {
    "inputs": [],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "propertyId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "buyer",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "price",
        "type": "uint256"
      }
    ],
    "name": "PropertySold",
    "type": "event"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "_propertyId",
        "type": "uint256"
      }
    ],
    "name": "buyProperty",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "_propertyId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "_newPrice",
        "type": "uint256"
      }
    ],
    "name": "changePrice",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "_propertyId",
        "type": "uint256"
      }
    ],
    "name": "getPropertyDetails",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "price",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "internalType": "string",
        "name": "details",
        "type": "string"
      },
      {
        "internalType": "bool",
        "name": "forSale",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "_price",
        "type": "uint256"
      },
      {
        "internalType": "string",
        "name": "_details",
        "type": "string"
      }
    ],
    "name": "listProperty",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "_propertyId",
        "type": "uint256"
      }
    ],
    "name": "putPropertyForSale",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "properties",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "price",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "internalType": "string",
        "name": "details",
        "type": "string"
      },
      {
        "internalType": "bool",
        "name": "forSale",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "propertyCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]; // Replace with your actual ABI
const contractAddress = 'YOUR_CONTRACT_ADDRESS'; // Replace with your deployed contract address

// Replace with your Ethereum provider URL (e.g., Ganache)
const web3 = new Web3('http://localhost:7545');

const realEstateContract = new web3.eth.Contract(contractABI, contractAddress);

// Example function to list a property
async function listNewProperty(price, details, fromAddress) {
    try {
        const tx = await realEstateContract.methods.listProperty(price, details).send({ from: fromAddress, gas: 3000000 }); // Adjust gas limit as needed
        console.log("Property Listed! Transaction Hash:", tx.transactionHash);
    } catch (error) {
        console.error("Error listing property:", error);
    }
}

// Example function to buy a property
async function buyExistingProperty(propertyId, amountToSend, fromAddress) {
    try {
        const tx = await realEstateContract.methods.buyProperty(propertyId).send({ from: fromAddress, value: amountToSend, gas: 3000000 }); // Adjust gas limit as needed
        console.log("Property Bought! Transaction Hash:", tx.transactionHash);
    } catch (error) {
        console.error("Error buying property:", error);
    }
}

// Example function to get property details
async function getPropertyInfo(propertyId) {
    try {
        const details = await realEstateContract.methods.getPropertyDetails(propertyId).call();
        console.log("Property Details:", details);
    } catch (error) {
        console.error("Error getting property details:", error);
    }
}

// Example function to change property price
async function changePropertyPrice(propertyId, newPrice, fromAddress) {
    try {
        const tx = await realEstateContract.methods.changePrice(propertyId, newPrice).send({ from: fromAddress, gas: 3000000 });
        console.log("Price changed! Transaction hash:", tx.transactionHash);
    } catch (error) {
        console.error("Error changing price:", error);
    }
}


// Example usage (replace with your values)

const ownerAddress = '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4';  // Replace with an actual account address
const buyerAddress = '0xa51c481674c53980054b3709055490634e6a2906'; // Replace with an actual account address

//Uncomment to run each one
//listNewProperty(1000000000000000000, "Beautiful apartment with a view", ownerAddress); // Price in Wei (1 Ether)

//buyExistingProperty(1, 1000000000000000000, buyerAddress); // Buy property with ID 1, sending 1 Ether

//getPropertyInfo(1); // Get details for property with ID 1

//changePropertyPrice(1, 1200000000000000000, ownerAddress); //Change property 1 price to 1.2 Ether

```

**Explanation of JavaScript Code:**

*   **`const Web3 = require('web3');`**: Imports the Web3.js library. You'll need to install it: `npm install web3`.
*   **`const contractABI = [...]`**:  This is the Application Binary Interface (ABI).  The ABI is a JSON array that describes the functions, events, and data structures of your smart contract.  *It's crucial to use the correct ABI generated by the Solidity compiler when you compile your `RealEstate.sol` file.*  Without the correct ABI, Web3.js won't know how to interact with your contract.
*   **`const contractAddress = 'YOUR_CONTRACT_ADDRESS';`**:  The address where your `RealEstate` contract is deployed on the blockchain.  You'll need to replace this with the actual address.
*   **`const web3 = new Web3('http://localhost:7545');`**: Creates a Web3 instance and connects to an Ethereum provider.  In this example, it's connecting to a local Ganache instance (a development blockchain).  You might need to change this URL depending on your provider.
*   **`const realEstateContract = new web3.eth.Contract(contractABI, contractAddress);`**: Creates a Web3 contract object.  This object is what you use to interact with your smart contract.  It takes the ABI and the contract address as arguments.
*   **`async function listNewProperty(price, details, fromAddress) { ... }`**: An example function that calls the `listProperty` function of the smart contract.
    *   `realEstateContract.methods.listProperty(price, details).send({ from: fromAddress, gas: 3000000 });`:  This is the core of the interaction.
        *   `realEstateContract.methods.listProperty(price, details)`:  Selects the `listProperty` function from the contract.
        *   `.send({ from: fromAddress, gas: 3000000 })`:  Sends the transaction to the blockchain.  `from` specifies the account that will pay for the transaction (gas).  `gas` is the gas limit for the transaction.  You may need to adjust the gas limit depending on the complexity of your contract.
    *   `console.log("Transaction Hash:", tx.transactionHash);`: Logs the transaction hash, which you can use to track the transaction on a block explorer.
*   **`async function buyExistingProperty(propertyId, amountToSend, fromAddress) { ... }`**:  An example function that calls the `buyProperty` function.  It includes the `value` parameter to send Ether along with the transaction.
*   **`async function getPropertyInfo(propertyId) { ... }`**:  An example function that calls the `getPropertyDetails` function.  Since `getPropertyDetails` is a `view` function, it doesn't modify the blockchain, so you use `.call()` instead of `.send()`.  `.call()` retrieves the return value of the function.
*   **`const ownerAddress = 'YOUR_ACCOUNT_ADDRESS';`**: Replace with an actual Ethereum account address that you control (e.g., from Ganache).  This address needs to have Ether to pay for gas.

**How to Run This Example:**

1.  **Install Prerequisites:**
    *   Node.js and npm (Node Package Manager)
    *   Ganache (a local Ethereum blockchain for development) or another Ethereum provider (like Infura).
2.  **Install Web3.js:**
    ```bash
    npm install web3
    ```
3.  **Compile the Solidity Contract:**  You'll need to use a Solidity compiler (like `solc`) to compile `RealEstate.sol` into bytecode and generate the ABI.  You can use Remix (an online Solidity IDE), Truffle, or Hardhat for this.
4.  **Deploy the Contract:** Deploy the compiled contract to your chosen Ethereum provider (Ganache, a testnet, or mainnet).  Note the contract address after deployment.
5.  **Update the JavaScript Code:**
    *   Replace `YOUR_CONTRACT_ADDRESS` in `interaction.js` with the actual contract address.
    *   Replace `YOUR_ACCOUNT_ADDRESS` with a valid Ethereum account address that you control. Make sure this account has enough Ether to pay for gas.  You can get test Ether from a faucet if you're using a testnet.
    *   Update the `contractABI` variable with the ABI generated during compilation.  This is a very long JSON array.
    *   Adjust the `web3` provider URL if you're not using Ganache on the default port.
6.  **Run the JavaScript Code:**
    ```bash
    node interaction.js
    ```

**Important Considerations:**

*   **Security:** This is a *very* basic example and is not secure for production use.  You need to carefully consider security vulnerabilities like reentrancy attacks, integer overflows, and front-running.  Use established security patterns and audit your code.
*   **Error Handling:**  The JavaScript code has basic error handling, but you should implement more robust error handling in a real application.
*   **Gas Optimization:**  Solidity code can be optimized to reduce gas costs.
*   **Data Storage:** Storing complex data directly on the blockchain can be expensive. Consider using off-chain storage solutions (like IPFS) for large data.
*   **Ownership Transfer:** The current ownership transfer is very basic.  In a real-world scenario, you'd need to handle legal aspects, escrows, and potentially involve oracles.
*   **User Interface:** This example uses a simple JavaScript console interface. For a real application, you'd want to create a user-friendly web interface (using React, Vue, or Angular) to interact with the smart contract.
*   **Testing:** Thoroughly test your smart contract using frameworks like Truffle or Hardhat before deploying it to a live environment.
*   **Gas Limit:** The `gas` value is important. If you set it too low, the transaction will fail. Too high and you waste gas. You can use `estimateGas` to get a more accurate value.

This comprehensive example provides a solid foundation for understanding how to build smart contract-based real estate applications. Remember to prioritize security and thorough testing as you develop your project further.
👁️ Viewed: 9

Comments