Rental Token
🪙ZONY Real Estate Token — ERC-20
ERC-20OpenZeppelin 5.xPolygonUSDC
ERC

ZonyRealEstateToken.sol

ERC-20 com distribuição de renda proporcional (pull-based). Pausable, Ownable, KYC allowlist. OpenZeppelin 5.x auditado.

🪙

ERC-20 Standard

Transferível e divisível

💰

Pull Dividends

Sem loops caros em gas

🔐

KYC Allowlist

Compliance regulatório

⏸️

Pausable

Emergências via owner

🏦

USDC ou ETH

Escolha no constructor

🔑

Multisig Ready

Gnosis Safe como owner

Compile via Hardhat ou Remix IDE. OpenZeppelin 5.x — Solidity 0.8.20.

solidity — ZonyRealEstateToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";

/**
 * @title  ZonyRealEstateToken
 * @notice ERC-20 com distribuição de renda (aluguel) proporcional.
 * @dev    Pull-based dividends usando "accumulated dividends per share".
 */
contract ZonyRealEstateToken is ERC20, ERC20Pausable, Ownable {
    using Address for address payable;

    uint8 private immutable _decimals;
    bool  public transfersRestricted;
    mapping(address => bool) public allowlist;

    // ──── DIVIDENDS ────
    uint256 public constant ACC_PRECISION = 1e18;
    uint256 public accDividendPerShare;
    mapping(address => uint256) public dividendDebt;
    mapping(address => uint256) public pendingDividends;
    address public payoutToken; // address(0) = ETH

    event DividendsDeposited(uint256 amount);
    event Claimed(address indexed user, uint256 amount);
    event AllowlistUpdated(address indexed user, bool allowed);
    event TransfersRestricted(bool enabled);

    constructor(
        string  memory name_,
        string  memory symbol_,
        uint8          decimals_,
        address        payoutToken_,
        address        owner_
    ) ERC20(name_, symbol_) Ownable(owner_) {
        _decimals   = decimals_;
        payoutToken = payoutToken_;
    }

    function decimals() public view override returns (uint8) { return _decimals; }

    // ──── KYC / ALLOWLIST ────
    function setAllowlist(address user, bool allowed) external onlyOwner {
        allowlist[user] = allowed;
        emit AllowlistUpdated(user, allowed);
    }
    function setTransfersRestricted(bool enabled) external onlyOwner {
        transfersRestricted = enabled;
        emit TransfersRestricted(enabled);
    }

    // ──── MINT ────
    function mint(address to, uint256 amount) external onlyOwner {
        _updateAccount(to);
        _mint(to, amount);
        dividendDebt[to] = (balanceOf(to) * accDividendPerShare) / ACC_PRECISION;
    }

    // ──── PAUSE ────
    function pause()   external onlyOwner { _pause();   }
    function unpause() external onlyOwner { _unpause(); }

    // ──── TRANSFER HOOK ────
    function _update(address from, address to, uint256 value)
        internal override(ERC20, ERC20Pausable)
    {
        if (transfersRestricted) {
            if (from != address(0)) require(allowlist[from], "FROM_NOT_ALLOWED");
            if (to   != address(0)) require(allowlist[to],   "TO_NOT_ALLOWED");
        }
        if (from != address(0)) _updateAccount(from);
        if (to   != address(0)) _updateAccount(to);
        super._update(from, to, value);
        if (from != address(0))
            dividendDebt[from] = (balanceOf(from) * accDividendPerShare) / ACC_PRECISION;
        if (to != address(0))
            dividendDebt[to]   = (balanceOf(to)   * accDividendPerShare) / ACC_PRECISION;
    }

    // ──── RENT DEPOSIT ────
    receive() external payable { _deposit(msg.value); }
    function depositETH() external payable onlyOwner { _deposit(msg.value); }
    function depositToken(uint256 amount) external onlyOwner {
        require(payoutToken != address(0), "PAYOUT_IS_ETH");
        (bool ok, bytes memory data) = payoutToken.call(
            abi.encodeWithSignature(
                "transferFrom(address,address,uint256)",
                msg.sender, address(this), amount
            )
        );
        require(ok && (data.length == 0 || abi.decode(data, (bool))), "TRANSFER_FROM_FAIL");
        _deposit(amount);
    }
    function _deposit(uint256 amount) internal {
        require(totalSupply() > 0, "NO_SUPPLY");
        accDividendPerShare += (amount * ACC_PRECISION) / totalSupply();
        emit DividendsDeposited(amount);
    }

    // ──── CLAIM ────
    function claim() external {
        _updateAccount(msg.sender);
        uint256 amount = pendingDividends[msg.sender];
        require(amount > 0, "NOTHING_TO_CLAIM");
        pendingDividends[msg.sender] = 0;
        if (payoutToken == address(0)) {
            payable(msg.sender).sendValue(amount);
        } else {
            (bool ok, bytes memory data) = payoutToken.call(
                abi.encodeWithSignature("transfer(address,uint256)", msg.sender, amount)
            );
            require(ok && (data.length == 0 || abi.decode(data, (bool))), "TRANSFER_FAIL");
        }
        emit Claimed(msg.sender, amount);
    }

    // ──── VIEW ────
    function claimable(address user) external view returns (uint256) {
        uint256 bal         = balanceOf(user);
        uint256 accumulated = (bal * accDividendPerShare) / ACC_PRECISION;
        uint256 debt        = dividendDebt[user];
        uint256 pending     = pendingDividends[user];
        if (accumulated < debt) return pending;
        return pending + (accumulated - debt);
    }

    // ──── INTERNAL ────
    function _updateAccount(address user) internal {
        uint256 bal = balanceOf(user);
        if (bal == 0) { dividendDebt[user] = 0; return; }
        uint256 accumulated = (bal * accDividendPerShare) / ACC_PRECISION;
        uint256 debt        = dividendDebt[user];
        if (accumulated > debt) pendingDividends[user] += (accumulated - debt);
    }
}

📋 Referência de Funções

mint(address, uint256)onlyOwnerCria tokens para um investidor
depositETH()onlyOwnerDeposita aluguel em ETH
depositToken(uint256)onlyOwnerDeposita aluguel em USDC/ERC20
claim()publicInvestidor reivindica renda proporcional
claimable(address)publicRetorna quanto o usuário pode reivindicar
setAllowlist(address, bool)onlyOwnerAdiciona/remove usuário na allowlist KYC
setTransfersRestricted(bool)onlyOwnerHabilita restrição de KYC em transfers
pause() / unpause()onlyOwnerPara ou retoma o contrato em emergências
accDividendPerShare()publicTotal acumulado de dividendos por token
payoutToken()publicEndereço do token de pagamento (0x0 = ETH)
base44
Edit with Base44