Asset (ERC1155) Contracts (old)

Overview

The Squish721 contract is a contract designed to store multiple ERC721 tokens, each representing an asset. This contract allows for the grouping of tokens into units, which're functions that handle the rights associated with the assets. Unlike contracts Squish721 manages several tokens that collectively represent a single asset. All contract. Features function on chain to ensure transparency and security. The metadata is stored using IPFS and users can manage all rights on chain through units—these units are boolean values inside the tokens. If a unit value is true it indicates that the token holds a right managed within the contract. Additionally our contract includes a DAO, for changes a compliance contract connected to the asset smart contract (but not embedded within it) a burning mechanism that issues an ERC721 token as proof of burnt tokens, a staking mechanism, price insurance pool and much more.


Tokenization Process

Step one - Filling out an online form and upload documents

First, you have to choose the kind of asset and fill out everything we ask for, upload documents and let that be verified by us and later by the oracles. If this information is true, an appointment can be made.

Step two - Automatic response with information and meeting

After the information is verified, you are able to make an appointment online with our company for the contract with a notary in most cases that the user has to pay.

Step three - Contract signature and uploading

Once the signature is done, we will upload it into a smart contract where we have multiple templates and just fill in/extract the information. Once the confirmation from the local court/authority is there, the contract will be sent on-chain to the user, with that, the contract mints the token and the asset gets independent, digital, secure, and decentralized for now and forever.

Step four - Token minting

The user inputted how many tokens he wants to have for the asset, and from that we take the token amount, which can be between 100-10.000 tokens per asset. Because a user needs a network identity to tokenize his assets from the beginning, the system has already stored and recognized the user's wallet. That means the tokens will be minted once the contract reaches the already stored identity.


Contract Creation Structure


Contract Structure

The ERC1155 contract consists of the following key components:

  1. Token Storage: The contract has a system to store ERC721 tokens. Each token has its unique ID and is linked to specific asset details. The minimum number of tokens allowed is 100 while the maximum is 10,000.

  2. Audit Contract: There is also an audit contract on the blockchain that maintains a record of the tokens and all related information. To access the land register data users are required to pay a fee of, than 5 stablecoins. This fee will be given to the property owner as interest.

  3. Unit Management: Units are categorized as classes that contain maps of tokens and underlying on chain functions responsible for managing rights. For instance within the contract there exists a class called "rentalRights" that encompasses a map consisting of IDs associated with these rights along with the corresponding functions that grant them. Tokens can be either. Added or removed from units. Typically during minting all units are predefined and each token possesses identical rights.

  4. DAO Mechanism: The contract establishes an autonomous organization to ensure active participation in decision making processes by every token holder.

  5. Burn Contract: Users have the option to burn their tokens and receive an ERC721 token as confirmation for the burning process; however this ERC721 token holds no value.

  6. Property Management: The on chain function is responsible for overseeing aspects related to real estate properties such as adding new properties implementing changes within existing properties and managing their status (e.g. rented, on sale, frozen). This information is crucial for steps.

  7. Freeze mechanism: The contract incorporates a capability to freeze tokens for a duration. While tokens are in a state they cannot be transferred or utilized. Generally freezing occurs when fraudulent activities are detected by the compliance contract and can only be "defrosted" by providing zk proof of ownership, from the respective token holder.

  8. AMMs: Automated Market Makers (AMMs) play a role in maintaining price stability and liquidity by ensuring there is always a buyer for tokens. (This is not integrated in the contract but accessible for every token)

  9. Access Control: Access Control measures guarantee that only the rightful asset holder can utilize their asset. This is made possible through a wallet provided by the Paddle Identity Management foundation, which incorporates biometric and zero knowledge based security.

  10. Listings: Token holders have the ability to list their property on a marketplace enabling them to offer it up for potential buyers

  11. Requests: By activating requests token holders can invite users to submit bids for their property.

  12. Tax Payments: The contract automates tax payments for transfers ensuring compliance with legal requirements.

  13. Lease Agreements: Landlords or tokenholders with " rights" have the capability to create lease agreements directly on chain. Tenants can then sign these agreements on chain using their proof from the Paddle wallet, which maintains zero knowledge privacy.

  14. Oracle Integration: Users are empowered to bring verified prices onto the blockchain through Oracle Integration. This serves to enhance the value of their assets.

  15. Transfers: Transfers between users are made seamless as they can directly send tokens to wallets, on the blockchain.

  16. Staking: Through staking mechanisms users can stake their tokens within an ERC20 minting contract. This contract mints ERC20 tokens backed by assets. Automatically stakes them. The staker receives interest payments directly.

  17. Financing: Users have the option to secure financing for real estate properties listed on the platform. To qualify for a loan they must possess collateral or have a trustworthy borrowing history within defined parameters. Additionally loans can be secured by using the property itself as backing.

  18. Compliance Contract: To ensure compliance and prevent activities an AI powered contract is in place.

  19. Escrow Contract: A contrcat that holds the assets until predefined conditions are met.


Base Code Structure

