Virtual Real Estate Marketplace Development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Virtual Real Estate Marketplace Development
Complex
from 1 week to 3 months
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Development of Virtual Real Estate Marketplace

Virtual real estate is an NFT representing coordinates or a plot in digital space. Decentraland LAND, Sandbox LAND, Otherside Otherdeed are the most well-known. Each has a marketplace for secondary trading, rental, and development. Development of a custom virtual real estate marketplace is an intersection of NFT marketplace infrastructure, on-chain rental mechanics, and spatial data management.

Technical specifics compared to a generic NFT marketplace: parcels have coordinates (x, y), possible adjacency and adjacency bonuses, rental is temporary with rights return, development creates a metadata relationship between LAND NFT and content NFT.

LAND NFT: Data Specifics

Coordinate system on-chain

Each parcel is an NFT with coordinates in a grid. Standard approach: tokenId encodes coordinates.

contract VirtualLand is ERC721 {
    struct Parcel {
        int256 x;
        int256 y;
        address tenant;         // current renter (if rented)
        uint256 leaseExpiry;    // timestamp of lease end
        string contentURI;      // what is built on the parcel
        uint8 zoneType;         // 0=residential, 1=commercial, 2=plaza
    }

    mapping(uint256 => Parcel) public parcels;
    mapping(int256 => mapping(int256 => uint256)) public coordToTokenId;
    // coordToTokenId[x][y] = tokenId

    int256 public constant GRID_MIN = -150;
    int256 public constant GRID_MAX = 150;

    // tokenId = unique index from coordinates
    function coordsToTokenId(int256 x, int256 y) public pure returns (uint256) {
        // Shift to non-negative values
        uint256 ux = uint256(x - GRID_MIN);
        uint256 uy = uint256(y - GRID_MIN);
        uint256 size = uint256(GRID_MAX - GRID_MIN + 1);
        return ux * size + uy;
    }

    function tokenIdToCoords(uint256 tokenId) public pure returns (int256 x, int256 y) {
        uint256 size = uint256(GRID_MAX - GRID_MIN + 1);
        x = int256(tokenId / size) + GRID_MIN;
        y = int256(tokenId % size) + GRID_MIN;
    }
}

Estate: Combined Parcels

Estate = multiple adjacent parcels combined into one asset. This is significant: a large developed plot is worth more than the sum of its parts. Mechanics:

contract EstateRegistry is ERC721 {
    struct Estate {
        uint256[] parcels;      // array of tokenId of included parcels
        address landContract;
    }

    mapping(uint256 => Estate) public estates;
    // parcel → estate (if it's part of an estate)
    mapping(uint256 => uint256) public parcelToEstate;

    function createEstate(uint256[] calldata parcelIds) external returns (uint256 estateId) {
        // Check adjacency
        require(_areAdjacent(parcelIds), "Parcels must be adjacent");
        // Check ownership of all parcels
        for (uint i = 0; i < parcelIds.length; i++) {
            require(landNft.ownerOf(parcelIds[i]) == msg.sender, "Not owner");
        }

        estateId = ++_estateIdCounter;
        // Transfer parcels to escrow of this contract
        for (uint i = 0; i < parcelIds.length; i++) {
            landNft.transferFrom(msg.sender, address(this), parcelIds[i]);
            parcelToEstate[parcelIds[i]] = estateId;
        }

        estates[estateId] = Estate({ parcels: parcelIds, landContract: address(landNft) });
        _mint(msg.sender, estateId);
    }
}

On-chain Rental (Rental Protocol)

Rental of virtual real estate is a significant use case: the owner holds LAND as an investment, the renter uses it for development/events. Key question: how to divide ownership (NFT with owner) and usage rights (with renter)?

ERC-4907: Rentable NFT Standard

ERC-4907 adds a user role to ERC-721 — a temporary user with an expiry timestamp. Contracts can check userOf(tokenId) instead of ownerOf for access.

