A few years ago, when Decentraland launched LAND, parcel coordinates were stored in a single contract. Modern metaverses demand flexible solutions: on-chain rentals, auctions, adjacency premium. We build marketplaces for virtual real estate that support Decentraland LAND, Sandbox LAND, Otherside Otherdeed, and custom L2 solutions. Each project needs its own secondary trading, rental, and development platform. Developing a virtual real estate marketplace is the intersection of NFT marketplace infrastructure, on-chain rental mechanics, and spatial data management. Our experience includes over 5 projects in this domain, and we guarantee smart contract audit.
Technical specifics compared to generic NFT marketplace: parcels have coordinates (x, y), adjacency and adjacency bonuses are possible, rental is temporary with rights return, development creates metadata relationships between LAND NFT and content NFT. These nuances require non-standard solutions: from coordinate-based tokenId to temporary rights delegation via ERC-4907.
How on-chain rental of virtual real estate works
ERC-4907: Rentable NFT Standard
Land rental is a significant use case: the owner holds LAND as an investment, the renter uses it for development/events. It's necessary to separate ownership (NFT with owner) and usage rights (with renter). 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 the LAND contract does not support ERC-4907: temporary NFT transfer with collateral. The renter deposits collateral (equal to or greater than LAND value), the NFT is transferred, and upon expiry it's automatically returned via a keeper or manual claim. Problem: the landlord loses physical possession of the NFT during the rental period (although they have the right to return it). Risk: the renter sells the NFT despite the collateral. Solution: the NFT is transferred to an escrow contract, not to the renter.
What marketplace mechanics are required?
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 especially relevant for primary LAND sales: price starts high and automatically decreases to a reserve. It eliminates gas wars during mint — comparison: Dutch auction reduces fees by 40% compared to fixed-price mint.
English Auction for secondary market of rare Estates: bidding with outbid protection (minimum bid increment of X%).
Royalties and fee structure
ERC-2981 for on-chain royalties. Standard virtual real estate marketplace fee structure:
| 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 is a controversial topic. Platforms like Blur have undermined enforcement. Solution: royalties enforcement through the contract (marketplace-independent), or a royalty-free model with alternative revenue sharing.
What is adjacency premium?
A unique feature of land marketplaces: adjacent parcels are worth more together than separately. The adjacency search algorithm checks coordinates for cardinal and diagonal contiguity. The frontend displays highlighted adjacent lots on hover over one — the user sees potential bundle purchases. A typical premium for forming an estate is 15-30% of the sum of individual parcels. For example, two adjacent parcels individually are worth 10 ETH each, but combined as an estate their total price is 23 ETH instead of 20, a premium of 3 ETH (15%).
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 leased)
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: merged parcels
Estate = multiple adjacent parcels merged 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 tokenIds of constituent parcels
address landContract;
}
mapping(uint256 => Estate) public estates;
// parcel → estate (if 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);
}
}
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 info.
WebGL and deck.gl — the most performant options for spatial rendering of thousands of objects. deck.gl (Uber) is optimized for geo data 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 — built
return [100, 100, 100] // gray — empty
},
getRadius: PARCEL_SIZE / 2,
pickable: true,
onClick: ({ object }) => setSelectedParcel(object),
})
Content Layer: what is built on LAND
Land development is a separate data layer. Standard formats: Decentraland SDK scene (Babylon.js-based), GLTF/GLB assets, iframe-based content. Content URI is stored in LAND NFT metadata. When development changes, the owner updates contentURI via setContentURI(tokenId, newURI). This is an on-chain transaction, the change history is preserved.
Analytics and price discovery
A marketplace without analytics is not competitive. Necessary minimum: floor price by zones, price history, volume, heatmap of activity, rental yield calculator. Data is indexed via The Graph subgraph for on-chain events and PostgreSQL with PostGIS for off-chain metadata and spatial queries.
How to build a virtual real estate marketplace: step-by-step guide
- Product design (1-2 weeks). World map, zoning, primary sale model, rental model, fee structure.
- Smart contracts (4-6 weeks). LAND NFT, Estate contract, Rental marketplace, Sale marketplace. Mandatory audit.
- Backend and indexer (3-4 weeks). Subgraph, REST/GraphQL API, spatial queries, price analytics.
- Map Frontend (4-6 weeks). Interactive map, parcel detail page, listing/rental UI, analytics dashboard.
- 3D Content preview (2-3 weeks, optional). GLTF preview for developed parcels.
- Testing and launch. End-to-end test, load test of the map.
MVP without Estate and 3D content takes 3-4 months. Full version with Estate, rental, analytics, and 3D preview — 6-8 months.
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 |
Order turnkey development — our engineers with 5+ years of blockchain experience will evaluate your project and offer the optimal solution.
What is included in the work
- Development and audit of smart contracts (Solidity, Foundry)
- Integration with The Graph for indexing
- Implementation of interactive map (deck.gl)
- Backend API and database (PostgreSQL + PostGIS)
- Integration and deployment documentation
- Technical support for 1 month after launch
Get a consultation on virtual real estate marketplace development. LAND owners save up to 30% on bundle sales thanks to adjacency premium.