This is our basic design for a normal property contract.

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract RealEstateToken is ERC1155, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;

    uint256 constant MIN_TOKENS = 100;
    uint256 constant MAX_TOKENS = 10000;

    // Asset price state variable
    uint256 public asset_price;

    struct Property {
        uint256 propertyId;
        string reservedId;
        string addressInfo;
        uint8 floors;
        uint8 rooms;
        string flat;
        string city;
        string country;
        uint256 entrypointId;
        uint256 numOfTokens;
        uint256 lastApprovedValue;
        string realEstateType;
        uint256 tokenizationDate;
        string buildingYearIPFSHash;  // IPFS hash for "building year"
    }

    mapping(uint256 => Property) public properties;

    event PropertyTokenized(address indexed to, uint256 indexed tokenId, uint256 amount);

    constructor(string memory uri) ERC1155(uri) {}

    function mintProperty(Property memory newProperty, address to, uint256 amount) external onlyOwner {
        require(amount >= MIN_TOKENS && amount <= MAX_TOKENS, "Invalid token amount");

        properties[_tokenIdCounter.current()] = newProperty;

        for (uint256 i = 0; i < amount; i++) {
            _mint(to, _tokenIdCounter.current(), 1, "");
            _tokenIdCounter.increment();
        }

        emit PropertyTokenized(to, _tokenIdCounter.current(), amount);
    }

    function updateLastApprovedValue(uint256 tokenId, uint256 newValuation) external onlyOwner {
        properties[tokenId].lastApprovedValue = newValuation;
    }

    function setAssetPrice(uint256 _price) external onlyOwner {
        asset_price = _price;
    }

    /**
     * @dev Computes the price per token by dividing the asset_price by the number of tokens.
     */
    function _tokenPrice() internal view returns (uint256) {
        return asset_price / MAX_TOKENS;
    }

    function getTokenPrice(uint256 tokenId) external view returns (uint256) {
        require(properties[tokenId].propertyId != 0, "Token not found");
        return _tokenPrice();
    }
    
     function transferRealEstateToken(address to, uint256 erc1155TokenId, uint256 amount) public {
        require(to != address(0), "Cannot transfer to the zero address");
        require(balanceOf(msg.sender, erc1155TokenId) >= amount, "Insufficient token balance");
        
        safeTransferFrom(msg.sender, to, erc1155TokenId, amount, "");
    }

    // ... Additional functions for modifying properties, fetching property data, etc.
}

Residental Code Struct

When we have bigger real estate, like large complexes, towers with multiple apartments, etc, we have a main contract storing additional data and rights about the whole complex, users are then able to mint sub-contract which are normaly property contract but connected to the main conract and within its parameters. The main contracts for example holds the information about special and common property, or the DAO where all token holders of sub-contracts can participate.

pragma solidity ^0.8.0;

// Assuming the RealEstateToken contract is in the same file or imported accordingly.

contract RealEstateComplex is RealEstateToken {
    
    // Represents a property within a larger complex (e.g., an apartment in a building)
    struct ComplexProperty {
        Property mainProperty;
        Property[] subProperties;
    }

    // Mapping from tokenId to ComplexProperty
    mapping(uint256 => ComplexProperty) public complexProperties;

    // Event to be emitted when a new complex property is tokenized
    event ComplexPropertyTokenized(address indexed to, uint256 indexed tokenId, uint256 totalTokensMinted);

    /**
     * @dev Tokenizes a complex property.
     * @param newComplexProperty The complex property to be tokenized.
     * @param to The address to which the tokens representing the property will be assigned.
     */
    function mintComplexProperty(ComplexProperty memory newComplexProperty, address to) external onlyOwner {
        
        uint256 totalTokens = newComplexProperty.mainProperty.numOfTokens;

        // Save the main property first
        properties[_tokenIdCounter.current()] = newComplexProperty.mainProperty;
        _mint(to, _tokenIdCounter.current(), newComplexProperty.mainProperty.numOfTokens, "");
        _tokenIdCounter.increment();

        // Iterate through sub-properties and mint tokens accordingly
        for (uint256 i = 0; i < newComplexProperty.subProperties.length; i++) {
            properties[_tokenIdCounter.current()] = newComplexProperty.subProperties[i];
            _mint(to, _tokenIdCounter.current(), newComplexProperty.subProperties[i].numOfTokens, "");
            totalTokens += newComplexProperty.subProperties[i].numOfTokens;
            _tokenIdCounter.increment();
        }

        complexProperties[_tokenIdCounter.current()] = newComplexProperty;
        
        emit ComplexPropertyTokenized(to, _tokenIdCounter.current(), totalTokens);
    }

    /**
     * @dev Fetches details about a complex property, given its tokenId.
     * @param tokenId The tokenId of the complex property.
     * @return The complex property associated with the tokenId.
     */
    function getComplexProperty(uint256 tokenId) external view returns (ComplexProperty memory) {
        return complexProperties[tokenId];
    }

    // ... Additional functions specific to complexes
}

Compliance Contract

The compliance agreement is a smart contract that plays a significant role in automating its services incorporating artificial intelligence. Each asset class has its compliance contract, responsible, for monitoring all activities occurring within that particular class. In cases of behavior or other issues the compliance contract may even select random wallets to assign special passwords if it deems it necessary.

Artificial Intelligence (AI) Integration

The AI integration serves as the vigilant guardian, assessing patterns, predicting fraudulent behaviors, and optimizing compliance procedures.