contract RentalMarketplace {
    struct RentalOffer {
        uint256 tokenId;
        address landContract;
        uint256 pricePerDay;
        uint256 minDays;
        uint256 maxDays;
        address paymentToken;    // ERC-20 or address(0) for native
        bool active;
    }

    mapping(bytes32 => RentalOffer) public rentalOffers;

    function createRentalOffer(
        uint256 tokenId,
        address landContract,
        uint256 pricePerDay,
        uint256 minDays,
        uint256 maxDays,
        address paymentToken
    ) external {
        require(IERC721(landContract).ownerOf(tokenId) == msg.sender, "Not owner");

        bytes32 offerId = keccak256(abi.encode(tokenId, landContract, msg.sender, block.timestamp));
        rentalOffers[offerId] = RentalOffer({
            tokenId: tokenId,
            landContract: landContract,
            pricePerDay: pricePerDay,
            minDays: minDays,
            maxDays: maxDays,
            paymentToken: paymentToken,
            active: true
        });
    }

    function rent(bytes32 offerId, uint256 days) external payable {
        RentalOffer storage offer = rentalOffers[offerId];
        require(offer.active, "Offer not active");
        require(days >= offer.minDays && days <= offer.maxDays, "Invalid duration");

        uint256 totalCost = offer.pricePerDay * days;
        uint256 expiry = block.timestamp + days * 1 days;

        // Payment
        if (offer.paymentToken == address(0)) {
            require(msg.value >= totalCost, "Insufficient payment");
        } else {
            IERC20(offer.paymentToken).safeTransferFrom(msg.sender, address(this), totalCost);
        }

        // Set user via ERC-4907
        IERC4907(offer.landContract).setUser(offer.tokenId, msg.sender, uint64(expiry));

        // Pay owner (minus protocol fee)
        uint256 fee = totalCost * PROTOCOL_FEE_BPS / 10000;
        _transferPayment(offer.paymentToken, IERC721(offer.landContract).ownerOf(offer.tokenId), totalCost - fee);

        emit Rented(offerId, msg.sender, days, expiry);
    }
}

Collateral Rental (without ERC-4907)

If LAND contract doesn't support ERC-4907: temporary NFT transfer with collateral. Renter deposits collateral (equal to or greater than LAND value), NFT is transferred, at the end — automatic return via keeper or manual claim.

Problem: landlord loses physical ownership of NFT during rental (though has the right to return it). Risk: renter sells NFT despite collateral. Solution: NFT is transferred to escrow contract, not the renter.

Marketplace Mechanics

Listing and Auctions

enum SaleType { FIXED_PRICE, ENGLISH_AUCTION, DUTCH_AUCTION }

struct Listing {
    uint256 tokenId;
    address seller;
    SaleType saleType;
    address paymentToken;
    uint256 startPrice;
    uint256 endPrice;          // for Dutch auction: final price
    uint256 startTime;
    uint256 endTime;
    uint256 highestBid;        // for English auction
    address highestBidder;
}

Dutch Auction is particularly relevant for primary sale of LAND: price starts high, automatically decreases to reserve. Eliminates gas wars on mint.

English Auction for secondary market of rare Estates: bidding with outbid protection (minimum bid increase by X%).

Royalties and Fee Structure

ERC-2981 for on-chain royalties. Standard structure for virtual real estate marketplace:

Fee Recipient Amount
Marketplace fee Protocol treasury 2-2.5%
Creator royalty Original metaverse creator 2.5-5%
Referral If referral program exists 0.5-1%
Seller LAND owner Remainder

Royalties for virtual real estate — controversial topic. Platforms like Blur undermined enforcement. Solution: royalty enforcement via contract (independent of marketplace), or royalty-free model with different type of revenue sharing.

Adjacency Premium and Bundle Pricing

Unique feature of land marketplaces: adjacent parcels are worth more together than separately. Adjacency search algorithm:

function findAdjacentParcels(parcels: Parcel[], targetParcel: Parcel): Parcel[] {
    const adjacent: Parcel[] = []
    const directions = [[-1,0],[1,0],[0,-1],[0,1],[-1,-1],[1,1],[-1,1],[1,-1]]

    for (const parcel of parcels) {
        for (const [dx, dy] of directions) {
            if (parcel.x === targetParcel.x + dx && parcel.y === targetParcel.y + dy) {
                adjacent.push(parcel)
                break
            }
        }
    }
    return adjacent
}

Frontend displays highlighted adjacent plots on the map on hover over one — users see potential bundle purchases.

Spatial Data and Map Interface

Interactive map — the main UI of the marketplace. Requirements: display thousands of parcels with color coding (for sale, for rent, occupied), smooth zoom/pan, click on parcel → detailed information.

Mapbox GL JS / deck.gl — most performant options for spatial rendering of thousands of objects. deck.gl (Uber) optimized for geodata and works with WebGL.

