Skip to main content

Getting Started with Vidya SDK

This comprehensive guide will walk you through setting up the Vidya SDK and building your first Web3 game. The SDK uses a Player-centric architecture that makes blockchain game development intuitive and powerful.

Core Concepts

The Vidya SDK is built around two main concepts:

  1. Player Object: The central hub for all player interactions
  2. Modular Item Class: Flexible item management with built-in methods

Prerequisites

Before you begin, ensure you have:

  • Node.js 18+ (TypeScript/JavaScript), Unity 2021.3+, Godot 4.0+, or Unreal Engine 5+
  • MetaMask or another Web3 wallet
  • ETH on Arbitrum for gas fees (Bridge ETH)
  • A Vidya Project ID (get one at vidya.game/developers)

Installation

npm install @vidya/sdk viem
# or
yarn add @vidya/sdk viem
# or
bun add @vidya/sdk viem

Quick Start: Player Authentication

The SDK provides two ways to create a Player object:

import { VidyaSDK, Player } from '@vidya/sdk';

// Initialize SDK with your project ID
const sdk = new VidyaSDK({
projectId: 'YOUR_PROJECT_ID',
chainId: 42161, // Arbitrum One
});

// Authenticate and create Player from session
async function initializeGame() {
try {
// Connect wallet and authenticate
const session = await sdk.auth.connect();

// Create Player object from session
const player = await Player.fromSession(session);

console.log('Player authenticated:', player.address);
console.log('Session expires:', player.session.expiresAt);

// Now you can use the player object throughout your game
return player;
} catch (error) {
console.error('Authentication failed:', error);
}
}

Method 2: From Wallet (Direct Connection)

import { Player } from '@vidya/sdk';
import { createWalletClient, custom } from 'viem';
import { arbitrum } from 'viem/chains';

// Create wallet client (e.g., from MetaMask)
const walletClient = createWalletClient({
chain: arbitrum,
transport: custom(window.ethereum),
});

// Create Player directly from wallet
const player = await Player.fromWallet(walletClient, {
projectId: 'YOUR_PROJECT_ID',
});

console.log('Player connected:', player.address);

Working with the Player Object

Once you have a Player object, you can access all game modules through it:

// Access different modules through the player
const player = await Player.fromSession(session);

// Inventory module
const inventory = await player.inventory.getItems();
const weapons = await player.inventory.getItems({
category: 'weapon'
});

// Marketplace module
const listings = await player.marketplace.getListings();

// Equipment module
const equipped = await player.equipment.getEquipped();

// Stats module
const stats = await player.stats.get();

Item Management with the Modular Item Class

The Item class provides built-in methods for all common operations:

// Get items using flexible predicates
const player = await Player.fromSession(session);

// Get a specific item by ID
const sword = await player.inventory.getItem(
item => item.id === 'legendary-sword-001'
);

// Get items by category
const potions = await player.inventory.getItems(
item => item.category === 'consumable' && item.name.includes('Potion')
);

// Get items by rarity
const rareItems = await player.inventory.getItems(
item => item.rarity >= 3
);

// Item operations
if (sword) {
// Transfer to another player
await sword.transfer(recipientAddress, 1);

// Equip the item
await sword.equip();

// List on marketplace
await sword.list({
price: '100',
currency: 'VIDYA',
duration: 7 * 24 * 60 * 60 // 7 days
});

// Consume (for consumables)
if (potions.length > 0) {
await potions[0].consume(1);
}
}

Complete Example: Building a Game Interface

HTML Interface

Create index.html:

<!DOCTYPE html>
<html>
<head>
<title>Vidya Game - Player-Centric Demo</title>
<style>
body {
font-family: 'Inter', -apple-system, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #0a0a0e;
color: #fff;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.button {
background: #F8FF50;
color: #000;
border: none;
padding: 12px 24px;
font-size: 16px;
cursor: pointer;
text-transform: uppercase;
font-weight: bold;
border-radius: 4px;
transition: all 0.2s;
}
.button:hover {
background: #f5fc2a;
transform: translateY(-2px);
}
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.player-info {
background: #1a1a1f;
padding: 20px;
border-radius: 8px;
margin-bottom: 2rem;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
background: #2a2a2f;
border: none;
color: #fff;
cursor: pointer;
border-radius: 4px;
}
.tab.active {
background: #8B5CF6;
}
.content {
background: #1a1a1f;
padding: 20px;
border-radius: 8px;
}
.inventory {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
}
.item {
background: #2a2a2f;
border: 2px solid #3a3a3f;
padding: 15px;
border-radius: 8px;
transition: all 0.2s;
}
.item:hover {
border-color: #8B5CF6;
transform: translateY(-2px);
}
.item-actions {
display: flex;
gap: 10px;
margin-top: 10px;
}
.item-actions button {
padding: 5px 10px;
font-size: 12px;
}
.error {
background: #ef4444;
color: white;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.success {
background: #10b981;
color: white;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
</style>
</head>
<body>
<div class="header">
<h1>Vidya Game Demo</h1>
<button id="connectBtn" class="button">Connect Player</button>
</div>

<div id="playerInfo" class="player-info" style="display: none;">
<h3>Player Details</h3>
<p>Address: <span id="playerAddress"></span></p>
<p>Session Expires: <span id="sessionExpires"></span></p>
</div>

<div id="messages"></div>

<div id="gameContent" style="display: none;">
<div class="tabs">
<button class="tab active" data-tab="inventory">Inventory</button>
<button class="tab" data-tab="marketplace">Marketplace</button>
<button class="tab" data-tab="equipment">Equipment</button>
</div>

<div class="content">
<div id="inventory" class="tab-content"></div>
<div id="marketplace" class="tab-content" style="display: none;"></div>
<div id="equipment" class="tab-content" style="display: none;"></div>
</div>
</div>

<script type="module">
import { VidyaSDK, Player } from 'https://unpkg.com/@vidya/sdk/dist/index.js';

// Initialize SDK
const sdk = new VidyaSDK({
projectId: 'demo-project', // Replace with your project ID
chainId: 42161,
});

let player = null;

// UI Elements
const connectBtn = document.getElementById('connectBtn');
const playerInfo = document.getElementById('playerInfo');
const gameContent = document.getElementById('gameContent');
const messages = document.getElementById('messages');

// Show message
function showMessage(message, type = 'info') {
const div = document.createElement('div');
div.className = type === 'error' ? 'error' : 'success';
div.textContent = message;
messages.appendChild(div);
setTimeout(() => div.remove(), 5000);
}

// Connect player
connectBtn.onclick = async () => {
try {
connectBtn.disabled = true;
connectBtn.textContent = 'Connecting...';

// Authenticate and create Player object
const session = await sdk.auth.connect();
player = await Player.fromSession(session);

// Update UI
document.getElementById('playerAddress').textContent = player.address;
document.getElementById('sessionExpires').textContent =
new Date(player.session.expiresAt).toLocaleString();

playerInfo.style.display = 'block';
gameContent.style.display = 'block';
connectBtn.textContent = 'Connected';

showMessage('Player connected successfully!', 'success');

// Load initial data
await loadInventory();

} catch (error) {
showMessage(`Connection failed: ${error.message}`, 'error');
connectBtn.disabled = false;
connectBtn.textContent = 'Connect Player';
}
};

// Tab switching
document.querySelectorAll('.tab').forEach(tab => {
tab.onclick = () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');

document.querySelectorAll('.tab-content').forEach(content => {
content.style.display = 'none';
});

const tabName = tab.dataset.tab;
document.getElementById(tabName).style.display = 'block';

// Load content based on tab
if (tabName === 'inventory') loadInventory();
else if (tabName === 'marketplace') loadMarketplace();
else if (tabName === 'equipment') loadEquipment();
};
});

// Load inventory
async function loadInventory() {
if (!player) return;

const inventoryDiv = document.getElementById('inventory');
inventoryDiv.innerHTML = '<p>Loading inventory...</p>';

try {
// Get all items
const items = await player.inventory.getItems();

if (items.length === 0) {
inventoryDiv.innerHTML = '<p>No items in inventory</p>';
return;
}

inventoryDiv.innerHTML = '<div class="inventory"></div>';
const grid = inventoryDiv.querySelector('.inventory');

items.forEach(item => {
const itemDiv = document.createElement('div');
itemDiv.className = 'item';
itemDiv.innerHTML = `
<h4>${item.name}</h4>
<p>Category: ${item.category}</p>
<p>Quantity: ${item.balance}</p>
<p>Rarity: ${item.rarity}</p>
<div class="item-actions">
<button class="button" onclick="equipItem('${item.id}')">Equip</button>
<button class="button" onclick="listItem('${item.id}')">List</button>
${item.category === 'consumable' ?
`<button class="button" onclick="consumeItem('${item.id}')">Use</button>` : ''}
</div>
`;
grid.appendChild(itemDiv);
});

// Make functions available globally for onclick handlers
window.equipItem = async (itemId) => {
try {
const item = await player.inventory.getItem(i => i.id === itemId);
await item.equip();
showMessage(`Equipped ${item.name}!`, 'success');
loadEquipment();
} catch (error) {
showMessage(`Failed to equip: ${error.message}`, 'error');
}
};

window.listItem = async (itemId) => {
try {
const item = await player.inventory.getItem(i => i.id === itemId);
const price = prompt('Enter price in VIDYA:');
if (!price) return;

await item.list({
price: price,
currency: 'VIDYA',
duration: 7 * 24 * 60 * 60
});
showMessage(`Listed ${item.name} for ${price} VIDYA!`, 'success');
} catch (error) {
showMessage(`Failed to list: ${error.message}`, 'error');
}
};

window.consumeItem = async (itemId) => {
try {
const item = await player.inventory.getItem(i => i.id === itemId);
await item.consume(1);
showMessage(`Used ${item.name}!`, 'success');
loadInventory();
} catch (error) {
showMessage(`Failed to consume: ${error.message}`, 'error');
}
};

} catch (error) {
inventoryDiv.innerHTML = `<p class="error">Failed to load inventory: ${error.message}</p>`;
}
}

// Load marketplace
async function loadMarketplace() {
if (!player) return;

const marketplaceDiv = document.getElementById('marketplace');
marketplaceDiv.innerHTML = '<p>Loading marketplace...</p>';

try {
const listings = await player.marketplace.getListings();

if (listings.length === 0) {
marketplaceDiv.innerHTML = '<p>No active listings</p>';
return;
}

marketplaceDiv.innerHTML = '<div class="inventory"></div>';
const grid = marketplaceDiv.querySelector('.inventory');

listings.forEach(listing => {
const listingDiv = document.createElement('div');
listingDiv.className = 'item';
listingDiv.innerHTML = `
<h4>${listing.item.name}</h4>
<p>Price: ${listing.price} ${listing.currency}</p>
<p>Seller: ${listing.seller.slice(0, 6)}...${listing.seller.slice(-4)}</p>
<div class="item-actions">
<button class="button" onclick="buyItem('${listing.id}')">Buy</button>
</div>
`;
grid.appendChild(listingDiv);
});

window.buyItem = async (listingId) => {
try {
await player.marketplace.buy(listingId);
showMessage('Purchase successful!', 'success');
loadMarketplace();
loadInventory();
} catch (error) {
showMessage(`Purchase failed: ${error.message}`, 'error');
}
};

} catch (error) {
marketplaceDiv.innerHTML = `<p class="error">Failed to load marketplace: ${error.message}</p>`;
}
}

// Load equipment
async function loadEquipment() {
if (!player) return;

const equipmentDiv = document.getElementById('equipment');
equipmentDiv.innerHTML = '<p>Loading equipment...</p>';

try {
const equipped = await player.equipment.getEquipped();

if (Object.keys(equipped).length === 0) {
equipmentDiv.innerHTML = '<p>No items equipped</p>';
return;
}

equipmentDiv.innerHTML = '<div class="inventory"></div>';
const grid = equipmentDiv.querySelector('.inventory');

Object.entries(equipped).forEach(([slot, item]) => {
const itemDiv = document.createElement('div');
itemDiv.className = 'item';
itemDiv.innerHTML = `
<h4>${slot.toUpperCase()}</h4>
<p>${item.name}</p>
<p>Stats: +${item.stats?.attack || 0} ATK, +${item.stats?.defense || 0} DEF</p>
<div class="item-actions">
<button class="button" onclick="unequipItem('${slot}')">Unequip</button>
</div>
`;
grid.appendChild(itemDiv);
});

window.unequipItem = async (slot) => {
try {
await player.equipment.unequip(slot);
showMessage(`Unequipped ${slot}!`, 'success');
loadEquipment();
loadInventory();
} catch (error) {
showMessage(`Failed to unequip: ${error.message}`, 'error');
}
};

} catch (error) {
equipmentDiv.innerHTML = `<p class="error">Failed to load equipment: ${error.message}</p>`;
}
}
</script>
</body>
</html>

TypeScript Implementation

Create game.ts:

import { VidyaSDK, Player, Item } from '@vidya/sdk';

class VidyaGame {
private sdk: VidyaSDK;
private player: Player | null = null;

constructor(projectId: string) {
this.sdk = new VidyaSDK({
projectId,
chainId: 42161, // Arbitrum One
});
}

async initialize(): Promise<void> {
try {
// Authenticate player
const session = await this.sdk.auth.connect();
this.player = await Player.fromSession(session);

console.log('Player initialized:', this.player.address);

// Set up event listeners
this.setupEventListeners();

} catch (error) {
console.error('Failed to initialize:', error);
throw error;
}
}

private setupEventListeners(): void {
if (!this.player) return;

// Listen for item received events
this.player.events.on('ItemReceived', (event) => {
console.log('New item received:', event);
this.onItemReceived(event.item);
});

// Listen for item consumed events
this.player.events.on('ItemConsumed', (event) => {
console.log('Item consumed:', event);
this.onItemConsumed(event.itemId, event.amount);
});

// Listen for equipment changes
this.player.events.on('ItemEquipped', (event) => {
console.log('Item equipped:', event);
this.onItemEquipped(event.item, event.slot);
});
}

// Inventory management
async getInventory(): Promise<Item[]> {
if (!this.player) throw new Error('Player not initialized');
return this.player.inventory.getItems();
}

async getWeapons(): Promise<Item[]> {
if (!this.player) throw new Error('Player not initialized');

return this.player.inventory.getItems(
item => item.category === 'weapon' && item.equipmentSlot === 'mainHand'
);
}

async getConsumables(): Promise<Item[]> {
if (!this.player) throw new Error('Player not initialized');

return this.player.inventory.getItems(
item => item.category === 'consumable'
);
}

// Item operations
async equipItem(itemId: string): Promise<void> {
if (!this.player) throw new Error('Player not initialized');

const item = await this.player.inventory.getItem(
i => i.id === itemId
);

if (!item) throw new Error('Item not found');

await item.equip();
}

async consumeItem(itemId: string, amount: number = 1): Promise<void> {
if (!this.player) throw new Error('Player not initialized');

const item = await this.player.inventory.getItem(
i => i.id === itemId
);

if (!item) throw new Error('Item not found');
if (item.category !== 'consumable') throw new Error('Item is not consumable');

await item.consume(amount);
}

async listItemForSale(itemId: string, price: string, currency: string = 'VIDYA'): Promise<void> {
if (!this.player) throw new Error('Player not initialized');

const item = await this.player.inventory.getItem(
i => i.id === itemId
);

if (!item) throw new Error('Item not found');

await item.list({
price,
currency,
duration: 7 * 24 * 60 * 60, // 7 days
});
}

// Marketplace operations
async getMarketplaceListings(filter?: {
category?: string;
minPrice?: string;
maxPrice?: string;
}): Promise<any[]> {
if (!this.player) throw new Error('Player not initialized');

const listings = await this.player.marketplace.getListings(filter);
return listings;
}

async purchaseItem(listingId: string): Promise<void> {
if (!this.player) throw new Error('Player not initialized');

await this.player.marketplace.buy(listingId);
}

// Crafting operations
async getCraftingRecipes(): Promise<any[]> {
if (!this.player) throw new Error('Player not initialized');

return this.player.crafting.getRecipes();
}

async craftItem(recipeId: string, quantity: number = 1): Promise<void> {
if (!this.player) throw new Error('Player not initialized');

await this.player.crafting.craft(recipeId, quantity);
}

// Event handlers
private onItemReceived(item: Item): void {
// Update UI, show notification, etc.
console.log(`Received ${item.name}!`);
}

private onItemConsumed(itemId: string, amount: number): void {
// Update UI, apply effects, etc.
console.log(`Consumed ${amount} of item ${itemId}`);
}

private onItemEquipped(item: Item, slot: string): void {
// Update character stats, visuals, etc.
console.log(`Equipped ${item.name} to ${slot}`);
}
}

// Usage example
async function main() {
const game = new VidyaGame('YOUR_PROJECT_ID');

try {
await game.initialize();

// Get inventory
const inventory = await game.getInventory();
console.log('Inventory:', inventory);

// Get specific items
const weapons = await game.getWeapons();
console.log('Weapons:', weapons);

// Equip first weapon
if (weapons.length > 0) {
await game.equipItem(weapons[0].id);
}

// Get marketplace listings
const listings = await game.getMarketplaceListings({
category: 'weapon',
maxPrice: '1000',
});
console.log('Weapon listings:', listings);

} catch (error) {
console.error('Game error:', error);
}
}

main();

Advanced Features

🔐 Session Management

// Check session validity
if (player.session.isExpired()) {
// Refresh session
await player.refreshSession();
}

// Get remaining session time
const timeLeft = player.session.getTimeUntilExpiry();
console.log(`Session expires in ${timeLeft} seconds`);

📊 Player Statistics

// Get player stats
const stats = await player.stats.get();
console.log('Level:', stats.level);
console.log('Experience:', stats.experience);
console.log('Achievements:', stats.achievements);

// Update stats (server-side only)
await player.stats.addExperience(100);
await player.stats.unlockAchievement('first_kill');

🎯 Quest System

// Get active quests
const quests = await player.quests.getActive();

// Complete quest objectives
await player.quests.completeObjective(questId, objectiveId);

// Claim quest rewards
await player.quests.claimRewards(questId);

💰 Currency Management

// Get balances
const balances = await player.wallet.getBalances();
console.log('VIDYA:', balances.VIDYA);
console.log('Gold:', balances.gold);

// Transfer tokens
await player.wallet.transfer({
to: recipientAddress,
amount: '100',
currency: 'VIDYA'
});

Best Practices

1. Error Handling

Always wrap SDK calls in try-catch blocks:

try {
await player.inventory.getItems();
} catch (error) {
if (error.code === 'INSUFFICIENT_BALANCE') {
// Handle specific error
} else {
// Handle general error
}
}

2. Optimistic Updates

Update UI immediately for better UX:

// Optimistic UI update
updateUIOptimistically(item);

try {
await item.equip();
} catch (error) {
// Revert UI on failure
revertUIUpdate(item);
}

3. Batch Operations

Use batch operations when possible:

// Instead of multiple calls
for (const itemId of itemIds) {
await player.inventory.getItem(i => i.id === itemId);
}

// Use a single call
const items = await player.inventory.getItems(
i => itemIds.includes(i.id)
);

4. Caching

Implement caching for frequently accessed data:

class GameCache {
private cache = new Map();
private ttl = 60000; // 1 minute

async get(key: string, fetcher: () => Promise<any>) {
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}

const data = await fetcher();
this.cache.set(key, { data, timestamp: Date.now() });
return data;
}
}

Troubleshooting

Common Issues

  1. "Player not authenticated"

    • Ensure you've called Player.fromSession() or Player.fromWallet()
    • Check if the session has expired
  2. "Transaction failed"

    • Verify sufficient gas (ETH) in wallet
    • Check network connection
    • Ensure correct chain (Arbitrum One)
  3. "Item not found"

    • Verify item ID is correct
    • Check if player owns the item
    • Ensure inventory is synced

Debug Mode

Enable debug logging:

const sdk = new VidyaSDK({
projectId: 'YOUR_PROJECT_ID',
chainId: 42161,
debug: true, // Enable debug logs
});

Migration Guide

If you're migrating from an older version:

From v1.x to v2.x

// Old way
const items = await sdk.inventory.getPlayerItems(playerAddress);

// New way
const player = await Player.fromSession(session);
const items = await player.inventory.getItems();

Key Changes

  1. Player-centric API: All operations now go through the Player object
  2. Item methods: Items now have built-in methods (transfer, equip, list, consume)
  3. Flexible queries: Use predicates instead of fixed filters
  4. Session management: Built-in session handling with automatic refresh

Next Steps

Ready to build your Web3 game?

Explore Full Docs

Get Your Project ID