Skip to content

Latest commit

 

History

History
571 lines (439 loc) · 12.8 KB

File metadata and controls

571 lines (439 loc) · 12.8 KB

API Integration Guide

📋 Document Summary

What This Document Covers:

  • Practical integration patterns for LaunchAgencyBot Web UI API
  • Error handling with retry logic and exponential backoff
  • Cache control strategies for static vs dynamic data
  • Session management and status polling for long-running operations
  • Bulk operations with concurrency control
  • Wallet validation and network selection patterns
  • Production recommendations (authentication, rate limiting, error reporting)

Sections in This Document:

Related Documentation:

Context Tags: #api #integration #patterns #best-practices #error-handling #production


Error Handling

Retry Logic for Service Unavailability

Implement exponential backoff for 503 Service Unavailable responses:

async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      if (!response.ok) {
        const error = await response.json();

        // Retry on 503 (service temporarily unavailable)
        if (response.status === 503 && attempt < maxRetries - 1) {
          const delay = Math.min(1000 * Math.pow(2, attempt), 5000);
          await new Promise(r => setTimeout(r, delay));
          continue;
        }

        throw new Error(error.detail || error.message);
      }

      return await response.json();
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;

      // Wait before retry
      const delay = Math.min(1000 * Math.pow(2, attempt), 5000);
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

Handling Validation Errors

FastAPI returns detailed validation errors with detail field. Check response.status === 400 for validation failures and display error.detail to users as validation feedback.


Cache Control

Respecting Cache Headers

The API sets different cache policies for static vs dynamic data:

// Cache-first for static data (tags, configuration)
async function fetchWithCache(url, ttl = 3600) {
  const cached = localStorage.getItem(url);

  if (cached) {
    const { data, timestamp } = JSON.parse(cached);
    if (Date.now() - timestamp < ttl * 1000) {
      return data;
    }
  }

  const response = await fetch(url);
  const data = await response.json();

  localStorage.setItem(url, JSON.stringify({
    data,
    timestamp: Date.now()
  }));

  return data;
}

// Use cached tags
const tags = await fetchWithCache('/api/generation/tags', 3600);

No-Cache for Dynamic Data

Always fetch fresh data for dynamic endpoints:

// Wallets, memecoins, trends should never be cached
const wallets = await fetch('/api/wallets', {
  cache: 'no-store',
  headers: {
    'Cache-Control': 'no-cache'
  }
});

Session Management

Polling Generation Status

Long-running operations use session-based tracking:

async function startGeneration(request) {
  // Start generation
  const { session_id } = await fetch('/api/generation/create', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(request)
  }).then(r => r.json());

  // Poll status until complete
  return pollGenerationStatus(session_id);
}

async function pollGenerationStatus(sessionId, interval = 1000) {
  while (true) {
    const status = await fetch(`/api/generation/status/${sessionId}`)
      .then(r => r.json());

    if (status.status === 'completed') {
      // Fetch final results
      return fetch(`/api/generation/results/${sessionId}`)
        .then(r => r.json());
    }

    if (status.status === 'failed') {
      // Check retry availability
      const canRetry = status.retry_count < 3;

      if (canRetry) {
        await fetch(`/api/generation/session/${sessionId}/retry`, {
          method: 'POST'
        });
        // Continue polling
      } else {
        throw new Error(`Generation failed: ${status.error_message}`);
      }
    }

    // Update UI with progress
    updateProgress(status.progress, status.stage_description);

    // Wait before next poll
    await new Promise(r => setTimeout(r, interval));
  }
}

Session Cleanup

Sessions auto-expire after 1 hour. Clean up local state:

function cleanupCompletedSessions() {
  const sessions = JSON.parse(localStorage.getItem('sessions') || '[]');
  const now = Date.now();

  const active = sessions.filter(s => {
    const age = now - s.timestamp;
    return age < 3600000; // 1 hour
  });

  localStorage.setItem('sessions', JSON.stringify(active));
}

Bulk Operations

Batch Approvals with Progress Tracking

async function bulkApprove(tokenAddresses, onProgress) {
  const results = [];

  for (let i = 0; i < tokenAddresses.length; i++) {
    const address = tokenAddresses[i];

    try {
      const result = await fetch(
        `/api/memecoins/approve/${address}`,
        { method: 'POST' }
      ).then(r => r.json());

      results.push({
        address,
        success: true,
        message: result.message
      });

    } catch (error) {
      results.push({
        address,
        success: false,
        error: error.message
      });
    }

    // Update progress
    onProgress({
      completed: i + 1,
      total: tokenAddresses.length,
      current: address,
      results
    });

    // Rate limiting
    if (i < tokenAddresses.length - 1) {
      await new Promise(r => setTimeout(r, 100));
    }
  }

  return results;
}

Parallel Operations with Concurrency Control

async function parallelOperations(items, operation, concurrency = 5) {
  const results = [];
  const queue = [...items];

  async function worker() {
    while (queue.length > 0) {
      const item = queue.shift();
      if (!item) break;

      try {
        const result = await operation(item);
        results.push({ item, success: true, result });
      } catch (error) {
        results.push({ item, success: false, error: error.message });
      }
    }
  }

  // Start workers
  const workers = Array(concurrency).fill(null).map(() => worker());
  await Promise.all(workers);

  return results;
}

