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('');
}