Game Starter Template

A complete starter template for integrating the SDK into your game. Includes wallet connection, balance display, and transaction sending.

Project Structure

game-starter/
├── index.html    # Main HTML file
├── styles.css    # Styling
├── game.js       # SDK integration logic
└── README.md     # Setup instructions

Key Code

// game.js - Core SDK integration

// Configuration
const HOST_ORIGIN = 'https://store.mythicalbeings.io';

// State
let provider = null;
let session = null;

// Initialize provider
async function init() {
  provider = new MythicalProvider(HOST_ORIGIN);
  
  // Check for existing session
  session = await provider.getSession();
  if (session) {
    updateUI(session);
  }
  
  // Set up event listeners
  provider.on('accountChanged', handleAccountChange);
  provider.on('locked', handleWalletLocked);
  provider.on('unlocked', handleWalletUnlocked);
}

// Connect wallet
async function connectWallet() {
  try {
    session = await provider.connect({
      appName: 'My Game',
      permissions: [
        'READ_ACCOUNT',
        'READ_BALANCES',
        'TX_SEND_IGNIS',
        'TX_TRANSFER_ASSET',
        'SIGN_TOKEN'
      ]
    });
    
    updateUI(session);
    await loadBalances();
  } catch (error) {
    handleError(error);
  }
}

// Load and display balances
async function loadBalances() {
  const balances = await provider.getBalances();
  const ignis = formatNQTToIgnis(balances.ignisNQT);
  
  document.getElementById('balance').textContent = `${ignis} IGNIS`;
}

// Send payment
async function sendPayment(recipientRS, amount, message) {
  const result = await provider.sendIgnis({
    recipientRS,
    amount: { ignis: amount },
    message
  });
  
  showNotification(`Payment sent! TX: ${result.fullHash.slice(0, 16)}...`);
}

// Initialize on load
document.addEventListener('DOMContentLoaded', init);

Marketplace Demo

Complete marketplace integration showing how to display order books, place orders, and manage trades.

Marketplace Integration

// Fetch and display order book
async function loadOrderBook(assetId) {
  const [askOrders, bidOrders] = await Promise.all([
    fetchAskOrders(assetId),
    fetchBidOrders(assetId)
  ]);
  
  displayOrderBook(askOrders, bidOrders);
}

// Place a sell order
async function placeSellOrder(assetId, quantity, pricePerUnit) {
  // Convert price to NQT per share
  const priceNQTPerShare = parseIgnisToNQT(pricePerUnit).toString();
  
  const result = await provider.placeAskOrder({
    assetId,
    quantityQNT: quantity,
    priceNQTPerShare
  });
  
  showNotification('Sell order placed!');
  await loadOrderBook(assetId);
}

// Place a buy order
async function placeBuyOrder(assetId, quantity, pricePerUnit) {
  const priceNQTPerShare = parseIgnisToNQT(pricePerUnit).toString();
  
  const result = await provider.placeBidOrder({
    assetId,
    quantityQNT: quantity,
    priceNQTPerShare
  });
  
  showNotification('Buy order placed!');
  await loadOrderBook(assetId);
}

// Cancel an order
async function cancelOrder(orderId, isSellOrder) {
  if (isSellOrder) {
    await provider.cancelAskOrder({ orderId });
  } else {
    await provider.cancelBidOrder({ orderId });
  }
  
  showNotification('Order cancelled');
}

Vanilla JavaScript (No Build)

Use the SDK directly in the browser without any build tools.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Mythical SDK - Vanilla JS</title>
</head>
<body>
  <div id="app">
    <button id="connect-btn">Connect Wallet</button>
    <div id="account-info" style="display: none;">
      <p>Account: <span id="account"></span></p>
      <p>Balance: <span id="balance"></span> IGNIS</p>
    </div>
  </div>

  <!-- After: npm install github:palheiro1/mythicalSDK -->
  <script type="module">
    // Import from installed package
    import { MythicalProvider } from '@mythicalb/ardor-provider';
    import { formatNQTToIgnis } from '@mythicalb/ardor-core';

    const provider = new MythicalProvider('https://store.mythicalbeings.io');

    document.getElementById('connect-btn').onclick = async () => {
      try {
        const session = await provider.connect({
          appName: 'Vanilla Demo',
          permissions: ['READ_ACCOUNT', 'READ_BALANCES']
        });

        document.getElementById('account').textContent = session.accountRS;
        
        const balances = await provider.getBalances();
        document.getElementById('balance').textContent = formatNQTToIgnis(balances.ignisNQT);
        
        document.getElementById('account-info').style.display = 'block';
        document.getElementById('connect-btn').style.display = 'none';
      } catch (error) {
        alert('Connection failed: ' + error.message);
      }
    };
  </script>
