Created
February 6, 2026 05:37
-
-
Save bekovrafik/d58222453759312c05791a9f4e3d5392 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.20+commit.a1b79de6.js&optimize=undefined&runs=200&gist=
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // SPDX-License-Identifier: MIT | |
| pragma solidity ^0.8.20; | |
| import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | |
| import "@openzeppelin/contracts/access/Ownable.sol"; | |
| import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
| import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; | |
| /** | |
| * @title PropertyToken | |
| * @dev Represents a fractionalized property with Escrow, Refunds, and Dividend capabilities. | |
| * FORCED TO 6 DECIMALS to match USDC. | |
| */ | |
| contract PropertyToken is ERC20, Ownable, ReentrancyGuard { | |
| enum PropertyTier { Rental, Growth, Stay } | |
| enum Status { FUNDRAISING, ACTIVE, REFUND_MODE } | |
| PropertyTier public tier; | |
| Status public currentStatus; | |
| uint256 public pricePerToken; | |
| uint256 public targetRaiseAmount; | |
| uint256 public totalRaised; | |
| string public legalDocHash; | |
| IERC20 public paymentToken; | |
| // --- DIVIDEND TRACKING --- | |
| uint256 public totalDividendsDistributed; | |
| mapping(address => uint256) public lastClaimedDividend; | |
| // We track "Dividends Per Token" (DPT) to allow fair claims | |
| uint256 public dividendsPerTokenCumulative; | |
| uint256 public constant PRECISION = 1e18; // Precision for math | |
| event Invested(address indexed investor, uint256 tokenAmount, uint256 cost, string legalDocHashSigned); | |
| event FundraisingFinalized(uint256 totalRaised, uint256 timestamp); | |
| event RefundEnabled(uint256 timestamp); | |
| event Refunded(address indexed investor, uint256 tokenAmount, uint256 refundAmount); | |
| // --- NEW EVENTS --- | |
| event DividendDeposited(uint256 amount, uint256 timestamp); | |
| event DividendClaimed(address indexed investor, uint256 amount); | |
| event DocHashUpdated(string oldHash, string newHash); | |
| constructor( | |
| string memory name, | |
| string memory symbol, | |
| uint256 _targetSupply, | |
| uint256 _price, | |
| PropertyTier _tier, | |
| string memory _docHash, | |
| address _paymentToken, | |
| address admin | |
| ) ERC20(name, symbol) Ownable(admin) { | |
| tier = _tier; | |
| pricePerToken = _price; | |
| legalDocHash = _docHash; | |
| paymentToken = IERC20(_paymentToken); | |
| targetRaiseAmount = _targetSupply * _price; | |
| currentStatus = Status.FUNDRAISING; | |
| } | |
| /** | |
| * @dev Overrides the default 18 decimals to match USDC (6 decimals). | |
| */ | |
| function decimals() public view virtual override returns (uint8) { | |
| return 6; | |
| } | |
| // --- INVESTMENT LOGIC --- | |
| function invest(uint256 amount, string memory legalDocHashSigned) external nonReentrant { | |
| require(currentStatus == Status.FUNDRAISING, "Fundraising not active"); | |
| uint256 cost = amount * pricePerToken; | |
| require(totalRaised + cost <= targetRaiseAmount, "Exceeds target raise"); | |
| // Before minting, claim any pending dividends to reset the user's tracker | |
| if (totalSupply() > 0 && currentStatus == Status.ACTIVE) { | |
| _claimDividend(msg.sender); | |
| } | |
| require(paymentToken.transferFrom(msg.sender, address(this), cost), "Payment failed"); | |
| _mint(msg.sender, amount * 10**decimals()); | |
| totalRaised += cost; | |
| // Reset dividend tracker for new tokens | |
| lastClaimedDividend[msg.sender] = dividendsPerTokenCumulative; | |
| emit Invested(msg.sender, amount, cost, legalDocHashSigned); | |
| } | |
| // --- REVENUE DISTRIBUTION --- | |
| /** | |
| * @notice Admin deposits USDC Rent into the contract. | |
| */ | |
| function depositRent(uint256 amount) external onlyOwner nonReentrant { | |
| require(currentStatus == Status.ACTIVE, "Property not active"); | |
| require(totalSupply() > 0, "No tokens"); | |
| require(paymentToken.transferFrom(msg.sender, address(this), amount), "Transfer failed"); | |
| // Calculate Share Per Token | |
| uint256 amountPerToken = (amount * PRECISION) / totalSupply(); | |
| dividendsPerTokenCumulative += amountPerToken; | |
| totalDividendsDistributed += amount; | |
| emit DividendDeposited(amount, block.timestamp); | |
| } | |
| /** | |
| * @notice User calls this to withdraw their share. | |
| */ | |
| function claimRent() external nonReentrant { | |
| _claimDividend(msg.sender); | |
| } | |
| function _claimDividend(address user) internal { | |
| uint256 owed = getClaimableRent(user); | |
| if (owed > 0) { | |
| lastClaimedDividend[user] = dividendsPerTokenCumulative; | |
| require(paymentToken.transfer(user, owed), "Transfer failed"); | |
| emit DividendClaimed(user, owed); | |
| } else { | |
| lastClaimedDividend[user] = dividendsPerTokenCumulative; | |
| } | |
| } | |
| function getClaimableRent(address user) public view returns (uint256) { | |
| uint256 userBalance = balanceOf(user); | |
| if (userBalance == 0) return 0; | |
| uint256 difference = dividendsPerTokenCumulative - lastClaimedDividend[user]; | |
| return (userBalance * difference) / PRECISION; | |
| } | |
| // --- STANDARD ADMIN FUNCTIONS --- | |
| function finalizeFundraising() external onlyOwner nonReentrant { | |
| require(currentStatus == Status.FUNDRAISING, "Not fundraising"); | |
| currentStatus = Status.ACTIVE; | |
| uint256 balance = paymentToken.balanceOf(address(this)); | |
| require(paymentToken.transfer(owner(), balance), "Withdraw failed"); | |
| emit FundraisingFinalized(totalRaised, block.timestamp); | |
| } | |
| function enableRefunds() external onlyOwner { | |
| require(currentStatus == Status.FUNDRAISING, "Too late"); | |
| currentStatus = Status.REFUND_MODE; | |
| emit RefundEnabled(block.timestamp); | |
| } | |
| function refund() external nonReentrant { | |
| require(currentStatus == Status.REFUND_MODE, "Refunds not active"); | |
| uint256 userBalance = balanceOf(msg.sender); | |
| require(userBalance > 0, "No tokens"); | |
| // Calculate Refund: (Tokens * Price) / 10^6 | |
| uint256 refundAmt = (userBalance * pricePerToken) / (10**decimals()); | |
| _burn(msg.sender, userBalance); | |
| require(paymentToken.transfer(msg.sender, refundAmt), "Transfer failed"); | |
| emit Refunded(msg.sender, userBalance, refundAmt); | |
| } | |
| function updateLegalDocHash(string memory newHash) external onlyOwner { | |
| string memory old = legalDocHash; | |
| legalDocHash = newHash; | |
| emit DocHashUpdated(old, newHash); | |
| } | |
| } | |
| /** | |
| * @title PropertyFactory | |
| * @dev Deploys the new Crowdfunding-ready PropertyTokens. | |
| */ | |
| contract PropertyFactory is Ownable { | |
| address[] public allProperties; | |
| IERC20 public stablecoin; | |
| event PropertyDeployed( | |
| address indexed propertyAddress, | |
| string name, | |
| uint256 price, | |
| uint8 tier, | |
| uint256 targetRaise | |
| ); | |
| constructor(address _stablecoinAddress) Ownable(msg.sender) { | |
| stablecoin = IERC20(_stablecoinAddress); | |
| } | |
| function deployProperty( | |
| string memory name, | |
| string memory symbol, | |
| uint256 supply, | |
| uint256 price, | |
| uint8 tierIndex, | |
| string memory docHash | |
| ) external onlyOwner returns (address) { | |
| require(tierIndex <= 2, "Invalid property tier"); | |
| PropertyToken newProperty = new PropertyToken( | |
| name, | |
| symbol, | |
| supply, | |
| price, | |
| PropertyToken.PropertyTier(tierIndex), | |
| docHash, | |
| address(stablecoin), | |
| owner() | |
| ); | |
| address propertyAddr = address(newProperty); | |
| allProperties.push(propertyAddr); | |
| uint256 target = supply * price; | |
| emit PropertyDeployed(propertyAddr, name, price, tierIndex, target); | |
| return propertyAddr; | |
| } | |
| function getProperties() external view returns (address[] memory) { | |
| return allProperties; | |
| } | |
| function setStablecoin(address _newStablecoin) external onlyOwner { | |
| stablecoin = IERC20(_newStablecoin); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment