0% found this document useful (0 votes)
20 views17 pages

Download

The VexoraProperties contract is an ERC20 token designed for tokenized real estate investments, featuring fractional ownership, property registry, rental income distribution, and governance. It integrates with PancakeSwap for liquidity and includes roles for property management and compliance. The contract allows for property management functions, investment tracking, and income distribution, ensuring compliance with transaction limits and whitelisting for participants.

Uploaded by

vexoraproperties
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
20 views17 pages

Download

The VexoraProperties contract is an ERC20 token designed for tokenized real estate investments, featuring fractional ownership, property registry, rental income distribution, and governance. It integrates with PancakeSwap for liquidity and includes roles for property management and compliance. The contract allows for property management functions, investment tracking, and income distribution, ensuring compliance with transaction limits and whitelisting for participants.

Uploaded by

vexoraproperties
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 17

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.29;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

interface IPancakeRouter01 {
function factory() external pure returns (address);
function WETH() external pure returns (address);
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable returns (uint amountToken, uint amountETH, uint liquidity);
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to,
uint deadline)
external
payable
returns (uint[] memory amounts);
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata
path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata
path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint
deadline)
external
payable
returns (uint[] memory amounts);
}

interface IPancakeRouter02 is IPancakeRouter01 {


function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
}
interface IPancakeFactory {
function createPair(address tokenA, address tokenB) external returns (address pair);
}