Fraud Detection:

  • Function: Monitors for anomalies in transaction patterns.

  • Consideration: Compares transaction behaviors with historical data. Sudden, large transfers or rapid, multiple small transfers from a single wallet may trigger alerts.

Dynamic Watchlist Management:

  • Function: AI adds/removes addresses from the Active Watchlist based on behavior patterns.

  • Consideration: Regular large transactions from new wallets, inactive wallets becoming suddenly active, etc.

Random Security Verification:

  • Function: AI's predictive algorithms select wallets for random security checks based on perceived risk.

  • Consideration: Selection might be based on wallet behavior, balance, frequency of transactions, etc.

Formulas and Considerations:

  • If ΔTx > μ(Tx) + 2σ(Tx) then Trigger Alert. (μ(Tx) = Mean of past transactions; σ(Tx) = Standard deviation of transactions)

  • If Tx_Frequency(W) > 2*Avg_Tx_Frequency then Add to Watchlist. (W = Wallet)

  • P = f(B, Tx, Age, Anomalies) (P = Probability of wallet selection)

  • Exponential Moving Average (EMA): EMA t ​ =(Tx t ​ ×α)+(EMA t−1 ​ ×(1−α))

Where:

  • TxtTxt is the transaction amount at time t.

  • EMAtEMAt is the exponential moving average at time t.

  • αα is the smoothing factor, typically between 0 and 1.

Fraud can be flagged if: TxtEMAt>k×SD∣Txt−EMAt∣>k×SD∣

Where:

  • SDSD is the standard deviation of the transaction amounts in a defined period.

  • kk is a multiplier (often 2 or 3) indicating the sensitivity of the detection.

  • Time-Series Decomposition for Seasonal Anomalies:

    Real estate transactions may have seasonal components (e.g., more transactions during summer). By decomposing a time series into its trend, seasonal, and residual components, we can detect anomalies in each individually. For instance:

    Txt=Trendt+Seasonalt+ResidualtTxt=Trendt+Seasonalt+Residualt

    Anomalies can be detected if the residual component deviates significantly from zero.


Randomly Chosen Security Requests:

Random security requests should not only be random but also targeted based on risk.

Risk Weighting:

Define a risk score, RR, for each wallet as:

R=w1×Freq+w2×Baldiff+w3×TxvarR=w1×Freq+w2×Baldiff+w3×Txvar

Where:

  • FreqFreq is the transaction frequency.

  • BaldiffBaldiff is the change in balance over a defined period.

  • TxvarTxvar is the variance in transaction amounts.

  • w1,w2,w3w1,w2,w3 are weights defining the importance of each factor.

The probability PP of a wallet being selected for a random security check is:

P(W)=R(W)all WR(W)P(W) = \frac{R(W)}{\sum_{\text{all } W}R(W)}


Entropy-based Sampling:

Wallets can also be sampled based on the entropy of their behavior:

H(W)=P(x)×log(P(x)) H(W) = -\sum P(x) \times \log(P(x))

Where:

  • P(x)P(x) is the probability of behavior xx

    for the wallet.

Higher entropy indicates more unpredictable behavior. Wallets with higher entropy values can be sampled more frequently for security checks.


Graph Theory

Definition: Graph theory is a field of mathematics that studies networks of interconnected nodes and edges.

Parameters:

  • VV: Set of vertices or nodes.

  • EE: Set of edges.

Mathematical Representation: A graph GG can be represented as G=(V,E)G=(V,E).

Formulas: Degree of a vertex: deg(v)=uVauvdeg(v)=∑u∈Vauv Where auvauv is an entry in the adjacency matrix.

Inputs:

  • Set of nodes.

  • Set of edges connecting nodes.

Outputs:

  • Graph structure.

  • Properties like degree of nodes, connectivity, etc.

Explanation: Graph theory provides tools for analyzing networks in various domains. It can be used to study social networks, computer networks, and many other types of networks.


Autoregressive Integrated Moving Average (ARIMA)

Definition: ARIMA is a time series forecasting method that combines autoregression, differencing, and a moving average model.

Parameters:

  • pp: Number of lag observations included (lag order).

  • dd: Number of times raw observations are differenced (degree of differencing).

  • qq: Size of the moving average window.

Formulas: (1ϕB)(1B)dYt=(1+θB)ϵt(1−ϕB)(1−B)dYt=(1+θB)ϵt

Where:

  • BB is the backshift operator.

  • ϵtϵt is white noise.

Inputs:

  • Time series data.

Outputs:

  • Forecasted values.

Explanation: ARIMA models are applied to time series data to forecast future points. The model captures various patterns in the time series data and uses them for forecasting.


Clustering for Anomaly Detection

Definition: Clustering is an unsupervised machine learning technique used to group similar data points together. For anomaly detection, data points that don't belong to any cluster are considered anomalies.

Parameters:

  • kk: Number of clusters.

  • XX: Data points.

Formulas: Objective function for K-means clustering: J=i=1nj=1kzijxiCj2J=∑i=1n∑j=1kzij∣∣xi−Cj∣∣2 Where zijzij is 1 if xixi is assigned to cluster jj and 0 otherwise.

Inputs:

  • Set of data points.

Outputs:

  • Cluster assignments for each data point.

  • Centroids of clusters.

Explanation: Clustering groups similar data points together. In anomaly detection, points that are far from any cluster centroid are considered anomalies.


