Metaverse 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
Metaverse Development
Complex
from 2 weeks 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

Metaverse Development

Metaverse is an overloaded term. Before designing, choose a specific model: persistent 3D world with real-time player interaction (like Decentraland, The Sandbox), or social layer over different applications, or virtual offices for enterprise? Each model—different technology stack.

I'll describe web3-native persistent world architecture: multiplayer 3D environment with NFT ownership of land/assets, on-chain economy and decentralized governance. Most technically complex and most interesting variant.

Architectural Layers of Metaverse

1. Blockchain Layer: Ownership and Economy

Everything valuable exists on-chain:

  • LAND NFT—virtual land parcels (ERC-721)
  • Avatar NFT—characters with attributes
  • Wearables NFT—clothes, items (ERC-1155 for fungible, ERC-721 for unique)
  • Governance token—DAO management participation
  • In-world currency—ERC-20 token for economy

Everything else—off-chain (land content, 3D assets, interaction history).

2. Content Layer: What's on LAND

LAND owner deploys content on parcel: 3D scenes (GLTF/GLB files), interactivity scripts, portals to other worlds. Content stored decentralized—IPFS or Arweave.

3. Real-time Layer: Multiplayer Engine

Players see each other and interact in real-time—this is real-time networking task, not blockchain. Game servers (or P2P peer relay) sync avatar positions and states.

4. Application Layer: dApps and Games on LAND

Land owner deploys applications—mini-games, NFT galleries, virtual stores, concert venues. Each application can have own on-chain logic.

LAND System: NFT Land and Coordinate Grid

Coordinate System

Classic approach: map as 2D coordinate grid. Decentraland uses (-150, -150) to (150, 150). The Sandbox—408×408.

contract LandRegistry is ERC721 {
    int16 public constant MIN_X = -100;
    int16 public constant MAX_X = 100;
    int16 public constant MIN_Y = -100;
    int16 public constant MAX_Y = 100;
    
    // Token ID encodes coordinates: id = (x + 100) * 201 + (y + 100)
    function coordinatesToId(int16 x, int16 y) public pure returns (uint256) {
        require(x >= MIN_X && x <= MAX_X, "X out of range");
        require(y >= MIN_Y && y <= MAX_Y, "Y out of range");
        return uint256(uint16(x - MIN_X)) * 201 + uint256(uint16(y - MIN_Y));
    }
    
    function idToCoordinates(uint256 tokenId) public pure returns (int16 x, int16 y) {
        y = int16(int256(tokenId % 201)) + MIN_Y;
        x = int16(int256(tokenId / 201)) + MIN_X;
    }
    
    // Adjacency check: needed for Estate (merging adjacent parcels)
    function isAdjacent(uint256 tokenId1, uint256 tokenId2) public pure returns (bool) {
        (int16 x1, int16 y1) = idToCoordinates(tokenId1);
        (int16 x2, int16 y2) = idToCoordinates(tokenId2);
        int16 dx = x1 - x2;
        int16 dy = y1 - y2;
        return (dx == 0 && (dy == 1 || dy == -1)) || (dy == 0 && (dx == 1 || dx == -1));
    }
}

Estate: Merging Adjacent LAND

Owner of several adjacent parcels can merge into Estate for larger scenes:

