MCAP Integration Examples
This document provides comprehensive, runnable code examples for integrating with the MCAP gaming platform. These examples provide practical implementations that you can copy and adapt for your own games.
Table of Contents
Authentication Examples
JavaScript/TypeScript Frontend Authentication
// Example using ethers.js for wallet interaction
import { ethers } from 'ethers';
interface MCAPAuthConfig {
baseUrl: string;
providerId: string;
}
interface NonceResponse {
nonce: string;
}
interface LoginResponse {
access_token: string;
refresh_token: string;
token_type: string;
expires_in: number;
}
class MCAPAuth {
private config: MCAPAuthConfig;
private provider: ethers.providers.Web3Provider;
constructor(config: MCAPAuthConfig) {
this.config = config;
// Initialize Web3 provider (assumes MetaMask or similar)
this.provider = new ethers.providers.Web3Provider(window.ethereum);
}
/**
* Step 1: Generate a nonce for the user's wallet
*/
async generateNonce(walletAddress: string): Promise<string> {
const response = await fetch(`${this.config.baseUrl}/v1/auth/nonce`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
wallet_address: walletAddress,
provider_id: this.config.providerId,
}),
});
if (!response.ok) {
throw new Error(`Failed to generate nonce: ${response.statusText}`);
}
const data: NonceResponse = await response.json();
return data.nonce;
}
/**
* Step 2: Sign the authentication message
*/
async signAuthMessage(nonce: string): Promise<{ signature: string; address: string }> {
const signer = this.provider.getSigner();
const address = await signer.getAddress();
// The exact message format required by MCAP
const message = `Sign this message to authenticate with nonce: ${nonce}`;
try {
const signature = await signer.signMessage(message);
return { signature, address };
} catch (error) {
throw new Error(`Failed to sign message: ${error.message}`);
}
}
/**
* Step 3: Login with signed message to get JWT
*/
async login(walletAddress: string, signature: string, nonce: string): Promise<LoginResponse> {
const response = await fetch(`${this.config.baseUrl}/v1/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
provider_id: this.config.providerId,
wallet_address: walletAddress,
signature: signature,
nonce: nonce,
}),
});
if (!response.ok) {
throw new Error(`Login failed: ${response.statusText}`);
}
return await response.json();
}
/**
* Complete authentication flow
*/
async authenticate(): Promise<LoginResponse> {
try {
// Request wallet connection
await this.provider.send('eth_requestAccounts', []);
const signer = this.provider.getSigner();
const walletAddress = await signer.getAddress();
console.log(`Authenticating wallet: ${walletAddress}`);
// Step 1: Generate nonce
const nonce = await this.generateNonce(walletAddress);
console.log(`Generated nonce: ${nonce}`);
// Step 2: Sign message
const { signature } = await this.signAuthMessage(nonce);
console.log(`Message signed successfully`);
// Step 3: Login
const loginResponse = await this.login(walletAddress, signature, nonce);
console.log(`Authentication successful`);
return loginResponse;
} catch (error) {
console.error('Authentication failed:', error);
throw error;
}
}
/**
* Refresh access token using refresh token
*/
async refreshToken(refreshToken: string): Promise<LoginResponse> {
const response = await fetch(`${this.config.baseUrl}/v1/auth/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
refresh_token: refreshToken,
provider_id: this.config.providerId,
}),
});
if (!response.ok) {
throw new Error(`Token refresh failed: ${response.statusText}`);
}
return await response.json();
}
}
// Usage example
async function authenticateUser() {
const auth = new MCAPAuth({
baseUrl: 'https://mcap-api-564778804231.us-east4.run.app',
providerId: 'your-provider-id-here',
});
try {
const tokens = await auth.authenticate();
// Store tokens securely (localStorage, secure cookie, etc.)
localStorage.setItem('mcap_access_token', tokens.access_token);
localStorage.setItem('mcap_refresh_token', tokens.refresh_token);
console.log('User authenticated successfully');
return tokens;
} catch (error) {
console.error('Authentication failed:', error);
throw error;
}
}
Flow Wallet Authentication
// Example for Flow wallets using FCL (Flow Client Library)
import * as fcl from '@onflow/fcl';
class MCAPFlowAuth extends MCAPAuth {
/**
* Flow wallet authentication with EVM compatibility
*/
async authenticateFlowWallet(): Promise<LoginResponse> {
try {
// Authenticate with Flow wallet
const user = await fcl.authenticate();
const flowAddress = user.addr;
// Get Flow EVM address (if using Flow EVM)
const flowEvmAddress = await this.getFlowEvmAddress(flowAddress);
// Generate nonce
const nonce = await this.generateNonce(flowAddress);
// Sign message with Flow wallet
const signature = await this.signFlowMessage(nonce);
// Login with Flow-specific parameters
const response = await fetch(`${this.config.baseUrl}/v1/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
provider_id: this.config.providerId,
wallet_address: flowAddress,
signature: signature,
nonce: nonce,
wallet_type: 'flow',
flow_evm_address: flowEvmAddress,
key_id: 0, // Usually 0 for main key
}),
});
if (!response.ok) {
throw new Error(`Flow login failed: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Flow authentication failed:', error);
throw error;
}
}
private async signFlowMessage(nonce: string): Promise<string> {
const message = `Sign this message to authenticate with nonce: ${nonce}`;
// Sign with Flow wallet
const signature = await fcl.currentUser.signUserMessage(message);
return signature;
}
private async getFlowEvmAddress(flowAddress: string): Promise<string> {
// Implementation depends on your Flow EVM setup
// This would typically query the Flow blockchain for the EVM address
// associated with the Flow address
// For now, returning a placeholder
return '0x' + '0'.repeat(40); // Replace with actual implementation
}
}
Session Management
interface SessionConfig {
baseUrl: string;
accessToken: string;
}
interface CreateSessionRequest {
token_id: string;
game_id: string;
}
interface SessionResponse {
session_id: string;
}
interface SessionDetails {
wallet_id: string;
provider_id: string;
game_id: string;
balance: {
balance: string;
locked_balance: string;
on_chain_balance: string;
token: {
id: string;
symbol: string;
display_decimals: number;
};
};
expires_at: string;
created_at: string;
}
class MCAPSession {
private config: SessionConfig;
constructor(config: SessionConfig) {
this.config = config;
}
/**
* Create a new gaming session
*/
async createSession(tokenId: string, gameId: string): Promise<string> {
const response = await fetch(`${this.config.baseUrl}/v1/sessions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.config.accessToken}`,
},
body: JSON.stringify({
token_id: tokenId,
game_id: gameId,
} as CreateSessionRequest),
});
if (!response.ok) {
throw new Error(`Failed to create session: ${response.statusText}`);
}
const data: SessionResponse = await response.json();
return data.session_id;
}
/**
* Get session details (requires provider API key)
*/
async getSessionDetails(sessionId: string, apiKey: string): Promise<SessionDetails> {
const response = await fetch(`${this.config.baseUrl}/v1/providers/sessions/${sessionId}`, {
headers: {
'Authorization': `Bearer ${apiKey}`,
},
});
if (!response.ok) {
throw new Error(`Failed to get session details: ${response.statusText}`);
}
return await response.json();
}
}
// Usage example
async function createGameSession(accessToken: string, tokenId: string, gameId: string) {
const session = new MCAPSession({
baseUrl: 'https://mcap-api-564778804231.us-east4.run.app',
accessToken: accessToken,
});
try {
const sessionId = await session.createSession(tokenId, gameId);
console.log(`Session created: ${sessionId}`);
return sessionId;
} catch (error) {
console.error('Failed to create session:', error);
throw error;
}
}
Single Player Betting
interface PlaceBetRequest {
bet_id: string;
session_id: string;
amount: string; // Amount in wei
bet_data: Record<string, any>;
}
interface BetOutcomeRequest {
bet_id: string;
session_id: string;
payout: string; // Payout in wei (0 for loss)
bet_data?: Record<string, any>;
result_data?: Record<string, any>;
}
interface BetResponse {
balance: {
balance: string;
locked_balance: string;
on_chain_balance: string;
token: {
id: string;
symbol: string;
display_decimals: number;
};
};
}
class MCAPBetting {
private config: { baseUrl: string; apiKey: string };
constructor(baseUrl: string, apiKey: string) {
this.config = { baseUrl, apiKey };
}
/**
* Place a bet (locks user funds)
*/
async placeBet(
betId: string,
sessionId: string,
amount: string,
betData: Record<string, any> = {}
): Promise<BetResponse> {
const response = await fetch(`${this.config.baseUrl}/v1/providers/bets/place-bet`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.config.apiKey}`,
},
body: JSON.stringify({
bet_id: betId,
session_id: sessionId,
amount: amount,
bet_data: betData,
} as PlaceBetRequest),
});
if (!response.ok) {
throw new Error(`Failed to place bet: ${response.statusText}`);
}
return await response.json();
}
/**
* Resolve bet outcome (settles the bet)
*/
async resolveBet(
betId: string,
sessionId: string,
payout: string,
betData?: Record<string, any>,
resultData?: Record<string, any>
): Promise<BetResponse> {
const response = await fetch(`${this.config.baseUrl}/v1/providers/bets/bet-outcome`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.config.apiKey}`,
},
body: JSON.stringify({
bet_id: betId,
session_id: sessionId,
payout: payout,
bet_data: betData,
result_data: resultData,
} as BetOutcomeRequest),
});
if (!response.ok) {
throw new Error(`Failed to resolve bet: ${response.statusText}`);
}
return await response.json();
}
/**
* Void a bet (returns locked funds)
*/
async voidBet(betId: string): Promise<void> {
const response = await fetch(`${this.config.baseUrl}/v1/providers/bets/void`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.config.apiKey}`,
},
body: JSON.stringify({
bet_id: betId,
}),
});
if (!response.ok) {
throw new Error(`Failed to void bet: ${response.statusText}`);
}
}
}
// Example: Simple Single-Player Game Flow
async function playSinglePlayerGame(
betting: MCAPBetting,
sessionId: string,
betAmount: string // Amount in wei (e.g., "1000000000000000000" for 1 token)
): Promise<{ isWin: boolean; payout: string }> {
const betId = `game_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
try {
// Step 1: Place bet (locks user funds)
console.log('Placing bet...');
await betting.placeBet(betId, sessionId, betAmount, {
game_type: 'slot',
timestamp: new Date().toISOString(),
});
// Step 2: YOUR GAME LOGIC GOES HERE
// - Generate random outcomes
// - Apply game rules
// - Calculate win/loss and multipliers
// - Determine final payout amount
const gameOutcome = {
isWin: Math.random() < 0.5, // 50% win chance
multiplier: 2.0, // 2x payout on win
};
// Step 3: Calculate payout based on game outcome
const payout = gameOutcome.isWin
? (BigInt(betAmount) * BigInt(200) / BigInt(100)).toString() // 2x multiplier
: '0'; // No payout on loss
// Step 4: Settle bet with outcome
console.log('Settling bet...');
await betting.resolveBet(
betId,
sessionId,
payout,
{ /* optional: additional bet data */ },
{
multiplier: gameOutcome.multiplier,
win: gameOutcome.isWin,
// Include any relevant game result data
}
);
return {
isWin: gameOutcome.isWin,
payout: payout,
};
} catch (error) {
// If anything goes wrong, void the bet to return user funds
try {
await betting.voidBet(betId);
console.log('Bet voided due to error');
} catch (voidError) {
console.error('Failed to void bet:', voidError);
}
throw error;
}
}
// Usage example
async function runGameExample(sessionId: string) {
const betting = new MCAPBetting(
'https://mcap-api-564778804231.us-east4.run.app',
'your-api-key-here'
);
try {
// Play with 1 token (adjust decimals based on your token)
const result = await playSinglePlayerGame(betting, sessionId, '1000000000000000000');
if (result.isWin) {
console.log(`🎉 WIN! Payout: ${result.payout} wei`);
} else {
console.log(`😞 Loss`);
}
return result;
} catch (error) {
console.error('Game failed:', error);
throw error;
}
}
Multiplayer Betting
interface CreateMatchRequest {
creator_id: string;
max_players: number;
wager: string;
game_type: string;
session_id: string;
expiry_hours?: number;
}
interface JoinMatchRequest {
player_id: string;
session_id: string;
}
interface SettleMatchRequest {
payouts: Array<{
player_id: string;
amount: string;
}>;
}
interface MatchResponse {
match: {
id: string;
provider_id: string;
token_id: string;
creator_id: string;
max_players: number;
current_players: number;
wager: string;
total_pot: string;
game_type: string;
status: 'pending' | 'active' | 'settled' | 'cancelled' | 'expired';
players: Array<{
player_id: string;
bet_id: string;
session_id: string;
joined_at: string;
}>;
expires_at: string;
created_at: string;
updated_at: string;
};
message: string;
}
class MCAPMultiplayer {
private config: { baseUrl: string };
constructor(baseUrl: string) {
this.config = { baseUrl };
}
/**
* Create a new multiplayer match (requires JWT)
*/
async createMatch(
accessToken: string,
creatorId: string,
maxPlayers: number,
wager: string,
gameType: string,
sessionId: string,
expiryHours: number = 24
): Promise<MatchResponse> {
const response = await fetch(`${this.config.baseUrl}/v1/user/multiplayer-bets/matches`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
},
body: JSON.stringify({
creator_id: creatorId,
max_players: maxPlayers,
wager: wager,
game_type: gameType,
session_id: sessionId,
expiry_hours: expiryHours,
} as CreateMatchRequest),
});
if (!response.ok) {
throw new Error(`Failed to create match: ${response.statusText}`);
}
return await response.json();
}
/**
* Join an existing match (requires JWT)
*/
async joinMatch(
accessToken: string,
matchId: string,
playerId: string,
sessionId: string
): Promise<MatchResponse> {
const response = await fetch(`${this.config.baseUrl}/v1/user/multiplayer-bets/matches/${matchId}/join`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
},
body: JSON.stringify({
player_id: playerId,
session_id: sessionId,
} as JoinMatchRequest),
});
if (!response.ok) {
throw new Error(`Failed to join match: ${response.statusText}`);
}
return await response.json();
}
/**
* Start a match (requires provider API key)
*/
async startMatch(apiKey: string, matchId: string): Promise<void> {
const response = await fetch(`${this.config.baseUrl}/v1/providers/multiplayer-bets/matches/${matchId}/start`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify({}),
});
if (!response.ok) {
throw new Error(`Failed to start match: ${response.statusText}`);
}
}
/**
* Settle a match with payouts (requires provider API key)
*/
async settleMatch(
apiKey: string,
matchId: string,
payouts: Array<{ player_id: string; amount: string }>
): Promise<void> {
const response = await fetch(`${this.config.baseUrl}/v1/providers/multiplayer-bets/matches/${matchId}/settle`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify({
payouts: payouts,
} as SettleMatchRequest),
});
if (!response.ok) {
throw new Error(`Failed to settle match: ${response.statusText}`);
}
}
/**
* Cancel a match (requires provider API key)
*/
async cancelMatch(apiKey: string, matchId: string, reason: string): Promise<void> {
const response = await fetch(`${this.config.baseUrl}/v1/providers/multiplayer-bets/matches/${matchId}/cancel`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify({
reason: reason,
}),
});
if (!response.ok) {
throw new Error(`Failed to cancel match: ${response.statusText}`);
}
}
}
// Example: Head-to-Head Poker Game
class PokerGame {
private multiplayer: MCAPMultiplayer;
private apiKey: string;
constructor(baseUrl: string, apiKey: string) {
this.multiplayer = new MCAPMultiplayer(baseUrl);
this.apiKey = apiKey;
}
/**
* Create a poker room
*/
async createPokerRoom(
accessToken: string,
creatorId: string,
buyIn: string,
sessionId: string
): Promise<string> {
const match = await this.multiplayer.createMatch(
accessToken,
creatorId,
2, // Heads-up poker
buyIn,
'poker_heads_up',
sessionId,
2 // 2 hour expiry
);
console.log(`Poker room created: ${match.match.id}`);
console.log(`Waiting for opponent. Buy-in: ${buyIn} wei`);
return match.match.id;
}
/**
* Join a poker room
*/
async joinPokerRoom(
accessToken: string,
matchId: string,
playerId: string,
sessionId: string
): Promise<MatchResponse> {
const match = await this.multiplayer.joinMatch(accessToken, matchId, playerId, sessionId);
if (match.match.current_players === match.match.max_players) {
// Start the match automatically when full
await this.multiplayer.startMatch(this.apiKey, matchId);
console.log(`Poker match started: ${matchId}`);
}
return match;
}
/**
* Settle poker game (called after game completion)
*/
async settlePokerGame(
matchId: string,
winnerId: string,
loserId: string,
totalPot: string
): Promise<void> {
// Winner takes 95% of pot (5% house edge)
const winnerAmount = (BigInt(totalPot) * BigInt(95) / BigInt(100)).toString();
await this.multiplayer.settleMatch(this.apiKey, matchId, [
{ player_id: winnerId, amount: winnerAmount },
{ player_id: loserId, amount: '0' }, // Loser gets nothing
]);
console.log(`Poker game settled. Winner: ${winnerId}, Payout: ${winnerAmount} wei`);
}
}
// Usage example
async function playPokerMatch() {
const game = new PokerGame(
'https://mcap-api-564778804231.us-east4.run.app',
'your-api-key-here'
);
// Player 1 creates room
const player1Token = 'player1-jwt-token';
const player1Id = 'player1-wallet-uuid';
const player1SessionId = 'player1-session-uuid';
const matchId = await game.createPokerRoom(
player1Token,
player1Id,
'10000000000000000000', // 10 tokens buy-in
player1SessionId
);
// Player 2 joins room
const player2Token = 'player2-jwt-token';
const player2Id = 'player2-wallet-uuid';
const player2SessionId = 'player2-session-uuid';
await game.joinPokerRoom(player2Token, matchId, player2Id, player2SessionId);
// ... Game logic happens here ...
// After game completes, settle the match
// Example: Player 1 wins
await game.settlePokerGame(
matchId,
player1Id, // winner
player2Id, // loser
'20000000000000000000' // total pot (both buy-ins)
);
}
Complete Integration Flow
// Complete end-to-end integration example
class MCAPGameIntegration {
private auth: MCAPAuth;
private session: MCAPSession;
private betting: MCAPBetting;
private multiplayer: MCAPMultiplayer;
private accessToken?: string;
private refreshToken?: string;
constructor(
baseUrl: string,
providerId: string,
apiKey: string
) {
this.auth = new MCAPAuth({ baseUrl, providerId });
this.session = new MCAPSession({ baseUrl, accessToken: '' });
this.betting = new MCAPBetting(baseUrl, apiKey);
this.multiplayer = new MCAPMultiplayer(baseUrl);
}
/**
* Complete user onboarding flow
*/
async onboardUser(): Promise<{
accessToken: string;
walletAddress: string;
availableTokens: any[]
}> {
try {
// Step 1: Authenticate user
console.log('🔐 Authenticating user...');
const tokens = await this.auth.authenticate();
this.accessToken = tokens.access_token;
this.refreshToken = tokens.refresh_token;
// Update session config with token
this.session = new MCAPSession({
baseUrl: this.session['config'].baseUrl,
accessToken: this.accessToken,
});
// Step 2: Get wallet address
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const walletAddress = await signer.getAddress();
// Step 3: Get available tokens
console.log('🪙 Fetching available tokens...');
const availableTokens = await this.getAvailableTokens();
console.log('✅ User onboarding complete');
return {
accessToken: this.accessToken,
walletAddress,
availableTokens,
};
} catch (error) {
console.error('❌ User onboarding failed:', error);
throw error;
}
}
/**
* Start a gaming session
*/
async startGameSession(tokenId: string, gameId: string): Promise<{
sessionId: string;
userBalance: string;
tokenSymbol: string;
}> {
if (!this.accessToken) {
throw new Error('User not authenticated');
}
try {
console.log('🎮 Starting game session...');
// Create session
const sessionId = await this.session.createSession(tokenId, gameId);
// Get session details to show user balance
const sessionDetails = await this.session.getSessionDetails(
sessionId,
this.betting['config'].apiKey
);
console.log('✅ Game session started');
return {
sessionId,
userBalance: sessionDetails.balance.balance,
tokenSymbol: sessionDetails.balance.token.symbol,
};
} catch (error) {
console.error('❌ Failed to start game session:', error);
throw error;
}
}
/**
* Check if user has sufficient funds for a bet
*/
async checkSufficientFunds(sessionId: string, betAmount: string): Promise<boolean> {
try {
const sessionDetails = await this.session.getSessionDetails(
sessionId,
this.betting['config'].apiKey
);
const availableBalance = BigInt(sessionDetails.balance.balance);
const requiredAmount = BigInt(betAmount);
return availableBalance >= requiredAmount;
} catch (error) {
console.error('Failed to check funds:', error);
return false;
}
}
/**
* Get available tokens for the current provider
*/
async getAvailableTokens(): Promise<any[]> {
const response = await fetch(`${this.auth['config'].baseUrl}/v1/tokens/search`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
filters: {},
sort: { columns: [], order: 'desc' },
limit: 50,
cursor: null,
}),
});
if (!response.ok) {
throw new Error('Failed to fetch tokens');
}
const data = await response.json();
return data.results || [];
}
/**
* Handle token refresh when access token expires
*/
async handleTokenRefresh(): Promise<boolean> {
if (!this.refreshToken) {
return false;
}
try {
const tokens = await this.auth.refreshToken(this.refreshToken);
this.accessToken = tokens.access_token;
this.refreshToken = tokens.refresh_token;
// Update session config
this.session = new MCAPSession({
baseUrl: this.session['config'].baseUrl,
accessToken: this.accessToken,
});
return true;
} catch (error) {
console.error('Token refresh failed:', error);
return false;
}
}
/**
* Complete single-player game flow
*/
async playSinglePlayerGame(
sessionId: string,
betAmount: string,
gameLogic: () => { isWin: boolean; multiplier: number; gameData: any }
): Promise<{
isWin: boolean;
payout: string;
newBalance: string;
gameData: any;
}> {
const betId = `game_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
try {
// Check funds first
const hasFunds = await this.checkSufficientFunds(sessionId, betAmount);
if (!hasFunds) {
throw new Error('Insufficient funds for bet');
}
// Place bet
console.log('🎲 Placing bet...');
await this.betting.placeBet(betId, sessionId, betAmount, {
game_type: 'single_player',
timestamp: new Date().toISOString(),
});
// Run game logic
console.log('🎮 Running game logic...');
const result = gameLogic();
// Calculate payout
const betAmountBigInt = BigInt(betAmount);
const payout = result.isWin
? (betAmountBigInt * BigInt(Math.floor(result.multiplier * 100)) / BigInt(100)).toString()
: '0';
// Resolve bet
console.log('💰 Resolving bet...');
const betResponse = await this.betting.resolveBet(
betId,
sessionId,
payout,
result.gameData,
{
multiplier: result.multiplier,
win: result.isWin,
resolved_at: new Date().toISOString(),
}
);
console.log(result.isWin ? '🎉 WIN!' : '😞 Loss');
return {
isWin: result.isWin,
payout: payout,
newBalance: betResponse.balance.balance,
gameData: result.gameData,
};
} catch (error) {
// Void bet on error
try {
await this.betting.voidBet(betId);
console.log('🔄 Bet voided due to error');
} catch (voidError) {
console.error('Failed to void bet:', voidError);
}
throw error;
}
}
}
// Usage: Complete game integration
async function runGameDemo() {
const gameIntegration = new MCAPGameIntegration(
'https://mcap-api-564778804231.us-east4.run.app',
'your-provider-id',
'your-api-key'
);
try {
// 1. Onboard user
const { accessToken, walletAddress, availableTokens } = await gameIntegration.onboardUser();
console.log(`User ${walletAddress} onboarded successfully`);
console.log(`Available tokens:`, availableTokens.map(t => t.symbol));
// 2. Start game session
const tokenId = availableTokens[0].id; // Use first available token
const gameId = 'your-game-id';
const { sessionId, userBalance, tokenSymbol } = await gameIntegration.startGameSession(tokenId, gameId);
console.log(`Session started. Balance: ${userBalance} ${tokenSymbol}`);
// 3. Play a game
const betAmount = '1000000000000000000'; // 1 token in wei
const gameResult = await gameIntegration.playSinglePlayerGame(
sessionId,
betAmount,
() => {
// Simple game logic: 50% chance to win 2x
const isWin = Math.random() < 0.5;
return {
isWin,
multiplier: isWin ? 2 : 0,
gameData: {
random_number: Math.random(),
win_threshold: 0.5,
},
};
}
);
console.log('Game result:', gameResult);
console.log(`New balance: ${gameResult.newBalance} ${tokenSymbol}`);
} catch (error) {
console.error('Game demo failed:', error);
}
}
Python Backend Examples
import requests
import json
from typing import Dict, Any, Optional, List
import time
import uuid
class MCAPClient:
def __init__(self, base_url: str, provider_id: str, api_key: str):
self.base_url = base_url.rstrip('/')
self.provider_id = provider_id
self.api_key = api_key
self.session = requests.Session()
def _make_request(
self,
method: str,
endpoint: str,
data: Optional[Dict] = None,
auth_token: Optional[str] = None,
use_api_key: bool = False
) -> Dict[Any, Any]:
"""Make HTTP request to MCAP API"""
url = f"{self.base_url}{endpoint}"
headers = {'Content-Type': 'application/json'}
if use_api_key:
headers['Authorization'] = f'Bearer {self.api_key}'
elif auth_token:
headers['Authorization'] = f'Bearer {auth_token}'
try:
response = self.session.request(
method=method,
url=url,
headers=headers,
json=data,
timeout=30
)
response.raise_for_status()
return response.json() if response.content else {}
except requests.exceptions.RequestException as e:
raise Exception(f"API request failed: {str(e)}")
# Authentication methods
def generate_nonce(self, wallet_address: str) -> str:
"""Generate nonce for wallet authentication"""
data = {
'wallet_address': wallet_address,
'provider_id': self.provider_id
}
response = self._make_request('POST', '/v1/auth/nonce', data)
return response['nonce']
def verify_login(self, wallet_address: str, signature: str, nonce: str) -> Dict[str, Any]:
"""Verify wallet signature and get JWT tokens"""
data = {
'provider_id': self.provider_id,
'wallet_address': wallet_address,
'signature': signature,
'nonce': nonce
}
return self._make_request('POST', '/v1/auth/login', data)
# Session management
def get_session_details(self, session_id: str) -> Dict[str, Any]:
"""Get session details (provider endpoint)"""
return self._make_request(
'GET',
f'/v1/providers/sessions/{session_id}',
use_api_key=True
)
# Betting operations
def place_bet(
self,
bet_id: str,
session_id: str,
amount: str,
bet_data: Optional[Dict] = None
) -> Dict[str, Any]:
"""Place a bet (locks user funds)"""
data = {
'bet_id': bet_id,
'session_id': session_id,
'amount': amount,
'bet_data': bet_data or {}
}
return self._make_request(
'POST',
'/v1/providers/bets/place-bet',
data,
use_api_key=True
)
def resolve_bet(
self,
bet_id: str,
session_id: str,
payout: str,
bet_data: Optional[Dict] = None,
result_data: Optional[Dict] = None
) -> Dict[str, Any]:
"""Resolve bet outcome"""
data = {
'bet_id': bet_id,
'session_id': session_id,
'payout': payout,
'bet_data': bet_data,
'result_data': result_data
}
return self._make_request(
'POST',
'/v1/providers/bets/bet-outcome',
data,
use_api_key=True
)
def void_bet(self, bet_id: str) -> None:
"""Void a bet (returns locked funds)"""
data = {'bet_id': bet_id}
self._make_request('POST', '/v1/providers/bets/void', data, use_api_key=True)
# Simple single-player game backend example
def play_single_player_game_backend(
mcap_client: MCAPClient,
session_id: str,
bet_amount: str # Amount in wei (e.g., "1000000000000000000" for 1 token)
) -> Dict[str, Any]:
"""Execute a simple single-player game"""
bet_id = f"game_{int(time.time())}_{uuid.uuid4().hex[:8]}"
try:
# Step 1: Place bet (locks user funds)
print("Placing bet...")
bet_response = mcap_client.place_bet(
bet_id=bet_id,
session_id=session_id,
amount=bet_amount,
bet_data={'game_type': 'simple_game', 'timestamp': time.time()}
)
# Step 2: YOUR GAME LOGIC GOES HERE
# - Generate random outcomes
# - Apply game rules
# - Calculate win/loss and multipliers
# - Determine final payout amount
import random
game_outcome = {
'is_win': random.random() < 0.5, # 50% win chance
'multiplier': 2.0 # 2x payout on win
}
# Step 3: Calculate payout based on game outcome
if game_outcome['is_win']:
# Winner gets 2x their bet
payout = str(int(bet_amount) * 2)
else:
# No payout on loss
payout = '0'
# Step 4: Settle bet with outcome
print("Settling bet...")
mcap_client.resolve_bet(
bet_id=bet_id,
session_id=session_id,
payout=payout,
bet_data={}, # Optional: additional bet data
result_data={
'multiplier': game_outcome['multiplier'],
'win': game_outcome['is_win']
# Include any relevant game result data
}
)
return {
'bet_id': bet_id,
'is_win': game_outcome['is_win'],
'payout': payout,
'balance_after': bet_response['balance']
}
except Exception as e:
# Void bet on error
try:
mcap_client.void_bet(bet_id)
print("Bet voided due to error")
except:
pass
raise e
# Usage example
def run_game_backend_example():
# Initialize MCAP client
mcap_client = MCAPClient(
base_url='https://mcap-api-564778804231.us-east4.run.app',
provider_id='your-provider-id',
api_key='your-api-key'
)
# Example: Process a game request
session_id = 'example-session-id'
bet_amount = '1000000000000000000' # 1 token in wei
try:
result = play_single_player_game_backend(mcap_client, session_id, bet_amount)
print(f"Game result: {result}")
if result['is_win']:
print(f"🎉 WIN! Payout: {result['payout']} wei")
else:
print(f"😞 Loss")
except Exception as e:
print(f"Error processing game: {e}")
if __name__ == "__main__":
run_game_backend_example()
Error Handling and Best Practices
Error Handling
class MCAPErrorHandler {
static async withRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 3,
delay: number = 1000
): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
// Don't retry on authentication errors
if (lastError.message.includes('401') || lastError.message.includes('unauthorized')) {
throw lastError;
}
if (attempt === maxRetries) {
throw lastError;
}
// Exponential backoff
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, attempt - 1)));
}
}
throw lastError!;
}
static async withBetVoidOnError<T>(
operation: () => Promise<T>,
betId: string,
voidBetFn: (betId: string) => Promise<void>
): Promise<T> {
try {
return await operation();
} catch (error) {
try {
await voidBetFn(betId);
console.log(`Bet ${betId} voided due to error`);
} catch (voidError) {
console.error(`Failed to void bet ${betId}:`, voidError);
}
throw error;
}
}
}
// Usage in bet placement
async function safeBetPlacement(betting: MCAPBetting, betParams: any) {
return await MCAPErrorHandler.withBetVoidOnError(
async () => {
// Place bet
await betting.placeBet(betParams.betId, betParams.sessionId, betParams.amount);
// Run game logic
const result = runGameLogic();
// Resolve bet
return await betting.resolveBet(
betParams.betId,
betParams.sessionId,
result.payout
);
},
betParams.betId,
(betId) => betting.voidBet(betId)
);
}
Security Best Practices
class SecurityUtils {
// Validate bet amounts to prevent manipulation
static validateBetAmount(amount: string, minBet: string, maxBet: string): boolean {
try {
const betAmount = BigInt(amount);
const minAmount = BigInt(minBet);
const maxAmount = BigInt(maxBet);
return betAmount >= minAmount && betAmount <= maxAmount;
} catch {
return false;
}
}
// Generate secure bet IDs
static generateBetId(prefix: string = 'bet'): string {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 15);
return `${prefix}_${timestamp}_${random}`;
}
// Validate session before operations
static async validateSession(
mcapClient: any,
sessionId: string,
apiKey: string
): Promise<boolean> {
try {
const session = await mcapClient.getSessionDetails(sessionId, apiKey);
const expiresAt = new Date(session.expires_at);
return expiresAt > new Date();
} catch {
return false;
}
}
}
This comprehensive examples file provides practical, runnable code for all major MCAP integration patterns. Developers can use these examples as starting points for their own game implementations, covering everything from basic authentication to complex multiplayer betting scenarios.