What it does:
Allows multiple investors to collectively invest in a real estate property by owning fractional shares represented as on-chain tokens.
Why it matters:
Real estate becomes accessible, liquid, and transparent—no need to buy an entire property or trust intermediaries.
How it works:
A property is tokenized into fixed total shares.
Investors buy shares by depositing ETH.
Rental income or sale proceeds are distributed proportionally.
Ownership, balances, and payouts are fully on-chain.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title FractionalRealEstateInvestment
* @author Nam
* @notice Tokenized fractional ownership of a real estate property
*/
contract FractionalRealEstateInvestment {
// -------------------- TOKEN METADATA --------------------
string public name = "Fractional Property Share";
string public symbol = "FPS";
uint8 public decimals = 18;
uint256 public totalSupply;
uint256 public immutable sharePrice; // price per share in wei
// -------------------- ROLES --------------------
address public owner;
// -------------------- STATE --------------------
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
uint256 public totalRentalIncome;
uint256 public totalDistributed;
// -------------------- EVENTS --------------------
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
event SharesPurchased(address indexed buyer, uint256 shares);
event RentalIncomeAdded(uint256 amount);
event IncomeClaimed(address indexed investor, uint256 amount);
// -------------------- MODIFIERS --------------------
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
// -------------------- CONSTRUCTOR --------------------
constructor(
uint256 _totalShares,
uint256 _sharePrice
) {
require(_totalShares > 0, "Invalid shares");
require(_sharePrice > 0, "Invalid price");
owner = msg.sender;
totalSupply = _totalShares * 10 ** decimals;
sharePrice = _sharePrice;
// Owner initially holds all shares
balanceOf[owner] = totalSupply;
emit Transfer(address(0), owner, totalSupply);
}
// -------------------- ERC20 CORE --------------------
function transfer(address to, uint256 amount) external returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}
function approve(address spender, uint256 amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool) {
require(allowance[from][msg.sender] >= amount, "Allowance exceeded");
allowance[from][msg.sender] -= amount;
_transfer(from, to, amount);
return true;
}
function _transfer(address from, address to, uint256 amount) internal {
require(to != address(0), "Invalid recipient");
require(balanceOf[from] >= amount, "Insufficient balance");
balanceOf[from] -= amount;
balanceOf[to] += amount;
emit Transfer(from, to, amount);
}
// -------------------- INVESTMENT --------------------
/**
* @notice Buy property shares with ETH
*/
function buyShares(uint256 shares) external payable {
uint256 amount = shares * 10 ** decimals;
uint256 cost = shares * sharePrice;
require(msg.value == cost, "Incorrect ETH amount");
require(balanceOf[owner] >= amount, "Not enough shares");
_transfer(owner, msg.sender, amount);
emit SharesPurchased(msg.sender, shares);
}
// -------------------- RENTAL INCOME --------------------
/**
* @notice Add rental income to the pool
*/
function addRentalIncome() external payable onlyOwner {
require(msg.value > 0, "Zero income");
totalRentalIncome += msg.value;
emit RentalIncomeAdded(msg.value);
}
/**
* @notice Claim proportional rental income
*/
function claimIncome() external {
uint256 investorBalance = balanceOf[msg.sender];
require(investorBalance > 0, "No shares owned");
uint256 entitled = (totalRentalIncome * investorBalance) / totalSupply;
uint256 alreadyClaimed = (totalDistributed * investorBalance) / totalSupply;
uint256 claimable = entitled - alreadyClaimed;
require(claimable > 0, "Nothing to claim");
totalDistributed += claimable;
payable(msg.sender).transfer(claimable);
emit IncomeClaimed(msg.sender, claimable);
}
// -------------------- ADMIN --------------------
/**
* @notice Withdraw remaining ETH (e.g., property sale)
*/
function withdrawAll() external onlyOwner {
payable(owner).transfer(address(this).balance);
}
}
Build and Grow By Nam Le Thanh