contract EstateRegistry is ERC721 {
    struct Estate {
        uint256[] landIds;   // included LAND tokens
        string name;
        string ipfsHash;     // metadata
    }
    
    mapping(uint256 => Estate) public estates;
    
    function createEstate(
        uint256[] calldata landIds,
        string calldata name
    ) external returns (uint256 estateId) {
        require(landIds.length >= 2, "Need at least 2 parcels");
        
        // Verify ownership and adjacency
        for (uint256 i = 0; i < landIds.length; i++) {
            require(landRegistry.ownerOf(landIds[i]) == msg.sender, "Not land owner");
        }
        require(_isConnectedGraph(landIds), "Parcels not adjacent");
        
        // Transfer LAND to estate contract (lock)
        for (uint256 i = 0; i < landIds.length; i++) {
            landRegistry.transferFrom(msg.sender, address(this), landIds[i]);
        }
        
        estateId = ++nextEstateId;
        estates[estateId] = Estate({ landIds: landIds, name: name, ipfsHash: "" });
        _mint(msg.sender, estateId);
        
        emit EstateCreated(estateId, msg.sender, landIds);
    }
    
    // BFS check that all parcels connected in one graph
    function _isConnectedGraph(uint256[] calldata ids) internal view returns (bool) {
        if (ids.length == 1) return true;
        
        bool[] memory visited = new bool[](ids.length);
        uint256[] memory queue = new uint256[](ids.length);
        uint256 qHead = 0;
        uint256 qTail = 0;
        
        visited[0] = true;
        queue[qTail++] = ids[0];
        
        while (qHead < qTail) {
            uint256 current = queue[qHead++];
            for (uint256 i = 0; i < ids.length; i++) {
                if (!visited[i] && landRegistry.isAdjacent(current, ids[i])) {
                    visited[i] = true;
                    queue[qTail++] = ids[i];
                }
            }
        }
        
        for (uint256 i = 0; i < ids.length; i++) {
            if (!visited[i]) return false;
        }
        return true;
    }
}

Content System: What Deploys on LAND

Scene Descriptor

Each LAND has scene—set of 3D objects, scripts, portals. Stored on IPFS:

// Scene descriptor (IPFS JSON)
interface SceneDescriptor {
  version: '2.0';
  landId: number;
  owner: string;           // ethereum address
  title: string;
  description: string;
  
  // 3D content
  models: Array<{
    src: string;           // ipfs://Qm... link to GLTF/GLB
    position: [number, number, number];
    rotation: [number, number, number];
    scale: [number, number, number];
  }>;
  
  // Interactivity scripts
  scripts: Array<{
    src: string;           // ipfs://Qm... JavaScript module
    entryPoint: string;    // exported function name
  }>;
  
  // Avatar spawn points
  spawnPoints: Array<{
    position: [number, number, number];
    cameraTarget: [number, number, number];
  }>;
  
  // Adjacent land / portal links
  portals: Array<{
    position: [number, number, number];
    targetLandId: number;
    targetUrl?: string;    // or external URL
  }>;
}

Publishing scene:

contract LandContent {
    // IPFS hash scene for each LAND
    mapping(uint256 => string) public sceneHash;
    mapping(uint256 => uint256) public sceneVersion;
    
    function publishScene(uint256 landId, string calldata ipfsHash) external {
        require(landRegistry.ownerOf(landId) == msg.sender, "Not owner");
        
        // Basic validation: non-empty hash
        require(bytes(ipfsHash).length == 46, "Invalid IPFS hash"); // Qm... = 46 chars
        
        sceneHash[landId] = ipfsHash;
        sceneVersion[landId]++;
        
        emit ScenePublished(landId, msg.sender, ipfsHash, sceneVersion[landId]);
    }
}

Scene Scripting SDK

Developers write scripts for interactivity on LAND. Decentralized version of Unity/Unreal scripting:

// Metaverse Scene Script SDK (runs in sandbox iframe/WebWorker)
import { engine, Transform, MeshRenderer, OnPointerDown } from '@metaverse/sdk';

// Interactive door
const door = engine.addEntity();
engine.addComponentOrReplace(door, Transform, {
  position: { x: 0, y: 0, z: 5 },
  rotation: { x: 0, y: 0, z: 0, w: 1 },
  scale: { x: 1, y: 1, z: 1 },
});

let isOpen = false;

engine.addComponentOrReplace(door, OnPointerDown, {
  callback: async () => {
    isOpen = !isOpen;
    // Animate open/close
    const transform = engine.getComponent(door, Transform);
    transform.rotation = isOpen
      ? Quaternion.fromEulerDegrees(0, 90, 0)
      : Quaternion.fromEulerDegrees(0, 0, 0);
  },
  hoverText: isOpen ? 'Close door' : 'Open door',
});