Bayesian Networks

Definition: A Bayesian network is a probabilistic graphical model that represents a set of variables and their conditional dependencies.

Parameters:

  • XX: Random variables.

  • PP: Conditional probabilities.

Formulas: Using Bayes' theorem: P(AB)=P(BA)×P(A)P(B)P(A∣B)=P(B∣A)×P(A)P(B)

Inputs:

  • Set of random variables.

  • Conditional probability tables.

Outputs:

  • Probabilities of various events.

Explanation: Bayesian networks are used to model uncertain systems and reason about them probabilistically.


Neural Networks

Definition: Neural networks are a set of algorithms, modeled loosely after the human brain, designed to recognize patterns.

Parameters:

  • WW: Weights.

  • bb: Biases.

  • ff: Activation function.

Formulas: Output of a neuron: y=f(WX+b)y=f(W⋅X+b) Where ff is an activation function like sigmoid, ReLU, etc.

Inputs:

  • Input data.

  • Network architecture (number of layers, neurons).

Outputs:

  • Predicted output.

Explanation: Neural networks are used for various tasks like classification, regression, and more. They can learn complex patterns from data.


On-Chain Costs

Name Price Times

Minting fee

one stablecoin

1

Storage fee

dependent on metadata amount

1

Transaction fee

/

Every transaction


Interoperability

After completing the beta version our next step is to focus on ensuring compatibility with the Ethereum blockchain. This will provide users with autonomy and a wider range of opportunities. Our objective is to integrate our contract and token structure, onto the Ethereum blockchain ensuring that all functionalities are fully operational.


On- and Off-Chain Data Storage

The ERC 1155 standard, also known as the " Token Standard" enables the creation of both interchangeable and unique tokens within a single contract. This standard is particularly beneficial for real estate assets and their related information as it offers a solution. However storing amounts of data on the blockchain is not cost effective or scalable. To address this we propose a solution that combines on chain storage using zk starks to store data within our dynamic cell based DAG structure along with, off chain storage utilizing IPFS.

On-Chain Data with ERC-1155:

What is stored on-chain?

  • Token Metadata: Essential token attributes such as token IDs, ownership details, and quantities.

  • IPFS Hashes: Unique content-addressable identifiers pointing to detailed asset data stored off-chain on IPFS.

  • Unit Management Data: Information about units (e.g., "rentalRights") and their association with specific token IDs.

Technical Interactions:

  • mintToken: Function to mint new tokens. It accepts parameters like to (recipient address), tokenId, and metadataURI (IPFS hash).

  • transferToken: Enables token transfers, updating on-chain ownership records.

  • associateUnit: Links a token ID with a specific unit, storing this association on-chain.

Off-Chain Data with IPFS:

What is stored off-chain on IPFS?

  • Asset Details: Comprehensive real estate data, including images, videos, 3D models, floor plans, and historical data.

  • Digital Documents: Lease agreements, property deeds, and other legal documents.

  • Transaction Histories: Detailed logs of past transactions, offers, and bids for each property.

Technical Interactions:

  1. Data Serialization: Real estate data is serialized, typically into JSON format, preparing it for IPFS storage.

  2. IPFS Storage: Serialized data is added to IPFS, returning a unique hash. This process involves:

    • ipfs add serializedData.json: Adds the data file to IPFS.

    • The command returns a unique hash (e.g., QmWx9...) representing the data's IPFS address.

  3. Hash Linking: The unique IPFS hash is then linked to the corresponding token ID within the ERC-1155 contract.

Bridging ERC-1155 with IPFS:

Smart Contract Integration:

  • storeIPFSHash: A function within the ERC-1155 contract that associates a token ID with its corresponding IPFS hash.

    function storeIPFSHash(uint256 tokenId, string memory ipfsHash) public onlyOwner {
        tokenIPFSHash[tokenId] = ipfsHash;
        emit IPFSHashStored(tokenId, ipfsHash);
    }
  • fetchIPFSHash: Allows users to retrieve the IPFS hash for a specific token ID, facilitating off-chain data access.

    function fetchIPFSHash(uint256 tokenId) public view returns (string memory) {
        return tokenIPFSHash[tokenId];
    }

Data Retrieval Process:

  1. Interact with the ERC-1155 contract to fetch the IPFS hash for a specific token ID.

  2. Use the retrieved IPFS hash to access the detailed off-chain data from IPFS.

Security Considerations:

  • Immutable Links: Once an IPFS hash is stored on-chain, it provides an immutable link to the off-chain data, ensuring data consistency.

  • Data Integrity: IPFS inherently ensures data integrity through its content-addressable storage mechanism. Any alteration in off-chain data results in a changed hash, making tampering evident.


Audit Contract:

The AuditContract aims to act as a decentralized land register, where properties are represented by tokens. Token holders can set a fee for external parties to access a zero-knowledge proof of their property. This proof enables non-token holders to verify the existence of the property and view some metadata about it.

Key Features:

  1. Token holders can register their properties.

  2. The access fee is set by the token holder but within a range defined by the contract.

  3. External parties can pay the fee to get a zk-proof of the property, which can then be verified.

  4. Fee distribution mechanism among the token holders.