</body>
</html>

React Hooks

Custom React hooks for easy integration with React applications.

// useMythical.ts
import { useState, useEffect, useCallback, useRef } from 'react';
import { MythicalProvider } from '@mythicalb/ardor-provider';
import { formatNQTToIgnis } from '@mythicalb/ardor-core';

const HOST_ORIGIN = 'https://store.mythicalbeings.io';

interface Session {
  accountRS: string;
  publicKey: string;
  permissions: string[];
}

interface Balances {
  ignis: string;
  assets: Array<{ name: string; quantity: string }>;
}

export function useMythical() {
  const providerRef = useRef<MythicalProvider | null>(null);
  const [session, setSession] = useState<Session | null>(null);
  const [balances, setBalances] = useState<Balances | null>(null);
  const [isConnecting, setIsConnecting] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  // Initialize provider
  useEffect(() => {
    providerRef.current = new MythicalProvider(HOST_ORIGIN);
    
    // Check existing session
    providerRef.current.getSession().then(s => {
      if (s) setSession(s);
    });

    // Set up event listeners
    const handleAccountChange = (event: any) => {
      setSession(prev => prev ? { ...prev, ...event } : null);
    };

    providerRef.current.on('accountChanged', handleAccountChange);
    providerRef.current.on('locked', () => setSession(null));

    return () => {
      providerRef.current?.off('accountChanged', handleAccountChange);
    };
  }, []);

  // Connect
  const connect = useCallback(async (permissions: string[]) => {
    if (!providerRef.current) return;
    
    setIsConnecting(true);
    setError(null);
    
    try {
      const session = await providerRef.current.connect({
        appName: 'My React Game',
        permissions
      });
      setSession(session);
    } catch (err) {
      setError(err as Error);
    } finally {
      setIsConnecting(false);
    }
  }, []);

  // Disconnect
  const disconnect = useCallback(async () => {
    await providerRef.current?.disconnect();
    setSession(null);
    setBalances(null);
  }, []);

  // Load balances
  const loadBalances = useCallback(async () => {
    if (!providerRef.current || !session) return;
    
    const result = await providerRef.current.getBalances();
    setBalances({
      ignis: formatNQTToIgnis(result.ignisNQT),
      assets: result.assets.map(a => ({
        name: a.name,
        quantity: a.quantityQNT
      }))
    });
  }, [session]);

  // Send IGNIS
  const sendIgnis = useCallback(async (
    recipientRS: string, 
    amount: string,
    message?: string
  ) => {
    if (!providerRef.current) throw new Error('Not initialized');
    
    return providerRef.current.sendIgnis({
      recipientRS,
      amount: { ignis: amount },
      message
    });
  }, []);

  return {
    provider: providerRef.current,
    session,
    balances,
    isConnecting,
    error,
    connect,
    disconnect,
    loadBalances,
    sendIgnis
  };
}

Usage in Component

// GameWallet.tsx
import { useMythical } from './useMythical';

export function GameWallet() {
  const {
    session,
    balances,
    isConnecting,
    connect,
    disconnect,
    loadBalances,
    sendIgnis
  } = useMythical();

  useEffect(() => {
    if (session) loadBalances();
  }, [session, loadBalances]);

  if (!session) {
    return (
      <button 
        onClick={() => connect(['READ_ACCOUNT', 'READ_BALANCES', 'TX_SEND_IGNIS'])}
        disabled={isConnecting}
      >
        {isConnecting ? 'Connecting...' : 'Connect Wallet'}
      </button>
    );
  }

  return (
    <div className="wallet-info">
      <p>Account: {session.accountRS}</p>
      <p>Balance: {balances?.ignis || '...'} IGNIS</p>
      <button onClick={disconnect}>Disconnect</button>
    </div>
  );
}

Vue Composable

Vue 3 composable for reactive wallet state.

// useMythical.ts (Vue 3)
import { ref, onMounted, onUnmounted } from 'vue';
import { MythicalProvider } from '@mythicalb/ardor-provider';
import { formatNQTToIgnis } from '@mythicalb/ardor-core';

const HOST_ORIGIN = 'https://store.mythicalbeings.io';