// NFT gate: only holders of specific NFT can enter
import { checkNFTOwnership } from '@metaverse/blockchain';

engine.addComponentOrReplace(nftGate, OnPointerDown, {
  callback: async () => {
    const userAddress = await engine.getUserAddress();
    const hasNFT = await checkNFTOwnership(userAddress, NFT_CONTRACT, TOKEN_ID);
    
    if (!hasNFT) {
      engine.showNotification('You need the Golden Pass NFT to enter');
      return;
    }
    
    engine.teleportPlayer({ x: INSIDE_X, y: 0, z: INSIDE_Z });
  },
});

Scripting sandbox—isolated WebWorker or iframe without direct DOM access. Blockchain interaction API provided via postMessage, not direct wallet access.

Real-time Networking: Avatar Synchronization

Architecture: Area Servers

World divided into regions (each region = N×N LAND). Each region served by one game server. When player moves between regions—handoff to other server.

                    ┌─────────────────────────────┐
                    │    Load Balancer / Router    │
                    │   (by player coordinates)    │
                    └──────────┬──────────────────┘
                               │
          ┌────────────────────┼────────────────────┐
          ▼                    ▼                    ▼
  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
  │  Area Server │    │  Area Server │    │  Area Server │
  │  Region A    │    │  Region B    │    │  Region C    │
  │  (-100,-100) │    │  (0,0)       │    │  (100,100)   │
  │  to (0,0)   │    │  to (100,100)│    │  ...         │
  └──────────────┘    └──────────────┘    └──────────────┘
// Area Server: manage avatars in region
import { WebSocketServer } from 'ws';
import { World } from '@dimforge/rapier3d'; // physics engine

class AreaServer {
  private players = new Map<string, PlayerState>();
  private physicsWorld = new World({ x: 0, y: -9.81, z: 0 });
  
  handlePlayerJoin(playerId: string, ws: WebSocket, position: Vector3) {
    const state: PlayerState = {
      id: playerId,
      position,
      rotation: Quaternion.identity(),
      animation: 'idle',
      ws,
    };
    
    this.players.set(playerId, state);
    
    // Send new player all region states
    const snapshot = this.getRegionSnapshot();
    ws.send(JSON.stringify({ type: 'region_snapshot', players: snapshot }));
    
    // Notify others about new player
    this.broadcast({ type: 'player_joined', player: state }, playerId);
  }
  
  handleMovement(playerId: string, movement: MovementPacket) {
    const player = this.players.get(playerId);
    if (!player) return;
    
    // Server-side movement validation (anti-cheat)
    if (!this.isValidMovement(player, movement)) {
      // Correct position
      player.ws.send(JSON.stringify({
        type: 'position_correction',
        position: player.position,
      }));
      return;
    }
    
    player.position = movement.position;
    player.rotation = movement.rotation;
    player.animation = movement.animation;
    
    // Broadcast to all in region (delta compression)
    this.broadcastMovement(playerId, movement);
  }
  
  // 20 updates/sec for smooth movement
  private startTickLoop() {
    setInterval(() => this.tick(), 50);
  }
  
  private tick() {
    this.physicsWorld.step();
    
    // Collect dirty states and broadcast as batch
    const updates = this.getDirtyPlayerStates();
    if (updates.length > 0) {
      this.broadcast({ type: 'batch_update', updates });
    }
  }
}

Proximity-based Broadcasting

No need to broadcast player position to everyone in region—only those nearby. Interest Management:

const VISIBILITY_RADIUS = 100; // meters

function getVisiblePlayers(playerId: string): string[] {
  const player = players.get(playerId)!;
  return Array.from(players.values())
    .filter(p => p.id !== playerId)
    .filter(p => distance(p.position, player.position) < VISIBILITY_RADIUS)
    .map(p => p.id);
}

This reduces bandwidth from O(N²) to O(N × K), where K = average visible players.

In-world Economy

Marketplace Contract

LAND owner sells virtual goods inside world:

