The Looti platform enables a marketplace where:
- Mini app developers (like Alex) can integrate rewards into their apps using the Rewards SDK
- Game developers (like Bob) can monetize their games using the Game Dev SDK
- End users enjoy games and earn rewards through the Looti ecosystem
- Games are hosted on developer's own infrastructure (URLs)
- Reduces Looti's server load
- Developers maintain control over their game code
- Looti backend manages all token/reward distribution
- Ensures security and prevents fraud
- Single source of truth for user balances
- Authentication handled through Farcaster
- User identity flows through Farcaster mini apps
- Social graph integration for viral growth
graph TD
MA[Mini App<br/>Alex's App] --> RS[looti/sdk<br/>Rewards SDK]
RS --> IPC[looti/ipc<br/>IPC Client]
RS --> UI[UI Components<br/>Modal/Notifications]
RS --> LB[Looti Backend API]
LB --> RM[Rewards Manager]
LB --> GM[Game Registry]
LB --> TB[Token Balance]
LB --> FC[Farcaster Auth]
LB --> SES[Session Manager]
MA --> GI[Game iFrame]
GI -.->|PostMessage via IPC| RS
IPC --> SDKClient[SDKClient Class]
SDKClient --> MV[Message Validation<br/>Zod Schemas]
style MA fill:#fff3e0
style RS fill:#f3e5f5
style IPC fill:#e1f5fe
style LB fill:#e8f5e9
style GI fill:#fce4ec
- Built on @looti/sdk: Extends existing modal and IPC functionality
- Type-safe IPC: Uses existing @looti/ipc for secure communication
- Session Management: Server-validated sessions with JWT tokens
- Reward Validation: All rewards validated server-side with idempotency
- Farcaster Native: Integrated FID authentication
graph TD
GC[Game Code<br/>Bob's Game] --> GS[looti/game-sdk]
GS --> GIC[Game IPC Client]
GS --> API[Looti API Client]
GS --> SEC[Security Module<br/>HMAC/JWT]
GIC --> PM[PostMessage Handler]
GIC --> MV[Message Validation]
API --> VM[Session Validation]
API --> AM[Analytics Module]
API --> MM[Monetization Manager]
API --> GR[Game Registry]
SEC --> SIG[Request Signing]
SEC --> IK[Idempotency Keys]
GC --> GH[Game Hosting<br/>developer-url.com]
PM -.->|IPC Messages| PS[Parent SDK]
style GC fill:#fce4ec
style GS fill:#f3e5f5
style GIC fill:#e1f5fe
style API fill:#e8f5e9
style GH fill:#e3f2fd
style SEC fill:#ffccbc
- Session-based Auth: JWT tokens passed via URL params from parent
- Built-in Security: HMAC signing and idempotency key generation
- IPC Communication: Standardized message format with parent frame
- Analytics Tracking: Automatic event capture and reporting
- Rate Limiting: Client-side throttling with server validation
- TypeScript First: Full type safety for all game events
graph TD
subgraph "Client Layer"
U[Farcaster Users]
MA[Mini Apps]
end
subgraph "SDK Layer"
RS[Rewards SDK]
GS[Game Dev SDK]
end
subgraph "Security Layer"
SES[Session Manager]
VAL[Validation Engine]
RL[Rate Limiter]
IK[Idempotency Store]
end
subgraph "Looti Platform"
API[API Gateway]
AUTH[Farcaster Auth]
RE[Rewards Engine]
GR[Game Registry]
AN[Analytics]
FL[Fraud Detection]
end
subgraph "External Games"
G1[Bob's Game]
G2[Carol's Game]
G3[More Games...]
end
subgraph "Storage"
DB[(Database)]
RD[(Redis Cache)]
end
U --> MA
MA --> RS
RS --> SES
SES --> API
G1 --> GS
G2 --> GS
G3 --> GS
GS --> VAL
VAL --> API
API --> RL
API --> IK
API --> AUTH
API --> RE
API --> GR
API --> AN
API --> FL
RE --> DB
GR --> DB
AN --> DB
IK --> RD
RL --> RD
SES --> RD
MA -.->|iframe + token| G1
MA -.->|iframe + token| G2
style U fill:#e1f5fe
style MA fill:#fff3e0
style RS fill:#f3e5f5
style GS fill:#f3e5f5
style VAL fill:#ffccbc
style API fill:#e8f5e9
style G1 fill:#fce4ec
style DB fill:#f0f0f0
style RD fill:#ffebee
sequenceDiagram
participant User as User
participant MA as Mini App
participant SDK as Rewards SDK
participant LB as Looti Backend
participant Redis as Redis Cache
participant GI as Game iFrame
participant GS as Game SDK
participant GH as Game Host
Note over User,GH: Initial Session Creation Phase
User->>MA: 1. Click "Play Game"
MA->>SDK: 2. showGame(gameId)
SDK->>SDK: 3. Get Farcaster FID
SDK->>LB: 4. POST /api/session/create<br/>{gameId, fid, appId}
Note right of SDK: Headers:<br/>X-App-Id: mini_app_id<br/>X-Farcaster-FID: user_fid
LB->>LB: 5. Validate app & user
LB->>LB: 6. Generate session token<br/>& unique sessionId
Note over LB,Redis: Session Storage
LB->>Redis: 7. Store session<br/>TTL: 30 minutes
Note right of Redis: Key: session:abc123<br/>Data: {userId, gameId,<br/>appId, nonce, expires}
LB-->>SDK: 8. Return session<br/>{sessionId, token, gameUrl}
Note over SDK,GH: iFrame Loading Phase
SDK->>SDK: 9. Build secure URL<br/>gameUrl?token=JWT&session=abc123
SDK->>MA: 10. Create iFrame element
MA->>GI: 11. Load game in iFrame
Note over GI,GH: Game Initialization Phase
GI->>GH: 12. Request game assets
GH-->>GI: 13. Load game + SDK
GI->>GS: 14. Initialize Game SDK
GS->>GS: 15. Extract token from URL
Note over GS,LB: Session Validation Phase
GS->>LB: 16. POST /api/session/validate<br/>{token, sessionId}
LB->>Redis: 17. Get session:abc123
Redis-->>LB: 18. Return session data
LB->>LB: 19. Verify:<br/>- JWT signature<br/>- Session exists<br/>- Not expired<br/>- Game matches
alt Session Valid
LB-->>GS: 20a. Success response<br/>{valid: true, userId, limits}
GS->>GI: 21a. Enable game features
GS->>SDK: 22a. PostMessage: 'ready'
SDK->>MA: 23a. Game ready callback
Note over User: Game starts!
else Session Invalid
LB-->>GS: 20b. Error response<br/>{valid: false, error}
GS->>SDK: 21b. PostMessage: 'error'
SDK->>MA: 22b. Handle error
Note over User: Show error message
end
Note over User,GH: During Gameplay (Reward Flow)
User->>GI: 24. Complete achievement
GI->>GS: 25. triggerReward()
GS->>GS: 26. Sign request with HMAC
GS->>LB: 27. POST /api/reward/trigger<br/>{sessionId, amount, idempotencyKey, signature}
LB->>Redis: 28. Check idempotency
LB->>LB: 29. Validate signature & limits
alt Reward Valid
LB-->>GS: 30a. Reward confirmed
GS->>SDK: 31a. PostMessage: 'reward'
SDK->>MA: 32a. Update UI/balance
else Reward Invalid
LB-->>GS: 30b. Reward rejected
GS->>SDK: 31b. PostMessage: 'error'
end
Note over Redis: Session expires after 30 min
-
Session Creation:
- Mini app requests session from Looti backend with user's Farcaster FID
- Backend generates JWT token with embedded session data
- Session stored in Redis with 30-minute TTL
-
Token Transmission:
- Token passed to game via URL parameters (secure HTTPS)
- Game SDK extracts token from URL on initialization
-
Validation Process:
- Game SDK validates session with backend before enabling features
- Backend verifies JWT signature, session existence, and expiration
- Only validated sessions can trigger rewards
-
Security Measures:
- Sessions expire after 30 minutes
- Each session tied to specific user, game, and mini app
- All reward requests require valid session + HMAC signature
- Idempotency keys prevent duplicate rewards
-
Looti Backend Setup
- API Gateway with rate limiting
- Farcaster authentication integration (FID validation)
- Database schema for users, games, transactions
- Redis for session management and idempotency
- Basic reward engine with token management
-
Security Layer
- Session-based authentication with JWT tokens
- API key generation for game developers
- HMAC signature validation for all reward requests
- CORS configuration for iframe communication
- Domain whitelisting for approved games
- Idempotency key storage with Redis TTL
-
Developer Portal
// Admin dashboard for game developers interface AdminDashboard { // Game registration registerGame(): { apiKey: string, apiSecret: string }; // Configuration management configureLimits(gameId: string, limits: GameLimits): void; updateWhitelistedDomains(gameId: string, domains: string[]): void; // Analytics & monitoring viewAnalytics(gameId: string): GameAnalytics; viewRewardHistory(gameId: string): RewardTransaction[]; // Budget management setDailyBudget(gameId: string, amount: number): void; setMonthlyBudget(gameId: string, amount: number): void; }
-
Game Configuration Storage
-- Database schema for game configurations CREATE TABLE games ( game_id UUID PRIMARY KEY, game_name VARCHAR(255), api_key VARCHAR(64) UNIQUE, api_secret_hash VARCHAR(255), -- Admin-configured limits max_reward_per_action INT DEFAULT 100, max_reward_per_user_daily INT DEFAULT 1000, max_reward_per_user_hourly INT DEFAULT 200, max_game_budget_daily INT DEFAULT 50000, max_game_budget_monthly INT DEFAULT 1000000, cooldown_seconds INT DEFAULT 60, max_actions_per_minute INT DEFAULT 10, -- Settings whitelisted_domains TEXT[], status VARCHAR(20) DEFAULT 'testing', created_at TIMESTAMP, updated_at TIMESTAMP ); -- Track usage against limits CREATE TABLE game_usage ( game_id UUID REFERENCES games(game_id), date DATE, daily_spent INT DEFAULT 0, monthly_spent INT DEFAULT 0, PRIMARY KEY (game_id, date) );
-
SDK Core Features
import { LootiGameSDK } from '@looti/game-sdk'; // Initialize with authentication const gameSDK = new LootiGameSDK({ apiKey: process.env.LOOTI_API_KEY, gameId: 'puzzle-game-v1', secret: process.env.LOOTI_SECRET, // For HMAC signing debug: process.env.NODE_ENV === 'development' }); // Validate session from URL params (passed by parent frame) await gameSDK.validateSession(urlParams.token, urlParams.sessionId); // Track player actions with automatic rate limiting await gameSDK.trackAction('level_complete', { level: 5, score: 1000, time: 120 }); // Trigger reward with idempotency const reward = await gameSDK.triggerReward('achievement_unlocked', { achievementId: 'first_win', amount: 100, idempotencyKey: gameSDK.generateIdempotencyKey('first_win') }); // Communicate with parent frame gameSDK.sendToParent('game_completed', { finalScore: 1000, rewardEarned: reward.amount });
-
IPC Integration for Game → SDK Communication
// Built on existing @looti/ipc patterns class GameClient extends IpcClient { constructor(config: GameConfig) { super({ debug: config.debug, allowedOrigins: ['https://looti.club', ...config.allowedOrigins] }); } // Send game events to parent sendGameEvent(event: string, data: any) { this.postMessage({ type: 'data', payload: { event, data } }); } // Send reward trigger to parent sendRewardTrigger(reward: RewardData) { this.postMessage({ type: 'reward', payload: reward }); } }
-
Developer Portal Features
- Game registration with webhook for approval
- API key and secret generation
- Analytics dashboard (plays, rewards, earnings)
- Testing sandbox with mock rewards
- Documentation and code examples
-
Extend Existing SDK with Rewards Features
import { LootiSDK } from '@looti/sdk'; class LootiRewardsSDK extends LootiSDK { private sessionId?: string; private userBalance: number = 0; constructor(config: LootiRewardsConfig) { super({ appId: config.appId, debug: config.debug, timeout: config.timeout || 15000 // Longer for game loading }); // Initialize Farcaster context this.farcasterFid = config.farcasterFid; } // Enhanced show method with session creation async showGame(options: ShowGameOptions): Promise<HTMLIFrameElement> { // Create session with backend const session = await this.createGameSession(options.gameId); this.sessionId = session.sessionId; // Build secure iframe URL with session token const iframeUrl = this.buildSecureGameUrl(options.gameId, session); // Use existing modal infrastructure const iframe = await this.show({ gameId: options.gameId, url: iframeUrl, // Override with secure URL modal: { style: options.style || 'pixelated', onClose: () => this.handleGameClose(options.onClose) } }); // Set up reward listeners this.setupRewardListeners(); return iframe; } private setupRewardListeners() { // Listen for reward events from game this.iframe.onIpcMessage('reward', async (message) => { const validation = await this.validateReward(message.payload); if (validation.success) { this.userBalance = validation.newBalance; this.showRewardNotification(message.payload); } }); // Listen for game completion this.iframe.onIpcMessage('game_completed', (message) => { this.handleGameCompletion(message.payload); }); } private async createGameSession(gameId: string): Promise<GameSession> { const response = await fetch(`${LOOTI_API}/session/create`, { method: 'POST', headers: { 'X-App-Id': this.config.appId, 'X-Farcaster-FID': this.farcasterFid }, body: JSON.stringify({ gameId }) }); return response.json(); } private buildSecureGameUrl(gameId: string, session: GameSession): string { const params = new URLSearchParams({ token: session.token, sessionId: session.sessionId, userId: this.farcasterFid, timestamp: Date.now().toString() }); return `${session.gameUrl}?${params}`; } }
-
UI Components Library
// Reward notification component class RewardNotification { show(reward: RewardData) { const notification = this.createElement({ type: 'success', title: 'Reward Earned!', message: `You earned ${reward.amount} tokens!`, duration: 3000 }); this.animate(notification, 'slide-in'); } } // Game selection modal enhancements class GameSelectionModal { async render(games: Game[]) { return games.map(game => ({ ...game, expectedReward: game.averageReward, difficulty: game.difficulty, playTime: game.estimatedMinutes, thumbnail: game.thumbnailUrl })); } } // Balance display widget class BalanceWidget { constructor(private sdk: LootiRewardsSDK) {} async refresh() { const balance = await this.sdk.getBalance(); this.updateDisplay(balance); } }
-
Comprehensive Testing Strategy
// Unit tests for reward validation describe('RewardValidation', () => { it('should reject duplicate idempotency keys', async () => { const key = 'test-key-123'; await triggerReward({ idempotencyKey: key }); // Second attempt should fail await expect(triggerReward({ idempotencyKey: key })) .rejects.toThrow('Duplicate request'); }); it('should enforce rate limits', async () => { // Trigger 10 actions quickly for (let i = 0; i < 10; i++) { await triggerAction(); } // 11th should be rate limited await expect(triggerAction()) .rejects.toThrow('Rate limit exceeded'); }); }); // Integration tests with mock games describe('GameIntegration', () => { it('should handle complete game flow', async () => { const sdk = new LootiRewardsSDK({ appId: 'test' }); const iframe = await sdk.showGame({ gameId: 'test-game' }); // Simulate game events await simulateGameEvent(iframe, 'level_complete'); await simulateGameEvent(iframe, 'achievement_unlocked'); // Verify rewards issued const balance = await sdk.getBalance(); expect(balance).toBeGreaterThan(0); }); });
-
Security Audit Checklist
- HMAC signature validation on all reward endpoints
- Session token expiration (30 min TTL)
- Rate limiting per user/game/IP
- Idempotency key uniqueness validation
- Domain whitelisting for iframe origins
- Budget enforcement (daily/hourly limits)
- Fraud detection rules active
- Error logging without exposing internals
- CSP headers properly configured
- PostMessage origin validation
-
Load Testing
// Simulate high traffic scenarios const loadTest = { scenarios: [ { name: 'Normal Load', usersPerSecond: 100, duration: 300 // 5 minutes }, { name: 'Peak Load', usersPerSecond: 1000, duration: 60 // 1 minute spike }, { name: 'Sustained Gaming', usersPerSecond: 500, duration: 1800 // 30 minutes } ] };
-
SDK Package Structure
@looti/sdk # Core rewards SDK for mini apps @looti/game-sdk # SDK for game developers @looti/ipc # Shared IPC client (already exists) @looti/types # Shared TypeScript types @looti/test-utils # Testing utilities for developers -
Developer Documentation
- Quick start guides for both SDKs
- Security best practices guide
- Reward economy design guidelines
- API reference with TypeScript examples
- Video tutorials for common integrations
- Sample game templates (puzzle, arcade, quiz)
-
CLI Tool for Game Developers
# Looti CLI for game developers npx @looti/cli init # Initialize game project npx @looti/cli test # Test with mock rewards npx @looti/cli validate # Validate integration npx @looti/cli deploy # Register game with platform
// Game Dev SDK - Idempotent reward triggers
const lootiGameSDK = new LootiGameSDK({
apiKey: 'game_api_key',
gameId: 'unique_game_id'
});
// Each reward request requires a unique idempotency key
lootiGameSDK.triggerReward('achievement_unlocked', {
achievementId: 'first_win',
amount: 100,
idempotencyKey: `${userId}-${achievementId}-${timestamp}`, // Prevents duplicate rewards
userSessionId: 'session_xyz' // Must match server session
});// Security flow for reward validation
{
// Layer 1: Origin Validation
origin: 'https://approved-game.com', // Must be whitelisted
// Layer 2: Session Token
sessionToken: 'jwt_token_from_looti_backend', // Generated on game load
// Layer 3: Game Signature
signature: 'hmac_sha256_signature', // Game secret + request data
// Layer 4: Rate Limiting Key
rateLimitKey: `${gameId}-${userId}-${actionType}`,
// Layer 5: Idempotency
idempotencyKey: 'unique_request_id'
}// Game developer configures limits in admin dashboard
interface GameConfiguration {
gameId: string;
apiKey: string; // Public key for identification
apiSecret: string; // Secret for HMAC signing
// Developer-defined limits (set in admin panel)
limits: {
maxRewardPerAction: number; // e.g., 100 tokens
maxRewardPerUserDaily: number; // e.g., 1000 tokens/day
maxRewardPerUserHourly: number; // e.g., 200 tokens/hour
maxGameBudgetDaily: number; // e.g., 50000 tokens/day
maxGameBudgetMonthly: number; // e.g., 1000000 tokens/month
cooldownSeconds: number; // e.g., 60s between rewards
maxActionsPerMinute: number; // e.g., 10 actions/min
};
// Admin settings
settings: {
whitelistedDomains: string[]; // Where game can be hosted
enableAnalytics: boolean;
enableFraudDetection: boolean;
customWebhook?: string; // For game events
};
// Status
status: 'active' | 'suspended' | 'testing';
createdAt: Date;
updatedAt: Date;
}
// Admin dashboard flow
const adminDashboard = {
// 1. Game developer registers game
registerGame: async (gameDetails) => {
const { apiKey, apiSecret } = generateCredentials();
return await saveGameConfig({
...gameDetails,
apiKey,
apiSecret,
limits: DEFAULT_LIMITS // Start with platform defaults
});
},
// 2. Developer customizes limits
updateLimits: async (gameId, newLimits) => {
// Validate against platform maximums
const validated = validateLimits(newLimits);
return await updateGameConfig(gameId, { limits: validated });
},
// 3. Platform retrieves limits for validation
getGameConfig: async (apiKey) => {
return await db.games.findOne({ apiKey });
}
};graph TD
subgraph "Admin Dashboard"
AD[Admin Panel] -->|Configure| GC[Game Config DB]
GC -->|Store| LIMITS[Reward Limits]
GC -->|Store| KEYS[API Keys/Secrets]
GC -->|Store| DOMAINS[Whitelisted Domains]
end
subgraph "Runtime Validation"
MA[Mini App] -->|1. Request game| LB[Looti Backend]
LB -->|2. Fetch config| GC
GC -->|3. Return limits| LB
LB -->|4. Generate session<br/>with limits| ST[Session Token]
ST -->|5. Embed in URL| GI[Game iFrame]
GI -->|6. Validate action| VAL[Validation Layer]
VAL -->|7. Check against<br/>stored limits| GC
end
subgraph "Validation Checks"
VAL --> C1[Verify API Key]
VAL --> C2[Check Secret/HMAC]
VAL --> C3[Apply Stored Limits]
VAL --> C4[Check Budget]
VAL --> C5[Rate Limiting]
VAL --> C6[Idempotency]
end
C6 -->|All Pass| RW[Issue Reward]
C6 -->|Any Fail| REJ[Reject & Log]
style AD fill:#e3f2fd
style GC fill:#fff9c4
style LB fill:#e8f5e9
style VAL fill:#ffccbc
style RW fill:#c8e6c9
style REJ fill:#ef5350
Admin Dashboard - Game Registration:
// Admin API endpoint for game registration
app.post('/api/admin/games/register', authenticateAdmin, async (req, res) => {
const {
gameName,
gameUrl,
developerEmail,
initialLimits // Optional, uses defaults if not provided
} = req.body;
// Generate unique credentials
const apiKey = `pk_${crypto.randomBytes(16).toString('hex')}`;
const apiSecret = `sk_${crypto.randomBytes(32).toString('hex')}`;
// Create game configuration
const gameConfig = {
gameId: crypto.randomUUID(),
gameName,
gameUrl,
developerEmail,
apiKey,
apiSecret,
// Use developer-provided limits or platform defaults
limits: {
maxRewardPerAction: initialLimits?.maxRewardPerAction || 100,
maxRewardPerUserDaily: initialLimits?.maxRewardPerUserDaily || 1000,
maxRewardPerUserHourly: initialLimits?.maxRewardPerUserHourly || 200,
maxGameBudgetDaily: initialLimits?.maxGameBudgetDaily || 50000,
maxGameBudgetMonthly: initialLimits?.maxGameBudgetMonthly || 1000000,
cooldownSeconds: initialLimits?.cooldownSeconds || 60,
maxActionsPerMinute: initialLimits?.maxActionsPerMinute || 10
},
settings: {
whitelistedDomains: [new URL(gameUrl).origin],
enableAnalytics: true,
enableFraudDetection: true
},
status: 'testing', // Start in testing mode
createdAt: new Date(),
updatedAt: new Date()
};
// Store in database
await db.games.insert(gameConfig);
// Return credentials to developer
res.json({
gameId: gameConfig.gameId,
apiKey,
apiSecret,
message: 'Save your API secret securely - it cannot be retrieved later'
});
});Runtime Validation Using Stored Config:
// When validating rewards, fetch limits from DB
app.post('/api/reward/trigger', async (req, res) => {
const {
apiKey,
sessionId,
action,
amount,
idempotencyKey,
signature
} = req.body;
// 1. Fetch game configuration from database
const gameConfig = await db.games.findOne({ apiKey });
if (!gameConfig || gameConfig.status !== 'active') {
return res.status(401).json({ error: 'Invalid or inactive game' });
}
// 2. Verify signature using stored secret
const expectedSig = createHmac('sha256', gameConfig.apiSecret)
.update(JSON.stringify({ sessionId, action, amount }))
.digest('hex');
if (signature !== expectedSig) {
await logSecurityEvent('INVALID_SIGNATURE', { apiKey, sessionId });
return res.status(403).json({ error: 'Invalid signature' });
}
// 3. Check against admin-configured limits
const { limits } = gameConfig;
// Check per-action limit
if (amount > limits.maxRewardPerAction) {
return res.status(403).json({
error: 'Exceeds maximum reward per action',
limit: limits.maxRewardPerAction
});
}
// Check user daily limit
const userDailyTotal = await getUserDailyRewards(sessionId, gameConfig.gameId);
if (userDailyTotal + amount > limits.maxRewardPerUserDaily) {
return res.status(403).json({
error: 'User daily limit exceeded',
limit: limits.maxRewardPerUserDaily
});
}
// Check game budget
const gameDailySpent = await getGameDailySpend(gameConfig.gameId);
if (gameDailySpent + amount > limits.maxGameBudgetDaily) {
return res.status(403).json({
error: 'Game daily budget exceeded',
limit: limits.maxGameBudgetDaily
});
}
// 4. Check rate limiting
const rateLimitKey = `rate:${sessionId}:${gameConfig.gameId}`;
const recentActions = await redis.incr(rateLimitKey);
if (recentActions === 1) {
await redis.expire(rateLimitKey, 60); // 1 minute window
}
if (recentActions > limits.maxActionsPerMinute) {
return res.status(429).json({
error: 'Rate limit exceeded',
limit: limits.maxActionsPerMinute
});
}
// 5. Check idempotency
const existing = await redis.get(`idemp:${idempotencyKey}`);
if (existing) {
return res.status(409).json({ error: 'Duplicate request' });
}
// 6. All checks passed - issue reward
await issueReward(sessionId, amount, gameConfig.gameId);
// Store idempotency key
await redis.setex(`idemp:${idempotencyKey}`, 86400, '1');
res.json({
success: true,
rewardId: crypto.randomUUID(),
amount,
userDailyRemaining: limits.maxRewardPerUserDaily - userDailyTotal - amount,
gameDailyRemaining: limits.maxGameBudgetDaily - gameDailySpent - amount
});
});Admin Dashboard UI for Limit Configuration:
// Admin panel component for setting limits
const GameLimitsConfig = ({ gameId }) => {
const [limits, setLimits] = useState({
maxRewardPerAction: 100,
maxRewardPerUserDaily: 1000,
maxRewardPerUserHourly: 200,
maxGameBudgetDaily: 50000,
maxGameBudgetMonthly: 1000000,
cooldownSeconds: 60,
maxActionsPerMinute: 10
});
const updateLimits = async () => {
const response = await fetch(`/api/admin/games/${gameId}/limits`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${adminToken}`
},
body: JSON.stringify(limits)
});
if (response.ok) {
toast.success('Limits updated successfully');
}
};
return (
<div className="limits-config">
<h3>Configure Reward Limits</h3>
<label>Max Reward Per Action</label>
<input
type="number"
value={limits.maxRewardPerAction}
onChange={(e) => setLimits({...limits, maxRewardPerAction: e.target.value})}
/>
<label>Max Per User (Daily)</label>
<input
type="number"
value={limits.maxRewardPerUserDaily}
onChange={(e) => setLimits({...limits, maxRewardPerUserDaily: e.target.value})}
/>
<label>Game Budget (Daily)</label>
<input
type="number"
value={limits.maxGameBudgetDaily}
onChange={(e) => setLimits({...limits, maxGameBudgetDaily: e.target.value})}
/>
{/* More limit inputs... */}
<button onClick={updateLimits}>Save Limits</button>
</div>
);
};Domain Whitelisting:
const WHITELISTED_DOMAINS = [
'https://bobs-game.com',
'https://carols-racing.io'
];
// CSP Headers for iframe
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
`frame-ancestors ${WHITELISTED_DOMAINS.join(' ')};`
);
next();
});Fraud Detection Rules:
- Sudden spike in rewards from single IP
- Multiple accounts from same device fingerprint
- Rewards triggered outside normal game flow
- Abnormal play patterns (too fast completion)
- Geographic anomalies
Monitoring & Alerts:
// Real-time monitoring
const monitoringRules = {
alertThresholds: {
rewardsPerMinute: 1000, // Alert if > 1000 rewards/min
budgetUsagePercent: 80, // Alert at 80% budget used
failedValidations: 100, // Alert on suspicious activity
duplicateAttempts: 50 // Alert on duplicate attempts
}
};// Based on existing @looti/ipc patterns
interface LootiMessage {
type: 'ready' | 'data' | 'status' | 'error' | 'reward';
payload?: any;
}
// Game → Parent Messages
{
type: 'data',
payload: {
event: 'game_action' | 'level_complete' | 'achievement_unlocked',
data: {
score?: number;
level?: number;
achievementId?: string;
timestamp: number;
}
}
}
// Reward Request Message
{
type: 'reward',
payload: {
action: 'trigger_reward',
amount: number,
reason: string,
idempotencyKey: string,
signature: string // HMAC(secret, payload)
}
}
// Parent → Game Messages
{
type: 'data',
payload: {
event: 'session_validated' | 'reward_confirmed' | 'reward_rejected',
data: {
success: boolean;
balance?: number;
error?: string;
}
}
}-
iframe Sandboxing
- Restrict permissions for embedded games
- Content Security Policy headers
- Domain whitelist for approved games
-
Rate Limiting
- Per-user action limits
- Per-game reward caps
- Cooldown periods for rewards
-
Validation Pipeline
- Client-side event → SDK validation → Server validation → Fraud check → Reward issue
-
For Game Developers:
- Pay per unique player
- Revenue share on in-game purchases
- Bonus for high-engagement games
-
For Mini App Developers:
- Free SDK integration
- Optional premium features (custom themes, priority support)
- Developer Adoption: Number of games integrated
- User Engagement: Average session time, repeat players
- Reward Distribution: Tokens distributed vs. fraud prevented
- Platform Growth: MAU, DAU, retention rates
- Finalize API specifications
- Set up development environment
- Create SDK boilerplates
- Build MVP with 1-2 test games
- Alpha test with select developers
- Iterate based on feedback
- Public beta launch