Gaming Integration
Build blockchain games with seamless passkey authentication and gasless transactions.
🎮 Try it yourself: Clone and run the full gaming integration example:
git clone https://github.com/Veridex-Protocol/examples.git cd examples && npm install && npm run integration:gamingView source on GitHub (opens in a new tab) · Browse all examples
Overview
Veridex enables game developers to:
- No wallet popups - Players authenticate with device biometrics
- Invisible transactions - Gasless, auto-signed game actions
- Session keys - Smooth gameplay without constant prompts
- Cross-chain assets - Use items across games on different chains
Quick Start
import { createSDK } from '@veridex/sdk';
const sdk = createSDK('base', {
network: 'mainnet',
relayerUrl: 'https://relayer.veridex.network',
});
// Player login - one-time biometric
await sdk.passkey.authenticate();
// Create game session (1 hour, no prompts during gameplay)
const session = await sdk.sessions.create({
duration: 3600,
maxValue: parseUnits('10', 6), // $10 max spend
});
// Now all game actions are frictionless!
await claimDailyReward();
await mintItem(itemId);
await transferGold(playerId, 100);Session Keys for Gaming
The key to smooth gaming is session keys:
// Create optimized game session
async function startGameSession() {
return await sdk.sessions.create({
duration: 7200, // 2 hour play session
maxValue: parseUnits('50', 6), // Max $50 in transactions
allowedTokens: [GAME_TOKEN, GOLD_TOKEN],
allowedContracts: [GAME_CONTRACT, MARKETPLACE],
});
}
// Check if session is still valid
function canAutoSign(): boolean {
const session = sdk.sessions.getActive();
return session && session.expiresAt > Date.now();
}
// Game actions during session need no prompts
async function gameAction() {
if (canAutoSign()) {
await sdk.execute(...); // Instant!
} else {
// Session expired, prompt for new one
await startGameSession();
}
}In-Game Wallet UI
import { useState, useEffect } from 'react';
import { formatUnits } from 'ethers';
import { sdk } from '@/lib/veridex';
export function GameWallet() {
const [gold, setGold] = useState('0');
const [gems, setGems] = useState('0');
const [session, setSession] = useState<any>(null);
useEffect(() => {
loadBalances();
setSession(sdk.sessions.getActive());
}, []);
const loadBalances = async () => {
const balances = await sdk.getBalances();
setGold(balances.base?.GOLD || '0');
setGems(balances.base?.GEMS || '0');
};
return (
<div className="fixed top-4 right-4 bg-black/80 text-white p-4 rounded-lg">
{/* Balances */}
<div className="flex gap-4 mb-3">
<div className="flex items-center gap-2">
<span className="text-yellow-400">🪙</span>
<span>{gold}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-blue-400">💎</span>
<span>{gems}</span>
</div>
</div>
{/* Session Status */}
{session && (
<div className="text-xs text-green-400 flex items-center gap-1">
<span className="w-2 h-2 bg-green-400 rounded-full animate-pulse" />
Session active
</div>
)}
</div>
);
}Game Actions
Claim Rewards
async function claimDailyReward() {
const tx = await sdk.executeViaRelayer({
target: GAME_CONTRACT,
data: gameContract.interface.encodeFunctionData('claimDailyReward', []),
});
// Refresh UI
await loadBalances();
showToast('Daily reward claimed!');
}Mint Items
async function mintItem(itemId: number) {
const tx = await sdk.executeViaRelayer({
target: GAME_CONTRACT,
data: gameContract.interface.encodeFunctionData('mintItem', [
sdk.getVaultAddress(),
itemId,
]),
});
return tx;
}In-Game Purchases
async function purchaseItem(itemId: number, price: bigint) {
// Approve spend (if using ERC-20)
await sdk.approveViaRelayer({
token: GOLD_TOKEN,
spender: MARKETPLACE,
amount: price,
});
// Purchase
await sdk.executeViaRelayer({
target: MARKETPLACE,
data: marketplaceIface.encodeFunctionData('purchase', [itemId]),
});
showToast('Item purchased!');
}Player-to-Player Trade
async function tradeItem(
itemId: bigint,
partnerAddress: string,
goldAmount: bigint
) {
// Create escrow trade
await sdk.executeViaRelayer({
target: TRADE_CONTRACT,
data: tradeIface.encodeFunctionData('createTrade', [
ITEM_CONTRACT,
itemId,
GOLD_TOKEN,
goldAmount,
partnerAddress,
Math.floor(Date.now() / 1000) + 3600, // 1 hour expiry
]),
});
}Inventory System
interface GameItem {
tokenId: bigint;
itemType: number;
rarity: 'common' | 'rare' | 'epic' | 'legendary';
stats: {
attack?: number;
defense?: number;
speed?: number;
};
}
export function Inventory() {
const [items, setItems] = useState<GameItem[]>([]);
const [selectedItem, setSelectedItem] = useState<GameItem | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadInventory();
}, []);
const loadInventory = async () => {
setLoading(true);
const provider = new ethers.JsonRpcProvider('https://mainnet.base.org');
const itemContract = new ethers.Contract(ITEM_CONTRACT, itemAbi, provider);
const balance = await itemContract.balanceOf(sdk.getVaultAddress());
const loadedItems: GameItem[] = [];
for (let i = 0; i < balance; i++) {
const tokenId = await itemContract.tokenOfOwnerByIndex(sdk.getVaultAddress(), i);
const metadata = await itemContract.getItemMetadata(tokenId);
loadedItems.push({
tokenId,
itemType: metadata.itemType,
rarity: ['common', 'rare', 'epic', 'legendary'][metadata.rarity],
stats: {
attack: metadata.attack?.toNumber(),
defense: metadata.defense?.toNumber(),
speed: metadata.speed?.toNumber(),
},
});
}
setItems(loadedItems);
setLoading(false);
};
const equipItem = async (item: GameItem) => {
await sdk.executeViaRelayer({
target: GAME_CONTRACT,
data: gameIface.encodeFunctionData('equip', [item.tokenId]),
});
showToast('Item equipped!');
};
const sellItem = async (item: GameItem, price: bigint) => {
// Approve marketplace
await sdk.executeViaRelayer({
target: ITEM_CONTRACT,
data: itemIface.encodeFunctionData('approve', [MARKETPLACE, item.tokenId]),
});
// List for sale
await sdk.executeViaRelayer({
target: MARKETPLACE,
data: marketplaceIface.encodeFunctionData('list', [
ITEM_CONTRACT,
item.tokenId,
GOLD_TOKEN,
price,
]),
});
showToast('Item listed for sale!');
};
return (
<div className="grid grid-cols-4 gap-2 p-4">
{items.map((item) => (
<div
key={item.tokenId.toString()}
onClick={() => setSelectedItem(item)}
className={`
p-2 border rounded cursor-pointer
${getRarityColor(item.rarity)}
`}
>
<img src={getItemImage(item.itemType)} alt="" className="w-full" />
<div className="text-xs mt-1">{getItemName(item.itemType)}</div>
</div>
))}
</div>
);
}Leaderboards & Achievements
// Submit score (signed by passkey for authenticity)
async function submitScore(score: number) {
const message = ethers.solidityPackedKeccak256(
['address', 'uint256', 'uint256'],
[sdk.getVaultAddress(), score, Date.now()]
);
// Sign with passkey (proves authenticity)
const signature = await sdk.signMessage(message);
// Submit to game server
await fetch('/api/leaderboard', {
method: 'POST',
body: JSON.stringify({
player: sdk.getVaultAddress(),
score,
timestamp: Date.now(),
signature,
}),
});
}
// Claim achievement NFT
async function claimAchievement(achievementId: number) {
await sdk.executeViaRelayer({
target: ACHIEVEMENT_CONTRACT,
data: achievementIface.encodeFunctionData('claim', [
sdk.getVaultAddress(),
achievementId,
]),
});
}Multiplayer Considerations
// Join match with entry fee
async function joinMatch(matchId: string, entryFee: bigint) {
await sdk.approveViaRelayer({
token: GOLD_TOKEN,
spender: MATCH_CONTRACT,
amount: entryFee,
});
await sdk.executeViaRelayer({
target: MATCH_CONTRACT,
data: matchIface.encodeFunctionData('join', [matchId]),
});
}
// Claim winnings
async function claimWinnings(matchId: string) {
await sdk.executeViaRelayer({
target: MATCH_CONTRACT,
data: matchIface.encodeFunctionData('claimPrize', [matchId]),
});
}Best Practices
- Long sessions - Create 2-4 hour game sessions for smooth play
- Batch actions - Combine multiple game actions when possible
- Optimistic UI - Update UI immediately, sync state async
- Retry logic - Handle network issues gracefully
- Session refresh - Prompt for new session before expiry
Session Management Pattern
class GameSession {
private sessionPromise: Promise<void> | null = null;
async ensureSession(): Promise<void> {
const active = sdk.sessions.getActive();
if (active && active.expiresAt > Date.now() + 300000) {
return; // Session valid for at least 5 more minutes
}
// Prevent multiple concurrent session creations
if (!this.sessionPromise) {
this.sessionPromise = this.createSession();
}
await this.sessionPromise;
this.sessionPromise = null;
}
private async createSession(): Promise<void> {
showModal('Session expired. Touch to continue playing.');
await sdk.sessions.create({
duration: 7200,
maxValue: parseUnits('50', 6),
allowedContracts: [GAME_CONTRACT, MARKETPLACE],
});
hideModal();
}
async executeGameAction(action: () => Promise<void>): Promise<void> {
await this.ensureSession();
await action();
}
}
const gameSession = new GameSession();
// Usage
await gameSession.executeGameAction(() => claimReward());
await gameSession.executeGameAction(() => mintItem(1));Security for Games
- Use server-side validation for competitive games
- Sign scores/actions with passkey for authenticity
- Rate limit game actions to prevent abuse
- Use commit-reveal for fairness in random outcomes
- Store sensitive game state on-chain