import { DeckGL } from '@deck.gl/react'
import { ScatterplotLayer } from '@deck.gl/layers'

const parcelLayer = new ScatterplotLayer({
    data: parcels,
    getPosition: (d) => [d.x * PARCEL_SIZE, d.y * PARCEL_SIZE, 0],
    getFillColor: (d) => {
        if (d.forSale) return [0, 200, 100]       // green — for sale
        if (d.forRent) return [0, 100, 200]        // blue — for rent
        if (d.hasContent) return [150, 100, 200]   // purple — developed
        return [100, 100, 100]                     // gray — empty
    },
    getRadius: PARCEL_SIZE / 2,
    pickable: true,
    onClick: ({ object }) => setSelectedParcel(object),
})

Data Indexing. The Graph subgraph for on-chain events (Transfer, Rented, Listed). PostgreSQL for off-chain metadata and fast spatial queries. Postgis extension for geospatial queries:

-- Find all parcels within radius from coordinate
SELECT * FROM parcels
WHERE ST_DWithin(
    ST_MakePoint(x, y)::geometry,
    ST_MakePoint($1, $2)::geometry,
    $3 -- radius
)
AND for_sale = true;

Content Layer: What is Built on LAND

LAND development — a separate data layer. Standard formats:

Decentraland SDK scene. Babylon.js-based 3D scene. Described in TypeScript, deployed to content server. Tied to LAND coordinates.

GLTF / GLB assets. 3D objects loaded into spatial content. NFTs can represent specific 3D objects (wearables, buildings).

Iframe-based content. Simple web content in VR-overlay. Less immersive, but easy to create.

Content URI is stored in LAND NFT metadata. When development changes, owner updates contentURI via setContentURI(tokenId, newURI). This is an on-chain transaction, history of changes is preserved.

Analytics and Price Discovery

A marketplace without analytics — not competitive. Essential minimum:

  • Floor price by zones (residential vs commercial vs plaza adjacency)
  • Price history per parcel (via on-chain event indexing)
  • Volume by days/weeks
  • Activity heatmap: which areas trade more
  • Rental yield calculator: annual rental income / current floor price
interface PriceAnalytics {
    floorPrice: bigint
    avgPrice: bigint
    volumeLast7d: bigint
    salesCountLast7d: number
    priceChange7d: number      // %
    estateFloorPrice?: bigint  // separate floor for Estate
}

async function getZoneAnalytics(zoneId: number): Promise<PriceAnalytics> {
    // From subgraph or PostgreSQL
    const sales = await db.query(`
        SELECT price, timestamp FROM sales
        WHERE zone_id = $1 AND timestamp > NOW() - INTERVAL '7 days'
        ORDER BY price ASC
    `, [zoneId])

    return {
        floorPrice: sales[0]?.price ?? 0n,
        avgPrice: average(sales.map(s => s.price)),
        volumeLast7d: sum(sales.map(s => s.price)),
        salesCountLast7d: sales.length,
        priceChange7d: calculateChange(sales),
    }
}

Development Stack

Component Technology
LAND NFT Solidity ERC-721 + ERC-4907
Estate contract Solidity with adjacency validation
Rental contract Solidity + ERC-4907
Marketplace contract Solidity + ERC-2981
Indexer The Graph (subgraph)
Spatial DB PostgreSQL + PostGIS
Map frontend deck.gl / Mapbox GL JS + React
3D preview Three.js / Babylon.js
Backend API Node.js + Fastify
Storage IPFS (Pinata) + Arweave

Development Process

Product design (1-2 weeks). World map, zoning, primary sale model (Dutch auction?), rental model, fee structure.

Smart contracts (4-6 weeks). LAND NFT with coordinate system, Estate contract with adjacency logic, Rental marketplace with ERC-4907, Sale marketplace with auctions. Audit mandatory.

Backend and indexer (3-4 weeks). Subgraph for events, REST/GraphQL API, spatial queries in PostGIS, price analytics.

Map Frontend (4-6 weeks). Interactive map (deck.gl), parcel detail page, listing and rental UI, analytics dashboard.

3D Content preview (2-3 weeks, optional). GLTF preview for developed parcels, basic 3D viewer.

Testing and launch. End-to-end test of full flow (mint → list → buy → rent → build), load test of map (5000+ parcels in viewport).

MVP without Estate and 3D content — 3-4 months. Full marketplace with Estate, rental, analytics, and 3D preview — 6-8 months.