// Usage
const results = await parallelOperations(
  tokenAddresses,
  async (address) => {
    const response = await fetch(`/api/memecoins/pending/${address}`);
    return response.json();
  },
  5 // 5 concurrent requests
);

Wallet Validation

Pre-Launch Validation

Always validate wallet readiness before launches:

async function validateWalletForLaunch(walletAddress, requiredSol) {
  // Refresh wallet from blockchain
  const wallet = await fetch(`/api/wallets/${walletAddress}/refresh`, {
    method: 'POST'
  }).then(r => r.json());

  // Check status
  if (wallet.status !== 'FUNDED') {
    throw new Error(
      `Wallet not ready: status is ${wallet.status}. ` +
      `Fund wallet with at least 0.25 SOL.`
    );
  }

  // Check balance (with buffer for fees)
  const required = requiredSol + 0.1; // +0.1 SOL for fees
  if (wallet.sol_balance < required) {
    throw new Error(
      `Insufficient balance: has ${wallet.sol_balance} SOL, ` +
      `needs ${required} SOL (${requiredSol} + 0.1 fees)`
    );
  }

  return wallet;
}

Network Selection

Development vs Production

Always specify network for blockchain operations:

const network = process.env.NODE_ENV === 'production'
  ? 'mainnet'
  : 'devnet';

async function launchToken(request) {
  // Development - use devnet
  const response = await fetch(
    `/api/launches?network=${network}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(request)
    }
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.detail);
  }

  return response.json();
}

Pagination Strategies

Infinite Scroll with Cursor

class PaginatedFetcher {
  constructor(endpoint, pageSize = 20) {
    this.endpoint = endpoint;
    this.pageSize = pageSize;
    this.currentPage = 1;
    this.hasMore = true;
    this.items = [];
  }

  async fetchNext() {
    if (!this.hasMore) return [];

    const url = `${this.endpoint}?page=${this.currentPage}&limit=${this.pageSize}`;
    const response = await fetch(url).then(r => r.json());

    this.items.push(...response.memecoins);
    this.hasMore = response.has_next;
    this.currentPage++;

    return response.memecoins;
  }

  reset() {
    this.currentPage = 1;
    this.hasMore = true;
    this.items = [];
  }
}

// Usage
const fetcher = new PaginatedFetcher('/api/memecoins/pending', 50);
const firstPage = await fetcher.fetchNext();

Production Recommendations

1. Authentication

Implement JWT-based authentication for production:

// Add JWT token to requests
async function authenticatedFetch(url, options = {}) {
  const token = localStorage.getItem('jwt_token');

  return fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    }
  });
}

2. Rate Limiting

Implement client-side rate limiting:

class RateLimiter {
  constructor(maxRequests = 10, windowMs = 1000) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
    this.requests = [];
  }

  async throttle() {
    const now = Date.now();
    this.requests = this.requests.filter(t => now - t < this.windowMs);

    if (this.requests.length >= this.maxRequests) {
      const oldestRequest = this.requests[0];
      const waitTime = this.windowMs - (now - oldestRequest);
      await new Promise(r => setTimeout(r, waitTime));
    }

    this.requests.push(Date.now());
  }
}

const limiter = new RateLimiter(10, 1000); // 10 req/sec

async function makeRequest(url) {
  await limiter.throttle();
  return fetch(url);
}

3. Error Reporting

Integrate error tracking for production:

async function apiRequest(url, options = {}) {
  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      const error = await response.json();

      // Report to error tracking service
      reportError({
        type: 'API_ERROR',
        url,
        status: response.status,
        message: error.detail,
        timestamp: new Date().toISOString()
      });

      throw new Error(error.detail);
    }

    return response.json();
  } catch (error) {
    // Report network errors
    reportError({
      type: 'NETWORK_ERROR',
      url,
      message: error.message,
      timestamp: new Date().toISOString()
    });

    throw error;
  }
}

4. Request Timeouts

Set reasonable timeouts for all requests:

async function fetchWithTimeout(url, options = {}, timeout = 30000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    });

    clearTimeout(timeoutId);
    return response;
  } catch (error) {
    clearTimeout(timeoutId);

    if (error.name === 'AbortError') {
      throw new Error(`Request timeout after ${timeout}ms`);
    }

    throw error;
  }
}

WebSocket Integration (Future)

Real-Time Updates

For future WebSocket support (trends, generation status):

class RealtimeUpdates {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.listeners = new Map();
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      const listeners = this.listeners.get(data.type) || [];
      listeners.forEach(callback => callback(data));
    };

    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
      // Reconnect with exponential backoff
    };
  }

  subscribe(type, callback) {
    if (!this.listeners.has(type)) {
      this.listeners.set(type, []);
    }
    this.listeners.get(type).push(callback);
  }

  unsubscribe(type, callback) {
    const listeners = this.listeners.get(type) || [];
    const index = listeners.indexOf(callback);
    if (index > -1) listeners.splice(index, 1);
  }
}

Last Updated: January 2025 - LaunchAgencyBot Web UI v2.0