/**
* @title VexoraProperties
* @dev ERC20 token with features specialized for tokenized real estate investments:
* - Fractional ownership of properties
* - Property registry
* - Distribution of rental income
* - Property governance
* - Integration with BSC/PancakeSwap
*/
contract VexoraProperties is ERC20Burnable, ERC20Pausable, AccessControl,
ReentrancyGuard {
using SafeMath for uint256;

// Roles
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant PROPERTY_MANAGER_ROLE =
keccak256("PROPERTY_MANAGER_ROLE");
bytes32 public constant COMPLIANCE_ROLE = keccak256("COMPLIANCE_ROLE");

// PancakeSwap
IPancakeRouter02 public pancakeRouter;
address public pancakeswapPair;

// Token Configuration
uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18; // 1 billion tokens

// Token Distribution
uint256 public constant PROPERTY_ACQUISITION_ALLOCATION = 500_000_000 * 1e18; //
50%
uint256 public constant LIQUIDITY_ALLOCATION = 200_000_000 * 1e18; // 20%
uint256 public constant TEAM_ALLOCATION = 100_000_000 * 1e18; // 10%
uint256 public constant MARKETING_ALLOCATION = 100_000_000 * 1e18; // 10%
uint256 public constant RESERVE_FUND_ALLOCATION = 100_000_000 * 1e18; // 10%

// Addresses
address public treasuryWallet;
address public marketingWallet;
address public teamWallet;
address public reserveFundWallet;

// Property Management
struct Property {
uint256 id;
string name;
string location;
uint256 value; // Property value in USD cents
uint256 tokenAllocation; // Amount of tokens representing this property
bool isActive;
uint256 rentalIncome; // Accumulated rental income in stable coins (e.g., BUSD, USDT)
address paymentToken; // Token used for rental payments (e.g., BUSD)
uint256 lastDistributionTime;
}

struct PropertyInvestor {
uint256 propertyId;
address investor;
uint256 tokenAmount;
}

mapping(uint256 => Property) public properties;


mapping(uint256 => mapping(address => uint256)) public propertyInvestments;
mapping(address => uint256[]) public investorProperties;
uint256 public propertyCount;

// Distribution and Finance


mapping(address => uint256) public unclaimedDividends;
mapping(uint256 => uint256) public propertyTotalInvestment; // Total tokens invested in each
property

// Compliance
mapping(address => bool) public isWhitelisted;
bool public whitelistEnabled = true;
uint256 public maxTransactionAmount;

// Treasury
uint256 public treasuryBalance;

// Events
event PropertyAdded(uint256 indexed propertyId, string name, string location, uint256 value,
uint256 tokenAllocation);
event PropertyInvestmentMade(uint256 indexed propertyId, address indexed investor,
uint256 tokenAmount);
event PropertyInvestmentWithdrawn(uint256 indexed propertyId, address indexed investor,
uint256 tokenAmount);
event RentalIncomeReceived(uint256 indexed propertyId, uint256 amount);
event DividendDistributed(uint256 indexed propertyId, uint256 totalAmount);
event DividendClaimed(address indexed investor, uint256 amount);
event WhitelistStatusChanged(address indexed account, bool status);
event WhitelistEnabledChanged(bool enabled);
event PropertyStatusChanged(uint256 indexed propertyId, bool isActive);

// Modifiers
modifier onlyPropertyManager() {
require(hasRole(PROPERTY_MANAGER_ROLE, msg.sender), "Caller is not a property
manager");
_;
}

modifier onlyAdmin() {
require(hasRole(ADMIN_ROLE, msg.sender), "Caller is not an admin");
_;
}

modifier onlyCompliance() {
require(hasRole(COMPLIANCE_ROLE, msg.sender), "Caller is not compliance");
_;
}

/**
* @dev Constructor sets up roles and initializes the token
* @param _treasuryWallet Address of the treasury wallet
* @param _marketingWallet Address of the marketing wallet
* @param _teamWallet Address of the team wallet
* @param _reserveFundWallet Address of the reserve fund wallet
* @param _pancakeRouterAddress Address of the PancakeSwap router
*/
constructor(
address _treasuryWallet,
address _marketingWallet,
address _teamWallet,
address _reserveFundWallet,
address _pancakeRouterAddress
) ERC20("Vexora Properties Token", "VXP") {
require(_treasuryWallet != address(0), "Treasury wallet cannot be the zero address");
require(_marketingWallet != address(0), "Marketing wallet cannot be the zero address");
require(_teamWallet != address(0), "Team wallet cannot be the zero address");
require(_reserveFundWallet != address(0), "Reserve fund wallet cannot be the zero
address");
require(_pancakeRouterAddress != address(0), "PancakeSwap router cannot be the zero
address");

// Setup roles - FIXED: Using _grantRole instead of deprecated _setupRole


_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(ADMIN_ROLE, msg.sender);
_grantRole(PROPERTY_MANAGER_ROLE, msg.sender);
_grantRole(COMPLIANCE_ROLE, msg.sender);

// Set wallets
treasuryWallet = _treasuryWallet;
marketingWallet = _marketingWallet;
teamWallet = _teamWallet;
reserveFundWallet = _reserveFundWallet;

// Set PancakeSwap
pancakeRouter = IPancakeRouter02(_pancakeRouterAddress);

// Create pair with WBNB


pancakeswapPair = IPancakeFactory(pancakeRouter.factory())
.createPair(address(this), pancakeRouter.WETH());

// Set max transaction amount to 1% of total supply


maxTransactionAmount = MAX_SUPPLY.div(100);

// Mint and distribute tokens


_mint(treasuryWallet, PROPERTY_ACQUISITION_ALLOCATION);
_mint(address(this), LIQUIDITY_ALLOCATION); // Held for liquidity provision
_mint(teamWallet, TEAM_ALLOCATION);
_mint(marketingWallet, MARKETING_ALLOCATION);
_mint(reserveFundWallet, RESERVE_FUND_ALLOCATION);

// Whitelist the initial wallets


isWhitelisted[treasuryWallet] = true;
isWhitelisted[marketingWallet] = true;
isWhitelisted[teamWallet] = true;
isWhitelisted[reserveFundWallet] = true;
isWhitelisted[msg.sender] = true;
}

/**
* @dev Hook that is called before any transfer of tokens
* Implements compliance checks and transaction limits
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal override(ERC20, ERC20Pausable) {
super._beforeTokenTransfer(from, to, amount);

if (from == address(0) || to == address(0) || from == address(this) || to == address(this)) {


return; // Skip checks for minting, burning, and contract interactions
}

// Check if whitelist is enabled and enforce it


if (whitelistEnabled) {
require(isWhitelisted[from] && isWhitelisted[to], "Address not whitelisted");
}

// Check max transaction amount


require(amount <= maxTransactionAmount, "Transfer amount exceeds the max transaction
amount");
}

// ======================== Property Management Functions


========================

/**
* @dev Add a new property to the registry
* @param _name Name of the property
* @param _location Location of the property
* @param _value Value of the property in USD cents
* @param _tokenAllocation Amount of tokens allocated to this property
* @param _paymentToken Token used for rental payments
*/
function addProperty(
string memory _name,
string memory _location,
uint256 _value,
uint256 _tokenAllocation,
address _paymentToken
) external onlyPropertyManager {
require(_tokenAllocation <= PROPERTY_ACQUISITION_ALLOCATION, "Token allocation
exceeds available tokens");
require(bytes(_name).length > 0, "Name cannot be empty");
require(bytes(_location).length > 0, "Location cannot be empty");
require(_value > 0, "Value must be greater than 0");
require(_paymentToken != address(0), "Payment token cannot be zero address");

propertyCount++;
uint256 propertyId = propertyCount;

properties[propertyId] = Property({
id: propertyId,
name: _name,
location: _location,
value: _value,
tokenAllocation: _tokenAllocation,
isActive: true,
rentalIncome: 0,
paymentToken: _paymentToken,
lastDistributionTime: block.timestamp
});

emit PropertyAdded(propertyId, _name, _location, _value, _tokenAllocation);


}

/**
* @dev Invest tokens in a specific property
* @param _propertyId ID of the property
* @param _amount Amount of tokens to invest
*/
function investInProperty(uint256 _propertyId, uint256 _amount) external nonReentrant {
require(_propertyId <= propertyCount && _propertyId > 0, "Invalid property ID");
require(properties[_propertyId].isActive, "Property is not active");
require(_amount > 0, "Amount must be greater than 0");
require(balanceOf(msg.sender) >= _amount, "Insufficient balance");

Property storage property = properties[_propertyId];

// Ensure the total investment doesn't exceed the property's token allocation
uint256 newTotalInvestment = propertyTotalInvestment[_propertyId].add(_amount);
require(newTotalInvestment <= property.tokenAllocation, "Investment would exceed
property allocation");

// Update the investment records


propertyInvestments[_propertyId][msg.sender] =
propertyInvestments[_propertyId][msg.sender].add(_amount);
propertyTotalInvestment[_propertyId] = newTotalInvestment;

// Add property to investor's portfolio if not already there


bool propertyFound = false;
for (uint256 i = 0; i < investorProperties[msg.sender].length; i++) {
if (investorProperties[msg.sender][i] == _propertyId) {
propertyFound = true;
break;
}
}

if (!propertyFound) {
investorProperties[msg.sender].push(_propertyId);
}

// Transfer tokens to the contract for safekeeping


_transfer(msg.sender, address(this), _amount);

emit PropertyInvestmentMade(_propertyId, msg.sender, _amount);


}

/**
* @dev Withdraw investment from a property
* @param _propertyId ID of the property
* @param _amount Amount of tokens to withdraw
*/
function withdrawInvestment(uint256 _propertyId, uint256 _amount) external nonReentrant {
require(_propertyId <= propertyCount && _propertyId > 0, "Invalid property ID");
require(_amount > 0, "Amount must be greater than 0");
require(propertyInvestments[_propertyId][msg.sender] >= _amount, "Insufficient
investment");

// Update investment records


propertyInvestments[_propertyId][msg.sender] =
propertyInvestments[_propertyId][msg.sender].sub(_amount);
propertyTotalInvestment[_propertyId] =
propertyTotalInvestment[_propertyId].sub(_amount);
// Remove property from investor's portfolio if investment becomes zero
if (propertyInvestments[_propertyId][msg.sender] == 0) {
for (uint256 i = 0; i < investorProperties[msg.sender].length; i++) {
if (investorProperties[msg.sender][i] == _propertyId) {
// Replace the item to remove with the last item
investorProperties[msg.sender][i] =
investorProperties[msg.sender][investorProperties[msg.sender].length - 1];
// Remove the last item
investorProperties[msg.sender].pop();
break;
}
}
}

// Transfer tokens back to the investor


_transfer(address(this), msg.sender, _amount);

emit PropertyInvestmentWithdrawn(_propertyId, msg.sender, _amount);


}

/**
* @dev Set the active status of a property
* @param _propertyId ID of the property
* @param _isActive Whether the property is active
*/
function setPropertyStatus(uint256 _propertyId, bool _isActive) external onlyPropertyManager
{
require(_propertyId <= propertyCount && _propertyId > 0, "Invalid property ID");

properties[_propertyId].isActive = _isActive;

emit PropertyStatusChanged(_propertyId, _isActive);


}

/**
* @dev Get all properties invested in by an address
* @param _investor Address of the investor
* @return Array of property IDs
*/
function getInvestorProperties(address _investor) external view returns (uint256[] memory) {
return investorProperties[_investor];
}
/**
* @dev Get investment amount in a specific property
* @param _propertyId ID of the property
* @param _investor Address of the investor
* @return Amount of tokens invested
*/
function getPropertyInvestment(uint256 _propertyId, address _investor) external view returns
(uint256) {
return propertyInvestments[_propertyId][_investor];
}

// ======================== Income Distribution Functions


========================

/**
* @dev Record rental income for a property (paid in stable tokens)
* @param _propertyId ID of the property
* @param _amount Amount of stable tokens received as rent
*/
function recordRentalIncome(uint256 _propertyId, uint256 _amount) external
onlyPropertyManager {
require(_propertyId <= propertyCount && _propertyId > 0, "Invalid property ID");
require(_amount > 0, "Amount must be greater than 0");
require(properties[_propertyId].isActive, "Property is not active");

properties[_propertyId].rentalIncome = properties[_propertyId].rentalIncome.add(_amount);

emit RentalIncomeReceived(_propertyId, _amount);


}

/**
* @dev Distribute rental income among property investors
* @param _propertyId ID of the property
*/
function distributeRentalIncome(uint256 _propertyId) external onlyPropertyManager
nonReentrant {
require(_propertyId <= propertyCount && _propertyId > 0, "Invalid property ID");

Property storage property = properties[_propertyId];


require(property.isActive, "Property is not active");
require(property.rentalIncome > 0, "No rental income to distribute");
require(propertyTotalInvestment[_propertyId] > 0, "No investors for this property");
uint256 totalIncome = property.rentalIncome;

// Reset rental income for this property


property.rentalIncome = 0;
property.lastDistributionTime = block.timestamp;

// Transfer income from manager to contract (assumes manager received the rental income
as stable tokens)
// Implementation depends on the stable token used for payments

emit DividendDistributed(_propertyId, totalIncome);


}

/**
* @dev Claim accumulated dividends for an investor
*/
function claimDividends() external nonReentrant {
uint256 amount = unclaimedDividends[msg.sender];
require(amount > 0, "No dividends to claim");

unclaimedDividends[msg.sender] = 0;

// Transfer dividend tokens to the investor


// Implementation depends on the stable token used for payments

emit DividendClaimed(msg.sender, amount);


}

/**
* @dev Get unclaimed dividends for an investor
* @param _investor Address of the investor
* @return Amount of unclaimed dividends
*/
function getUnclaimedDividends(address _investor) external view returns (uint256) {
return unclaimedDividends[_investor];
}

// ======================== Compliance Functions ========================

/**
* @dev Add or remove an address from the whitelist
* @param _account Address to update
* @param _status Whitelist status
*/
function updateWhitelist(address _account, bool _status) external onlyCompliance {
isWhitelisted[_account] = _status;
emit WhitelistStatusChanged(_account, _status);
}

/**
* @dev Batch update whitelist for multiple addresses
* @param _accounts Array of addresses to update
* @param _status Whitelist status for all addresses
*/
function batchUpdateWhitelist(address[] calldata _accounts, bool _status) external
onlyCompliance {
for (uint256 i = 0; i < _accounts.length; i++) {
isWhitelisted[_accounts[i]] = _status;
emit WhitelistStatusChanged(_accounts[i], _status);
}
}

/**
* @dev Enable or disable the whitelist requirement
* @param _enabled Whether the whitelist is enabled
*/
function setWhitelistEnabled(bool _enabled) external onlyCompliance {
whitelistEnabled = _enabled;
emit WhitelistEnabledChanged(_enabled);
}

/**
* @dev Set the maximum transaction amount
* @param _maxAmount New maximum transaction amount
*/
function setMaxTransactionAmount(uint256 _maxAmount) external onlyAdmin {
require(_maxAmount > 0, "Max transaction amount must be greater than 0");
maxTransactionAmount = _maxAmount;
}

/**
* @dev Pause all token transfers
*/
function pause() external onlyAdmin {
_pause();
}
/**
* @dev Unpause all token transfers
*/
function unpause() external onlyAdmin {
_unpause();
}

// ======================== Liquidity and Treasury Functions


========================

/**
* @dev Add liquidity to PancakeSwap
* @param _tokenAmount Amount of VXP tokens to add to liquidity
* @param _router PancakeSwap router address
*/
function addLiquidityETH(uint256 _tokenAmount, address _router) external payable
onlyAdmin {
require(_tokenAmount > 0, "Token amount must be greater than 0");
require(msg.value > 0, "BNB amount must be greater than 0");

// Approve router to spend tokens


_approve(address(this), _router, _tokenAmount);

// Add liquidity
IPancakeRouter02(_router).addLiquidityETH{value: msg.value}(
address(this),
_tokenAmount,
0, // slippage is unavoidable
0, // slippage is unavoidable
treasuryWallet, // LP tokens sent to treasury
block.timestamp + 300 // deadline: 5 minutes
);
}

/**
* @dev Update treasury wallet address
* @param _newWallet New treasury wallet address
*/
function updateTreasuryWallet(address _newWallet) external onlyAdmin {
require(_newWallet != address(0), "New wallet cannot be the zero address");
treasuryWallet = _newWallet;
isWhitelisted[_newWallet] = true;
}

/**
* @dev Update marketing wallet address
* @param _newWallet New marketing wallet address
*/
function updateMarketingWallet(address _newWallet) external onlyAdmin {
require(_newWallet != address(0), "New wallet cannot be the zero address");
marketingWallet = _newWallet;
isWhitelisted[_newWallet] = true;
}

/**
* @dev Update team wallet address
* @param _newWallet New team wallet address
*/
function updateTeamWallet(address _newWallet) external onlyAdmin {
require(_newWallet != address(0), "New wallet cannot be the zero address");
teamWallet = _newWallet;
isWhitelisted[_newWallet] = true;
}

/**
* @dev Update reserve fund wallet address
* @param _newWallet New reserve fund wallet address
*/
function updateReserveFundWallet(address _newWallet) external onlyAdmin {
require(_newWallet != address(0), "New wallet cannot be the zero address");
reserveFundWallet = _newWallet;
isWhitelisted[_newWallet] = true;
}

// ======================== Utility Functions ========================

/**
* @dev Get property details
* @param _propertyId ID of the property
* @return Property details
*/
function getPropertyDetails(uint256 _propertyId) external view returns (
string memory name,
string memory location,
uint256 value,
uint256 tokenAllocation,
bool isActive,
uint256 rentalIncome,
address paymentToken,
uint256 lastDistributionTime,
uint256 totalInvestment
){
require(_propertyId <= propertyCount && _propertyId > 0, "Invalid property ID");

Property storage property = properties[_propertyId];

return (
property.name,
property.location,
property.value,
property.tokenAllocation,
property.isActive,
property.rentalIncome,
property.paymentToken,
property.lastDistributionTime,
propertyTotalInvestment[_propertyId]
);
}

/**
* @dev Get total number of properties
* @return Number of properties
*/
function getPropertyCount() external view returns (uint256) {
return propertyCount;
}

/**
* @dev Get total number of properties an investor has invested in
* @param _investor Address of the investor
* @return Number of properties
*/
function getInvestorPropertyCount(address _investor) external view returns (uint256) {
return investorProperties[_investor].length;
}

/**
* @dev Function to receive ETH that will be used for adding liquidity
*/
receive() external payable {}
}

You might also like