Contract Structure:

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract AuditContract is Ownable {
    IERC20 public stablecoin;

    mapping(uint256 => Property) public properties;
    address[] public propertyOwners;
    
    struct Property {
        address owner;
        string metadataURI;
        uint256 accessFee;
        Status status;
    }

    enum Status {
        InUse,
        Freezed,
        Available,
        ForSale
    }

    constructor(address _stablecoin) {
        stablecoin = IERC20(_stablecoin);
    }

    function registerProperty(uint256 tokenId, string memory metadataURI, uint256 accessFee, Status _status) public onlyOwner {
        require(accessFee > 0, "Access fee should be positive");

        properties[tokenId] = Property({
            owner: msg.sender,
            metadataURI: metadataURI,
            accessFee: accessFee,
            status: _status
        });

        propertyOwners.push(msg.sender);
    }

    function accessPropertyData(uint256 tokenId) public {
        Property memory property = properties[tokenId];
        
        require(stablecoin.transferFrom(msg.sender, address(this), property.accessFee), "Transfer of access fee failed");
        
        distributeFees(tokenId);
        
        // The actual zk-proof generation/validation would be off-chain, 
        // but the user can now access the metadataURI for that process.
    }

    function distributeFees(uint256 tokenId) internal {
        uint256 feePerOwner = properties[tokenId].accessFee / propertyOwners.length;
        
        for(uint256 i = 0; i < propertyOwners.length; i++) {
            stablecoin.transfer(propertyOwners[i], feePerOwner);
        }
    }

    function addRealEstateContract(address contractAddress) public onlyOwner {
        // Interactions with other "RealEstateContracts" can be implemented here
    }
}

This code covers:

  1. ZK-Proofs Off-chain: We assume the zk-proof generation/validation would be done off-chain, but upon successful payment, the user can access the metadataURI to proceed with that process.

  2. Fee Distribution: When a user pays to access a property's data, the fees are equally distributed among all registered property owners. You can modify the distribution logic as needed.

  3. Interactions with RealEstateContracts: There's a placeholder function addRealEstateContract. You can expand on this to integrate the audit contract with other RealEstateContracts or token-based contracts.


Role-Based Access Control

1. What is Role-Based Access Control (RBAC)?

RBAC is an access management approach that restricts system access to authorized users based on predefined roles. Instead of assigning permissions directly to individual users, permissions are grouped by roles. Users are then assigned roles, granting them all the permissions that come with each role.

2. Why RBAC in RealEstateToken System?

The world of real estate involves multiple stakeholders, each with distinct responsibilities and rights. By integrating RBAC, we provide a structured and scalable way to represent these different participants and their associated permissions.

3. Defined Roles in the System:

  • Property Owner: Represents the owner of a property.

    • Permissions: Tokenize, list, delist properties, and receive payment.

  • Tenant: Represents an individual or entity looking to rent a property.

    • Permissions: Rent properties, sign digital lease agreements, and make rent payments.

  • Auditor: Ensures the platform's integrity and compliance.

    • Permissions: Access property data, verify property details, and generate compliance reports.

  • Regulator: Provides oversight to the platform's operations.

    • Permissions: Monitor transactions, request audit trails, and flag suspicious activities.

  • Marketplace Admin: Manages the overall platform operations.

    • Permissions: Manage listings, resolve disputes, oversee transactions, and ensure quality standards.

4. Technical Implementation:

We leverage the power of the OpenZeppelin library for our RBAC implementation. Here’s a simplified outline:

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/AccessControl.sol";

contract RealEstateToken is AccessControl {
    bytes32 public constant PROPERTY_OWNER_ROLE = keccak256("PROPERTY_OWNER");
    bytes32 public constant TENANT_ROLE = keccak256("TENANT");
    bytes32 public constant AUDITOR_ROLE = keccak256("AUDITOR");
    //... Additional roles as required

    constructor() {
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);  // Admin manages role assignments.
    }

    function tokenizeProperty(string memory propertyDetails) public {
        require(hasRole(PROPERTY_OWNER_ROLE, msg.sender), "Access Denied: Only property owners can tokenize properties.");
        //... Tokenization logic
    }

    //... Additional role-based functions for tenants, auditors, etc.
}

With this setup, when a user with the role PROPERTY_OWNER tries to tokenize a property, our system checks the role and permits the action accordingly.

5. Advantages of our RBAC Implementation:

  • Scalability: New roles and permissions can be seamlessly integrated as our platform evolves.

  • Flexibility: Admins can effortlessly assign or revoke roles, streamlining access management.

  • Security: RBAC minimizes risk by ensuring users can only execute actions within their designated roles.

  • Efficiency: Admin overhead is significantly reduced by managing roles rather than individual user permissions.

Contract Implementation

Utilizing the role-based access system, we have introduced a rental right mechanism, wherein certain token holders are granted the privilege to receive a portion of the rental payments made to the property. Payments are sent directly to the smart contract in Ethereum, and are immediately redistributed amongst these token holders based on their respective ownership stakes.

To ensure fairness and transparency, each token is associated with a specific rental share, which determines the percentage of the received rent it's entitled to. Upon receipt of rental payment, the contract automatically calculates the total rental share and distributes the payment to the entitled token holders accordingly.