contract InWorldMarketplace {
    struct Listing {
        address seller;
        address nftContract;
        uint256 tokenId;
        uint256 price;          // in in-world currency (ERC-20)
        uint256 landId;         // which LAND listing on
        bool active;
    }
    
    // Royalty for LAND owner: 2.5% from sales on land
    uint256 public constant LAND_ROYALTY = 250; // basis points
    
    function buy(uint256 listingId) external {
        Listing storage listing = listings[listingId];
        require(listing.active, "Not active");
        
        uint256 landRoyalty = listing.price * LAND_ROYALTY / 10_000;
        uint256 sellerProceeds = listing.price - landRoyalty;
        
        // Buyer pays in-world currency
        worldToken.transferFrom(msg.sender, listing.seller, sellerProceeds);
        worldToken.transferFrom(msg.sender, landRegistry.ownerOf(listing.landId), landRoyalty);
        
        // Transfer NFT
        IERC721(listing.nftContract).transferFrom(
            address(this), msg.sender, listing.tokenId
        );
        
        listing.active = false;
        emit Sale(listingId, msg.sender, listing.price);
    }
}

Play-to-Earn Mechanics

In-world activities can generate in-world currency:

  • Visiting events on LAND (check-in reward)
  • Completing quests created by LAND owners
  • Participating in mini-games

Important: emission rate must be controlled to prevent inflation. Recommendation: weekly emission cap + halving mechanism like Bitcoin.

DAO and Governance

// Governance via Compound-style voting
contract MetaverseDAO is Governor, GovernorTimelockControl {
    // Holding LAND gives voting power
    function _getVotes(
        address account,
        uint256 blockNumber,
        bytes memory
    ) internal view override returns (uint256) {
        // 1 LAND = 1 vote + bonus for governance token staking
        uint256 landVotes = landRegistry.balanceOf(account); // at blockNumber
        uint256 tokenVotes = govToken.getPastVotes(account, blockNumber);
        return landVotes + tokenVotes;
    }
}

Governance decides: world size (new LAND), economy parameters, whitelist content formats, contract upgrades.

Technology Stack

3D Rendering

Three.js + React Three Fiber—for web-native metaverse. Good GLTF support, PBR materials, performance optimizations via instancing and LOD.

Babylon.js—alternative, especially good with WebXR (VR/AR). Decentraland uses Babylon.js.

Unity WebGL—best graphics quality, but large bundle (50–200 MB), slow load. Good for desktop-oriented product.

Full Stack

Layer Technology
Blockchain Polygon PoS or Arbitrum (low gas for LAND transactions)
LAND/NFT Solidity ERC-721 + ERC-1155, Foundry
Governance OpenZeppelin Governor + TimelockController
Storage IPFS (Pinata/Web3.Storage) + Arweave for permanent content
Real-time Node.js + uWebSockets.js (high performance)
Physics Rapier3D (Rust/WASM, faster than Cannon.js)
3D Web Three.js + React Three Fiber + Drei
Avatar system ReadyPlayerMe SDK or custom VRM avatars
State Redis for region state, PostgreSQL for persistent data
Indexing The Graph (LAND transfers, scene updates events)

Development Phases

Phase Content Timeline
Foundation LAND contracts, coordinate system, basic marketplace 4–6 weeks
Content system Scene descriptor, IPFS storage, scene publishing 3–4 weeks
3D Client Three.js world, scene loading, basic navigation 6–8 weeks
Real-time Area servers, avatar sync, proximity system 6–8 weeks
Economy In-world token, marketplace, play-to-earn 4–6 weeks
Scripting SDK Scene scripting sandbox, NFT gate APIs 4–6 weeks
Governance DAO contracts, voting UI 3–4 weeks
Audit LAND, marketplace, economy contracts 5–8 weeks
Alpha launch Private alpha with limited map 2–4 weeks

Realistic timeline from zero to public alpha: 12–18 months for team 8–12 people (2–3 blockchain, 2–3 3D/frontend, 2 backend, 1 PM, 1 designer). One of most scope-complex projects in Web3.

Main risk not technical but product: without content on LAND and active community world will be empty. Need early LAND holder and content creator program parallel to development.