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