Solidity Code:

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract RealEstateToken is ERC1155, AccessControl {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;

    // Define roles
    bytes32 public constant RENT_RECEIVER_ROLE = keccak256("RENT_RECEIVER_ROLE");

    // Define the constants for minimum and maximum tokens.
    uint256 constant MIN_TOKENS = 100;
    uint256 constant MAX_TOKENS = 10000;

    struct Property {
        uint256 propertyId;
        // ... other fields ...
        uint256 numOfTokens;
        uint256 lastApprovedValue;
        // ... more fields ...
    }

    // Mapping to link token to its property.
    mapping(uint256 => Property) public properties;
    // Mapping to keep track of each token's share of rent based on ownership
    mapping(uint256 => uint256) public tokenRentShares;

    event PropertyTokenized(address indexed to, uint256 indexed tokenId, uint256 amount);
    event RentReceived(address indexed sender, uint256 amount);
    event RentDistributed(address indexed receiver, uint256 tokenId, uint256 amount);

    constructor(string memory uri) ERC1155(uri) {
        // Granting the contract deployer the default admin role
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }

    function mintProperty(Property memory newProperty, address to, uint256 amount) external {
        require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Not authorized");
        // ... minting logic ...

        // Assigning rent receiver role to the minted tokens
        _setupRole(RENT_RECEIVER_ROLE, to);
    }

    // Function to receive rent payments
    receive() external payable {
        emit RentReceived(msg.sender, msg.value);

        // Calculate the total rent share
        uint256 totalRentShares = _calculateTotalRentShares();

        // Loop through all tokens and distribute rent based on share
        for (uint256 i = 0; i < _tokenIdCounter.current(); i++) {
            if (hasRole(RENT_RECEIVER_ROLE, ownerOf(i))) {
                uint256 rentShare = (msg.value * tokenRentShares[i]) / totalRentShares;
                payable(ownerOf(i)).transfer(rentShare);
                emit RentDistributed(ownerOf(i), i, rentShare);
            }
        }
    }

    function _calculateTotalRentShares() private view returns (uint256) {
        uint256 totalRentShares = 0;
        for (uint256 i = 0; i < _tokenIdCounter.current(); i++) {
            totalRentShares += tokenRentShares[i];
        }
        return totalRentShares;
    }

    // ... other functions ...
}

DAO Mechanism

Incorporated within our contract, the DAO mechanism upholds the principle that each token holder can actively participate in the governance of the token ecosystem. This collective approach facilitates key decisions for managing real estate tokens within a decentralized framework, ensuring that every stakeholder has a voice.

Contract Structure:

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";

contract DAOMechanism is Ownable {
    struct Proposal {
        uint256 id;
        string description;
        uint256 forVotes;
        uint256 againstVotes;
        mapping(address => uint256) votesByAddress;  // Changed from boolean to track the weight
    }

    IERC1155 public tokenContract;
    mapping(uint256 => Proposal) public proposals;
    uint256 public proposalCount = 0;

    uint256 public quorum = 50;  // 50% of total token supply should participate
    uint256 public votingDuration = 7 days;

    mapping(uint256 => uint256) public proposalEndTime;  // End time of each proposal

    event ProposalCreated(uint256 proposalId, string description);
    event Voted(uint256 proposalId, address voter, bool vote, uint256 weight);

    constructor(address _tokenContract) {
        tokenContract = IERC1155(_tokenContract);
    }

    function proposeChange(string memory proposalDescription) public {
        require(tokenContract.balanceOf(msg.sender, 1) > 0, "Must be a token holder to propose");

        proposals[proposalCount] = Proposal({
            id: proposalCount,
            description: proposalDescription,
            forVotes: 0,
            againstVotes: 0
        });

        proposalEndTime[proposalCount] = block.timestamp + votingDuration;
        emit ProposalCreated(proposalCount, proposalDescription);
        proposalCount++;
    }

    function voteOnProposal(uint256 proposalId, bool vote) public {
        require(tokenContract.balanceOf(msg.sender, 1) > 0, "Must be a token holder to vote");
        require(proposalEndTime[proposalId] > block.timestamp, "Voting duration ended");

        Proposal storage proposal = proposals[proposalId];
        require(proposal.votesByAddress[msg.sender] == 0, "Already voted on this proposal");

        uint256 weight = tokenContract.balanceOf(msg.sender, 1);
        if (vote) {
            proposal.forVotes += weight;
        } else {
            proposal.againstVotes += weight;
        }

        proposal.votesByAddress[msg.sender] = weight;

        emit Voted(proposalId, msg.sender, vote, weight);
    }

    function isProposalPassed(uint256 proposalId) public view returns(bool) {
        Proposal storage proposal = proposals[proposalId];

        uint256 totalTokens = tokenContract.totalSupply(1);
        uint256 totalVotes = proposal.forVotes + proposal.againstVotes;
        
        if(totalVotes < (quorum * totalTokens) / 100) {
            return false;  // Quorum not met
        }

        return proposal.forVotes >= ((2 * totalVotes) / 3 + 1); // Check 2/3+1% majority
    }
}