export function useMythical() {
  const provider = ref<MythicalProvider | null>(null);
  const session = ref<any>(null);
  const balances = ref<any>(null);
  const isConnecting = ref(false);
  const error = ref<Error | null>(null);

  onMounted(async () => {
    provider.value = new MythicalProvider(HOST_ORIGIN);
    
    // Check existing session
    const existingSession = await provider.value.getSession();
    if (existingSession) {
      session.value = existingSession;
    }

    // Event listeners
    provider.value.on('accountChanged', (event) => {
      session.value = { ...session.value, ...event };
    });

    provider.value.on('locked', () => {
      session.value = null;
    });
  });

  async function connect(permissions: string[]) {
    if (!provider.value) return;

    isConnecting.value = true;
    error.value = null;

    try {
      session.value = await provider.value.connect({
        appName: 'My Vue Game',
        permissions
      });
    } catch (e) {
      error.value = e as Error;
    } finally {
      isConnecting.value = false;
    }
  }

  async function disconnect() {
    await provider.value?.disconnect();
    session.value = null;
    balances.value = null;
  }

  async function loadBalances() {
    if (!provider.value || !session.value) return;

    const result = await provider.value.getBalances();
    balances.value = {
      ignis: formatNQTToIgnis(result.ignisNQT),
      assets: result.assets
    };
  }

  return {
    provider,
    session,
    balances,
    isConnecting,
    error,
    connect,
    disconnect,
    loadBalances
  };
}

Authentication Flow

Implement secure authentication using signed tokens.

// Client-side: Request signed token
async function authenticateWithBackend() {
  // 1. Get signed token from wallet
  const { token, signature, accountRS, timestamp } = await provider.signToken();
  
  // 2. Send to your backend
  const response = await fetch('/api/auth/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ token, signature, accountRS, timestamp })
  });
  
  // 3. Get session token from backend
  const { sessionToken } = await response.json();
  
  // 4. Store and use session token
  localStorage.setItem('sessionToken', sessionToken);
}
// Server-side (Node.js): Verify token
import { verifyToken } from 'ardor-crypto'; // hypothetical library

app.post('/api/auth/verify', async (req, res) => {
  const { token, signature, accountRS, timestamp } = req.body;
  
  // 1. Check timestamp (prevent replay attacks)
  const age = Date.now() - timestamp;
  if (age > 5 * 60 * 1000) { // 5 minutes
    return res.status(401).json({ error: 'Token expired' });
  }
  
  // 2. Verify signature
  const isValid = await verifyToken(token, signature, accountRS);
  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // 3. Create session
  const sessionToken = generateSessionToken(accountRS);
  
  res.json({ sessionToken, accountRS });
});

In-Game Payments

Process payments for in-game purchases.

interface GameItem {
  id: string;
  name: string;
  price: string; // in IGNIS
}

async function purchaseItem(item: GameItem, sellerRS: string) {
  // Show confirmation to user
  const confirmed = await showPurchaseConfirmation(item);
  if (!confirmed) return;

  try {
    // Send payment
    const result = await provider.sendIgnis({
      recipientRS: sellerRS,
      amount: { ignis: item.price },
      message: `Purchase: ${item.name} (ID: ${item.id})`
    });

    // Notify backend of successful payment
    await fetch('/api/purchases', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        itemId: item.id,
        txHash: result.fullHash,
        buyer: session.accountRS
      })
    });

    // Update UI
    showNotification(`Successfully purchased ${item.name}!`);
    await refreshInventory();
    
  } catch (error) {
    if (error.code === 4001) {
      showNotification('Purchase cancelled');
    } else {
      showError('Payment failed: ' + error.message);
    }
  }
}

NFT Inventory Display

Display and manage the player's NFT inventory.

interface NFT {
  assetId: string;
  name: string;
  quantity: number;
  imageUrl?: string;
  attributes?: Record<string, any>;
}

async function loadNFTInventory(): Promise<NFT[]> {
  const balances = await provider.getBalances();
  
  // Filter for game assets only
  const gameAssets = balances.assets.filter(asset => 
    isGameAsset(asset.asset) // Your logic to identify game NFTs
  );
  
  // Load metadata for each asset
  const nfts = await Promise.all(
    gameAssets.map(async (asset) => {
      const metadata = await fetchAssetMetadata(asset.asset);
      return {
        assetId: asset.asset,
        name: asset.name,
        quantity: parseInt(asset.quantityQNT),
        imageUrl: metadata.image,
        attributes: metadata.attributes
      };
    })
  );
  
  return nfts;
}

// Display in UI
async function renderInventory() {
  const nfts = await loadNFTInventory();
  
  const container = document.getElementById('inventory');
  container.innerHTML = nfts.map(nft => `
    <div class="nft-card">
      <img src="${nft.imageUrl}" alt="${nft.name}">
      <h3>${nft.name}</h3>
      <p>Quantity: ${nft.quantity}</p>
      <button onclick="transferNFT('${nft.assetId}')">Transfer</button>
      <button onclick="listForSale('${nft.assetId}')">Sell</button>
    </div>
  `).join('');
}