Function Descriptions:

  1. proposeChange:

    • Purpose: Allows token holders to suggest modifications or new functionalities.

    • Inputs: proposalDescription: Description of the proposed change.

    • Outputs: Emits ProposalCreated event.

    • Internal Operations: Verifies token ownership, initializes a new proposal, sets the proposal's end time, and increments the proposal counter.

  2. voteOnProposal:

    • Purpose: Token holders cast their votes on specific proposals.

    • Inputs: proposalId and vote.

    • Outputs: Emits Voted event, capturing the vote's weight.

    • Internal Operations: Confirms token ownership, ensures the voting window is open, checks if the voter has already voted, and then registers the vote based on its weight.

  3. isProposalPassed:

    • Purpose: Checks if a proposal has passed after the voting period.

    • Inputs: proposalId.

    • Outputs: Returns a boolean indicating the proposal's status.

    • Internal Operations: Checks if the quorum was achieved and if a 2/3+1% majority voted in favor.

Voting Weight:

Directly linked to token holdings; the more tokens a holder has, the more weight their vote carries.

Proposal Validation:

To propose a change, a user must hold at least one token.

Voting Outcome:

After the set voting duration, the contract determines the outcome based on the weighted votes.

Quorum Check:

It's crucial to ensure that at least 50% of total token holders participated in the vote.

Process Flow:

  1. Token holder proposes a change if they meet the ownership threshold.

  2. The proposal remains open for voting for the stipulated duration.

  3. Token holders cast weighted votes.

  4. After the voting duration, the contract evaluates the outcome.

  5. If the proposal garners a 2/3+1% majority and meets the quorum, it's approved.

Considerations:

  • Token Holder Restriction: Only token holders can propose and vote.

  • One Vote Per Holder: A token holder votes once per proposal, with their vote's weight being equivalent to their token ownership.

  • Dynamic Proposal Creation: Any emergent issues or suggestions can be proposed and subsequently voted on by the community.


Burn Contract with ERC721 Confirmation

Data Flow: Token Burning Procedure:

  1. User initiates the burn mechanism, defining both the quantity and specific ID of the ERC1155 tokens designated for destruction.

  2. The specified ERC1155 tokens undergo removal from the user's possession.

  3. Subsequently, an ERC721 token gets minted, bearing the proof-of-burn, and then gets allocated to the user's address.

Proof-of-Burn via ERC721:

  1. The minted ERC721 token encompasses a metadata payload that encapsulates the entirety of the burn event. This includes:

    • The exact quantity of the ERC1155 tokens annihilated.

    • The precise ID attached to the ERC1155 tokens.

    • A timestamp marking the event.

    • The initiator's Ethereum address.

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract EnhancedBurnContract is ERC721Enumerable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    IERC1155 public erc1155Token;

    // Metadata URI for ERC721 token
    mapping(uint256 => string) private _tokenURIs;

    constructor(address _erc1155Token) ERC721("ProofOfBurnToken", "PBURN") {
        erc1155Token = IERC1155(_erc1155Token);
    }

    function burnTokensAndGetProof(uint256 erc1155TokenId, uint256 amount) public returns (uint256) {
        erc1155Token.burn(msg.sender, erc1155TokenId, amount);
        
        _tokenIds.increment();
        uint256 newTokenId = _tokenIds.current();
        _mint(msg.sender, newTokenId);
        _setTokenURI(newTokenId, generateMetadataURI(erc1155TokenId, amount));

        return newTokenId;
    }

    function generateMetadataURI(uint256 erc1155TokenId, uint256 amount) internal view returns (string memory) {
        // This is a placeholder. In a real-world scenario, the function would generate or fetch a URI, possibly pointing to IPFS or another decentralized storage solution, containing the relevant metadata.
        return string(abi.encodePacked("https://tokenmetadata.domain/path/", toString(erc1155TokenId), "_", toString(amount)));
    }

    function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
        _tokenURIs[tokenId] = _tokenURI;
    }

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        return _tokenURIs[tokenId];
    }

    function toString(uint256 value) internal pure returns (string memory) {
        // Convert uint256 to string
        return ...;  // Placeholder
    }
}

Functions:

burnTokensAndGetProof:

  • Objective: Authorize users to eradicate their ERC1155 tokens and subsequently obtain an ERC721 token as a proof-of-burn.

  • Input Parameters:

    1. erc1155TokenId: ERC1155 token's distinctive identifier that's set for burn.

    2. amount: Quantity of ERC1155 tokens marked for annihilation.

  • Output: Deploys an ERC721 token and registers it under the user's address.

  • Internal Actions: The function begins by torching the specified ERC1155 tokens from the user's stash. It then mints a unique ERC721 token, links it with a distinct URI representing the burn details, and finally allocates it to the user.


Real Estate Prices

The price of a property can be verified by an official approval which is brought on-chain through our oracle structure, in transfers, the property takes the last approved value, when listing an object, the user can determine his own price. A token which is handled within the AMM, increased and decreases its value based on the supply and demand and balanced bonding curves.

We introduce our own bonding curve mechanism which offers non fluctuative, real time price feeds based on the real demand. Read more about it here.


Tax Payments

The Tax Payments feature guarantees that each token transfer adheres to regulations, by automating the process of tax payment. Whenever a token is transferred, during a sale a tax is applied to the transaction. The asset linked to the token is securely held in an escrow contract until the tax is settled ensuring that the system maintains its compliance and transparency.

Data Flow:

  1. Token Transfer Initiation:

    • A user initiates a token transfer, which could be due to a sale or any other reason.

    • The associated asset is moved to an escrow contract, awaiting tax payment.

  2. Tax Calculation:

    • The tax due is calculated based on the transaction value. For now, the tax rate is set at 3.5%.

  3. Tax Payment:

    • The tax amount is deducted from the proceeds of the sale or needs to be paid by the user.

    • Once the tax is paid, the asset is released from the escrow contract.

Contract Structure

pragma solidity ^0.8.0;

contract TaxPayments {
    // Tax rate set at 3.5%
    uint256 public constant TAX_RATE = 35; // Represented in tenths of a percent (i.e., 3.5% = 35 tenths of a percent)
    uint256 public constant BASIS_POINTS = 1000; // Represents the basis points for tax calculation

    event TaxPaid(address indexed payer, uint256 tokenId, uint256 taxAmount);

    function payTax(uint256 tokenId, uint256 saleAmount) public returns (uint256) {
        uint256 taxAmount = (saleAmount * TAX_RATE) / BASIS_POINTS;
        require(msg.value >= taxAmount, "Insufficient funds sent");

        // Transfer the taxAmount to the tax authority's address (this can be set up in the contract)
        // address(taxAuthority).transfer(taxAmount);

        emit TaxPaid(msg.sender, tokenId, taxAmount);
        return taxAmount;
    }
}

Function Descriptions:

payTax:

  • Purpose: Handles the tax payment process for a token transfer.

  • Inputs:

    • tokenId: The ID of the token being transferred.

    • saleAmount: The amount for which the token is being sold or transferred.

  • Outputs: Emits a TaxPaid event detailing who paid the tax, for which token, and the tax amount.

  • Internal Operations: The function calculates the tax due based on the saleAmount and the predefined TAX_RATE. It then checks if the user has sent enough funds to cover the tax. Once verified, the tax amount can be transferred to a designated tax authority's address (this step is commented out in the example for clarity).

Mathematical and Logical Considerations:

  1. Tax Calculation: The tax is calculated as a percentage of the sale amount. The formula used is:

    taxAmount=saleAmount×TAX_RATEBASIS_POINTS\text{taxAmount} = \frac{\text{saleAmount} \times \text{TAX\_RATE}}{\text{BASIS\_POINTS}}

    Here, BASIS_POINTS is used to represent the percentage in a way that avoids floating-point arithmetic in Solidity.

  2. Escrow Integration: While not shown in the provided code, the integration with the escrow contract is crucial. The escrow contract would listen for the TaxPaid event and release the asset once the tax is paid.

  3. Flexibility: The tax rate, while constant in this example, can be made variable to adapt to changing legal requirements or different tax rates for various types of transactions.

Abandonment Protocol

Overview

The "Abandonment Protocol" addresses a crucial aspect of digital inheritance, providing a seamless and secure method to transfer token ownership in the event of the death of a token holder. Considering the permanence of blockchain transactions and the immutability of smart contracts, handling such scenarios with forethought is vital.

This documentation provides two primary solutions, both of which can be integrated with ERC1155 contracts:

  1. Automated Transfer upon Oracle Verification: This method relies on a trusted oracle structure to verify a token holder's death, followed by an automatic token transfer to a pre-specified recipient.

  2. Multi-Signature Escrow with Legal Oversight: This method requires legal procedures to verify the token holder's death. Once confirmed, a multi-signature wallet governed by trusted parties will facilitate the token transfer.

1. Automated Transfer upon Oracle Verification

Implementation:

a. Pre-Specification of Recipient: Every token holder should be allowed to pre-specify a recipient for their tokens. This can be done through a function where they mention a particular Ethereum address to which the tokens would be transferred.

mapping(address => address) public beneficiary;
    
function setBeneficiary(address _beneficiary) external {
    beneficiary[msg.sender] = _beneficiary;
}

b. Oracle Verification: Your system should have a trusted oracle that verifies real-world data. This oracle will also check and verify the death of a token holder.

address oracleAddress;  // Trusted oracle's address

modifier onlyOracle() {
    require(msg.sender == oracleAddress, "Only oracle can call this function");
    _;
}

function verifyDeathAndTransfer(address deceased) external onlyOracle {
    address recipient = beneficiary[deceased];
    require(recipient != address(0), "No beneficiary set");

    // Logic for token transfer from 'deceased' to 'recipient' for all tokens owned by the deceased.
}

Pros:

  • Automatic and quick.

  • Reduces the need for legal intervention in token transfer.

Cons:

  • Relies heavily on the accuracy and integrity of the oracle.

  • May lead to unintended transfers if there are false positives.

Implementation:

a. Multi-Signature Wallet Setup: Set up a multi-signature wallet where tokens are temporarily held. Trusted parties (like notaries, legal representatives, or oracle parties) are the signatories.

b. Transfer to Multi-Signature Wallet upon Death Verification:

Upon receiving information about the death of a token holder, the tokens are transferred to the multi-sig wallet, waiting for further legal procedures.

c. Legal Verification:

Post the legal procedures; once the death is confirmed, the trusted parties will co-sign a transaction from the multi-sig wallet to the beneficiary address.

Pros:

  • Provides a layer of legal oversight.

  • Reduces risks of unintended transfers.

Cons:

  • Can be time-consuming due to legal procedures.

  • Relies on multiple parties to act in good faith.

Last updated

Logo