Skip to main content

Complete Token Launch Flow

JavaScript/TypeScript (Web)

A complete example using modern web technologies:
import { Connection, PublicKey, Transaction } from '@solana/web3.js';
import bs58 from 'bs58';

interface TokenData {
  name: string;
  symbol: string;
  description?: string;
  image?: string;
  website?: string;
  twitter?: string;
  caEnding?: string;
  quoteToken?: string;
}

class TokenLaunchClient {
  private baseURL = 'https://api.zcombinator.io';
  private requestCount = 0;
  private windowStart = Date.now();

  constructor(private wallet: any) {}

  private async makeRequest(endpoint: string, options: RequestInit) {
    // Simple rate limiting
    const now = Date.now();
    if (now - this.windowStart >= 2 * 60 * 1000) {
      this.requestCount = 0;
      this.windowStart = now;
    }

    if (this.requestCount >= 4) {
      const waitTime = 2 * 60 * 1000 - (now - this.windowStart);
      throw new Error(`Rate limited. Wait ${Math.ceil(waitTime / 1000)} seconds.`);
    }

    const response = await fetch(`${this.baseURL}${endpoint}`, options);
    this.requestCount++;

    if (response.status === 429) {
      throw new Error('Rate limited by server. Please wait 2 minutes.');
    }

    const data = await response.json();

    if (!response.ok) {
      throw new Error(data.error || 'API request failed');
    }

    return data;
  }

  async launchToken(tokenData: TokenData): Promise<any> {
    if (!this.wallet.publicKey) {
      throw new Error('Wallet not connected');
    }

    // Step 1: Validate inputs
    this.validateTokenData(tokenData);

    // Step 2: Create unsigned transaction
    console.log('Creating launch transaction...');
    const launchResult = await this.makeRequest('/launch', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ...tokenData,
        payerPublicKey: this.wallet.publicKey.toString(),
        quoteToken: tokenData.quoteToken || "SOL" // Defaults to SOL if not specified
      })
    });

    // Step 3: Sign transaction
    console.log('Signing transaction...');
    const transaction = Transaction.from(bs58.decode(launchResult.transaction));
    const signedTransaction = await this.wallet.signTransaction(transaction);

    // Step 4: Confirm launch
    console.log('Confirming launch...');
    const confirmResult = await this.makeRequest('/confirm-launch', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        signedTransaction: bs58.encode(signedTransaction.serialize()),
        baseMint: launchResult.baseMint,
        metadataUrl: launchResult.metadataUrl,
        name: tokenData.name,
        symbol: tokenData.symbol,
        payerPublicKey: this.wallet.publicKey.toString()
      })
    });

    console.log('Token launched successfully!', confirmResult.transactionSignature);
    return confirmResult;
  }

  async checkClaimEligibility(tokenAddress: string, walletAddress: string) {
    const result = await this.makeRequest(
      `/claims/${tokenAddress}?wallet=${walletAddress}`,
      { method: 'GET' }
    );
    return result;
  }

  async claimTokens(tokenAddress: string, amount: string): Promise<any> {
    if (!this.wallet.publicKey) {
      throw new Error('Wallet not connected');
    }

    // Step 1: Check eligibility
    const eligibility = await this.checkClaimEligibility(
      tokenAddress,
      this.wallet.publicKey.toString()
    );

    if (!eligibility.canClaimNow) {
      throw new Error(`Cannot claim now. Next claim: ${eligibility.nextInflationTime}`);
    }

    if (BigInt(amount) > BigInt(eligibility.availableToClaim)) {
      throw new Error(`Amount ${amount} exceeds available ${eligibility.availableToClaim}`);
    }

    // Step 2: Create claim transaction
    console.log('Creating claim transaction...');
    const mintResult = await this.makeRequest('/claims/mint', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        tokenAddress,
        userWallet: this.wallet.publicKey.toString(),
        claimAmount: amount
      })
    });

    // Step 3: Sign transaction
    console.log('Signing claim transaction...');
    const transaction = Transaction.from(bs58.decode(mintResult.transaction));
    const signedTransaction = await this.wallet.signTransaction(transaction);

    // Step 4: Confirm claim
    console.log('Confirming claim...');
    const confirmResult = await this.makeRequest('/claims/confirm', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        signedTransaction: bs58.encode(signedTransaction.serialize()),
        transactionKey: mintResult.transactionKey
      })
    });

    console.log('Tokens claimed successfully!', confirmResult.transactionSignature);
    return confirmResult;
  }

  async verifyToken(address: string) {
    return this.makeRequest(`/verify-token/${address}`, { method: 'GET' });
  }

  async getHealth() {
    return this.makeRequest('/health', { method: 'GET' });
  }

  private validateTokenData(data: TokenData) {
    if (!data.name?.trim()) throw new Error('Token name is required');
    if (!data.symbol?.trim()) throw new Error('Token symbol is required');
    if (data.caEnding && data.caEnding.length > 3) {
      throw new Error('CA ending must be 3 characters or less');
    }
    if (data.caEnding && /[0OIl]/.test(data.caEnding)) {
      throw new Error('CA ending contains invalid Base58 characters');
    }
  }
}

// Usage example
async function example() {
  // Assume wallet is connected (Phantom, Solflare, etc.)
  const wallet = window.solana;
  await wallet.connect();

  const client = new TokenLaunchClient(wallet);

  try {
    // Check API health
    const health = await client.getHealth();
    console.log('API Health:', health);

    // Launch a token
    const launchResult = await client.launchToken({
      name: "My Awesome Token",
      symbol: "MAT",
      description: "A demonstration token",
      image: "https://example.com/token-logo.png",
      website: "https://mytoken.com",
      twitter: "https://twitter.com/mytoken",
      caEnding: "MAT",
      quoteToken: "SOL" // Optional: "SOL" or "ZC"
    });

    console.log('Launched token:', launchResult.baseMint);

    // Verify the token was created
    const verification = await client.verifyToken(launchResult.baseMint);
    console.log('Token exists:', verification.exists);

    // Check claim eligibility
    const eligibility = await client.checkClaimEligibility(
      launchResult.baseMint,
      wallet.publicKey.toString()
    );
    console.log('Can claim:', eligibility.canClaimNow);

    // Claim tokens if eligible
    if (eligibility.canClaimNow && BigInt(eligibility.availableToClaim) > 0) {
      const claimResult = await client.claimTokens(
        launchResult.baseMint,
        eligibility.availableToClaim
      );
      console.log('Claimed tokens:', claimResult.transactionSignature);
    }

  } catch (error) {
    console.error('Error:', error.message);
  }
}

Python (Backend)

Server-side integration for automated systems:
import requests
import time
import base58
from solders.keypair import Keypair
from solders.transaction import Transaction
from typing import Dict, Any, Optional

class TokenLaunchClient:
    def __init__(self, wallet_keypair: Keypair):
        self.base_url = 'https://api.zcombinator.io'
        self.wallet_keypair = wallet_keypair
        self.session = requests.Session()
        self.request_count = 0
        self.window_start = time.time()

    def _make_request(self, endpoint: str, method: str = 'GET', data: Optional[Dict] = None) -> Dict[str, Any]:
        """Make API request with rate limiting"""
        # Simple rate limiting
        now = time.time()
        if now - self.window_start >= 120:  # 2 minutes
            self.request_count = 0
            self.window_start = now

        if self.request_count >= 4:
            wait_time = 120 - (now - self.window_start)
            raise Exception(f"Rate limited. Wait {wait_time:.0f} seconds.")

        url = f"{self.base_url}{endpoint}"

        try:
            if method == 'POST':
                response = self.session.post(url, json=data, timeout=30)
            else:
                response = self.session.get(url, timeout=30)

            self.request_count += 1

            if response.status_code == 429:
                raise Exception("Rate limited by server. Please wait 2 minutes.")

            response.raise_for_status()
            return response.json()

        except requests.RequestException as e:
            raise Exception(f"API request failed: {str(e)}")

    def launch_token(self, token_data: Dict[str, str]) -> Dict[str, Any]:
        """Launch a new token"""
        print("Creating launch transaction...")

        # Step 1: Create unsigned transaction
        launch_result = self._make_request('/launch', 'POST', {
            **token_data,
            'payerPublicKey': str(self.wallet_keypair.pubkey()),
            'quoteToken': token_data.get('quoteToken', 'SOL')  # Optional: 'SOL' or 'ZC'
        })

        # Step 2: Sign transaction
        print("Signing transaction...")
        transaction_bytes = base58.b58decode(launch_result['transaction'])
        transaction = Transaction.from_bytes(transaction_bytes)
        transaction.sign([self.wallet_keypair])

        # Step 3: Confirm launch
        print("Confirming launch...")
        confirm_result = self._make_request('/confirm-launch', 'POST', {
            'signedTransaction': base58.b58encode(bytes(transaction)).decode(),
            'baseMint': launch_result['baseMint'],
            'metadataUrl': launch_result['metadataUrl'],
            'name': token_data['name'],
            'symbol': token_data['symbol'],
            'payerPublicKey': str(self.wallet_keypair.pubkey())
        })

        print(f"Token launched successfully! Transaction: {confirm_result['transactionSignature']}")
        return confirm_result

    def check_claim_eligibility(self, token_address: str, wallet_address: str) -> Dict[str, Any]:
        """Check claim eligibility for a wallet"""
        return self._make_request(f'/claims/{token_address}?wallet={wallet_address}')

    def claim_tokens(self, token_address: str, amount: str) -> Dict[str, Any]:
        """Claim available tokens"""
        wallet_address = str(self.wallet_keypair.pubkey())

        # Step 1: Check eligibility
        eligibility = self.check_claim_eligibility(token_address, wallet_address)

        if not eligibility['canClaimNow']:
            raise Exception(f"Cannot claim now. Next claim: {eligibility['nextInflationTime']}")

        if int(amount) > int(eligibility['availableToClaim']):
            raise Exception(f"Amount {amount} exceeds available {eligibility['availableToClaim']}")

        # Step 2: Create claim transaction
        print("Creating claim transaction...")
        mint_result = self._make_request('/claims/mint', 'POST', {
            'tokenAddress': token_address,
            'userWallet': wallet_address,
            'claimAmount': amount
        })

        # Step 3: Sign transaction
        print("Signing claim transaction...")
        transaction_bytes = base58.b58decode(mint_result['transaction'])
        transaction = Transaction.from_bytes(transaction_bytes)
        transaction.sign([self.wallet_keypair])

        # Step 4: Confirm claim
        print("Confirming claim...")
        confirm_result = self._make_request('/claims/confirm', 'POST', {
            'signedTransaction': base58.b58encode(bytes(transaction)).decode(),
            'transactionKey': mint_result['transactionKey']
        })

        print(f"Tokens claimed successfully! Transaction: {confirm_result['transactionSignature']}")
        return confirm_result

    def verify_token(self, address: str) -> Dict[str, Any]:
        """Verify if a token exists"""
        return self._make_request(f'/verify-token/{address}')

    def get_health(self) -> Dict[str, Any]:
        """Check API health"""
        return self._make_request('/health')

# Usage example
def main():
    # Load your wallet keypair
    # keypair = Keypair.from_bytes(your_private_key_bytes)
    keypair = Keypair()  # Generate new keypair for demo

    client = TokenLaunchClient(keypair)

    try:
        # Check API health
        health = client.get_health()
        print("API Health:", health)

        # Launch a token
        token_data = {
            'name': 'My Python Token',
            'symbol': 'PYT',
            'description': 'A token launched from Python',
            'caEnding': 'PYT',
            'quoteToken': 'SOL'  # Optional: 'SOL' or 'ZC'
        }

        launch_result = client.launch_token(token_data)
        token_address = launch_result['baseMint']

        # Verify token was created
        verification = client.verify_token(token_address)
        print(f"Token exists: {verification['exists']}")

        # Check claim eligibility
        eligibility = client.check_claim_eligibility(
            token_address,
            str(keypair.pubkey())
        )
        print(f"Can claim: {eligibility['canClaimNow']}")
        print(f"Available to claim: {eligibility['availableToClaim']}")

        # Claim tokens if eligible
        if eligibility['canClaimNow'] and int(eligibility['availableToClaim']) > 0:
            claim_result = client.claim_tokens(
                token_address,
                eligibility['availableToClaim']
            )
            print(f"Claimed tokens: {claim_result['transactionSignature']}")

    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    main()

Go (Backend)

High-performance server integration:
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

type TokenLaunchClient struct {
    BaseURL     string
    HTTPClient  *http.Client
    RequestCount int
    WindowStart  time.Time
}

type TokenData struct {
    Name        string `json:"name"`
    Symbol      string `json:"symbol"`
    Description string `json:"description,omitempty"`
    Image       string `json:"image,omitempty"`
    Website     string `json:"website,omitempty"`
    Twitter     string `json:"twitter,omitempty"`
    CAEnding    string `json:"caEnding,omitempty"`
    PayerPubkey string `json:"payerPublicKey"`
    QuoteToken  string `json:"quoteToken,omitempty"`
}

type LaunchResult struct {
    Success         bool   `json:"success"`
    Transaction     string `json:"transaction"`
    BaseMint        string `json:"baseMint"`
    MetadataURL     string `json:"metadataUrl"`
    Message         string `json:"message"`
}

type ClaimEligibility struct {
    WalletAddress      string    `json:"walletAddress"`
    TokenAddress       string    `json:"tokenAddress"`
    TotalClaimed       string    `json:"totalClaimed"`
    AvailableToClaim   string    `json:"availableToClaim"`
    MaxClaimableNow    string    `json:"maxClaimableNow"`
    TokensPerPeriod    string    `json:"tokensPerPeriod"`
    InflationPeriods   int       `json:"inflationPeriods"`
    TokenLaunchTime    time.Time `json:"tokenLaunchTime"`
    NextInflationTime  time.Time `json:"nextInflationTime"`
    CanClaimNow        bool      `json:"canClaimNow"`
    TimeUntilNextClaim int64     `json:"timeUntilNextClaim"`
}

func NewTokenLaunchClient() *TokenLaunchClient {
    return &TokenLaunchClient{
        BaseURL: "https://api.zcombinator.io",
        HTTPClient: &http.Client{
            Timeout: 30 * time.Second,
        },
        WindowStart: time.Now(),
    }
}

func (c *TokenLaunchClient) makeRequest(method, endpoint string, body interface{}) ([]byte, error) {
    // Rate limiting
    now := time.Now()
    if now.Sub(c.WindowStart) >= 2*time.Minute {
        c.RequestCount = 0
        c.WindowStart = now
    }

    if c.RequestCount >= 4 {
        waitTime := 2*time.Minute - now.Sub(c.WindowStart)
        return nil, fmt.Errorf("rate limited. wait %v", waitTime)
    }

    var reqBody io.Reader
    if body != nil {
        jsonData, err := json.Marshal(body)
        if err != nil {
            return nil, err
        }
        reqBody = bytes.NewBuffer(jsonData)
    }

    req, err := http.NewRequest(method, c.BaseURL+endpoint, reqBody)
    if err != nil {
        return nil, err
    }

    if body != nil {
        req.Header.Set("Content-Type", "application/json")
    }

    resp, err := c.HTTPClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    c.RequestCount++

    respBody, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    if resp.StatusCode == 429 {
        return nil, fmt.Errorf("rate limited by server")
    }

    if resp.StatusCode >= 400 {
        var errorResp map[string]interface{}
        json.Unmarshal(respBody, &errorResp)
        if msg, ok := errorResp["error"].(string); ok {
            return nil, fmt.Errorf("API error: %s", msg)
        }
        return nil, fmt.Errorf("API error: status %d", resp.StatusCode)
    }

    return respBody, nil
}

func (c *TokenLaunchClient) CreateLaunchTransaction(tokenData TokenData) (*LaunchResult, error) {
    respBody, err := c.makeRequest("POST", "/launch", tokenData)
    if err != nil {
        return nil, err
    }

    var result LaunchResult
    err = json.Unmarshal(respBody, &result)
    if err != nil {
        return nil, err
    }

    return &result, nil
}

func (c *TokenLaunchClient) GetClaimEligibility(tokenAddress, walletAddress string) (*ClaimEligibility, error) {
    endpoint := fmt.Sprintf("/claims/%s?wallet=%s", tokenAddress, walletAddress)
    respBody, err := c.makeRequest("GET", endpoint, nil)
    if err != nil {
        return nil, err
    }

    var result ClaimEligibility
    err = json.Unmarshal(respBody, &result)
    if err != nil {
        return nil, err
    }

    return &result, nil
}

func (c *TokenLaunchClient) VerifyToken(address string) (map[string]interface{}, error) {
    endpoint := fmt.Sprintf("/verify-token/%s", address)
    respBody, err := c.makeRequest("GET", endpoint, nil)
    if err != nil {
        return nil, err
    }

    var result map[string]interface{}
    err = json.Unmarshal(respBody, &result)
    if err != nil {
        return nil, err
    }

    return result, nil
}

func (c *TokenLaunchClient) GetHealth() (map[string]interface{}, error) {
    respBody, err := c.makeRequest("GET", "/health", nil)
    if err != nil {
        return nil, err
    }

    var result map[string]interface{}
    err = json.Unmarshal(respBody, &result)
    if err != nil {
        return nil, err
    }

    return result, nil
}

func main() {
    client := NewTokenLaunchClient()

    // Check API health
    health, err := client.GetHealth()
    if err != nil {
        fmt.Printf("Health check failed: %v\n", err)
        return
    }
    fmt.Printf("API Health: %+v\n", health)

    // Create launch transaction
    tokenData := TokenData{
        Name:        "Go Token",
        Symbol:      "GOTKN",
        Description: "A token launched from Go",
        CAEnding:    "GO",
        PayerPubkey: "YourWalletPublicKeyHere",
        QuoteToken:  "SOL", // Optional: "SOL" or "ZC"
    }

    result, err := client.CreateLaunchTransaction(tokenData)
    if err != nil {
        fmt.Printf("Launch failed: %v\n", err)
        return
    }

    fmt.Printf("Launch transaction created: %s\n", result.BaseMint)
    fmt.Printf("Transaction to sign: %s\n", result.Transaction)

    // Note: In a real implementation, you would:
    // 1. Deserialize the transaction
    // 2. Sign it with your wallet
    // 3. Call /confirm-launch with the signed transaction
}

Testing and Development

Local Testing Setup

// test-client.js
const { TokenLaunchClient } = require('./token-launch-client');

class TestClient extends TokenLaunchClient {
  constructor() {
    super(null); // No wallet for testing
    this.baseURL = 'https://api.zcombinator.io'; // Use real API for integration tests
  }

  async testHealthEndpoint() {
    try {
      const health = await this.getHealth();
      console.log('✅ Health endpoint working:', health.status);
      return true;
    } catch (error) {
      console.error('❌ Health endpoint failed:', error.message);
      return false;
    }
  }

  async testTokenVerification() {
    try {
      // Test with known token (USDC)
      const result = await this.verifyToken('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
      console.log('✅ Token verification working:', result.exists);
      return true;
    } catch (error) {
      console.error('❌ Token verification failed:', error.message);
      return false;
    }
  }

  async testRateLimiting() {
    console.log('Testing rate limiting...');
    let successCount = 0;
    let rateLimited = false;

    for (let i = 0; i < 6; i++) {
      try {
        await this.getHealth();
        successCount++;
        console.log(`Request ${i + 1}: Success`);
      } catch (error) {
        if (error.message.includes('rate limit')) {
          rateLimited = true;
          console.log(`Request ${i + 1}: Rate limited (expected)`);
          break;
        } else {
          console.error(`Request ${i + 1}: Unexpected error:`, error.message);
        }
      }

      // Small delay between requests
      await new Promise(resolve => setTimeout(resolve, 100));
    }

    if (rateLimited && successCount >= 4) {
      console.log('✅ Rate limiting working correctly');
      return true;
    } else {
      console.log('❌ Rate limiting not working as expected');
      return false;
    }
  }

  async runAllTests() {
    console.log('Running API integration tests...\n');

    const tests = [
      { name: 'Health Endpoint', fn: () => this.testHealthEndpoint() },
      { name: 'Token Verification', fn: () => this.testTokenVerification() },
      { name: 'Rate Limiting', fn: () => this.testRateLimiting() }
    ];

    let passed = 0;
    let total = tests.length;

    for (const test of tests) {
      console.log(`Running: ${test.name}`);
      const result = await test.fn();
      if (result) passed++;
      console.log('');
    }

    console.log(`\nTest Results: ${passed}/${total} passed`);

    if (passed === total) {
      console.log('🎉 All tests passed!');
    } else {
      console.log('❌ Some tests failed. Check your API configuration.');
    }
  }
}

// Run tests
if (require.main === module) {
  const testClient = new TestClient();
  testClient.runAllTests().catch(console.error);
}

module.exports = { TestClient };

Error Handling Examples

class RobustTokenClient {
  constructor(wallet) {
    this.wallet = wallet;
    this.baseURL = 'https://api.zcombinator.io';
    this.maxRetries = 3;
  }

  async makeRequestWithRetry(endpoint, options, retryCount = 0) {
    try {
      const response = await fetch(`${this.baseURL}${endpoint}`, options);

      if (response.status === 429) {
        // Rate limited - wait and retry
        if (retryCount < this.maxRetries) {
          const delay = Math.min(1000 * (2 ** retryCount), 30000);
          console.log(`Rate limited. Waiting ${delay}ms before retry...`);
          await new Promise(resolve => setTimeout(resolve, delay));
          return this.makeRequestWithRetry(endpoint, options, retryCount + 1);
        }
        throw new Error('Rate limit exceeded. Please try again later.');
      }

      if (response.status >= 500) {
        // Server error - retry with exponential backoff
        if (retryCount < this.maxRetries) {
          const delay = 1000 * (2 ** retryCount);
          console.log(`Server error. Retrying in ${delay}ms...`);
          await new Promise(resolve => setTimeout(resolve, delay));
          return this.makeRequestWithRetry(endpoint, options, retryCount + 1);
        }
      }

      const data = await response.json();

      if (!response.ok) {
        throw new Error(data.error || `HTTP ${response.status}`);
      }

      return data;

    } catch (error) {
      if (error.name === 'TypeError' && error.message.includes('fetch')) {
        // Network error
        if (retryCount < this.maxRetries) {
          const delay = 1000 * (2 ** retryCount);
          console.log(`Network error. Retrying in ${delay}ms...`);
          await new Promise(resolve => setTimeout(resolve, delay));
          return this.makeRequestWithRetry(endpoint, options, retryCount + 1);
        }
        throw new Error('Network connection failed. Please check your internet connection.');
      }
      throw error;
    }
  }

  async launchTokenSafely(tokenData) {
    try {
      // Validate inputs first
      this.validateTokenData(tokenData);

      // Check wallet connection
      if (!this.wallet.publicKey) {
        throw new Error('Please connect your wallet first.');
      }

      // Check API health
      try {
        await this.makeRequestWithRetry('/health', { method: 'GET' });
      } catch (error) {
        throw new Error('API is currently unavailable. Please try again later.');
      }

      // Proceed with launch...
      const launchResult = await this.makeRequestWithRetry('/launch', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          ...tokenData,
          payerPublicKey: this.wallet.publicKey.toString(),
          quoteToken: tokenData.quoteToken || "SOL" // Optional: "SOL" or "ZC"
        })
      });

      return launchResult;

    } catch (error) {
      // Categorize and handle different types of errors
      if (error.message.includes('Missing required fields')) {
        throw new Error('Please fill in all required fields (name, symbol).');
      } else if (error.message.includes('CA ending')) {
        throw new Error('Custom address ending must be 3 characters or less with valid characters.');
      } else if (error.message.includes('rate limit')) {
        throw new Error('Too many requests. Please wait a moment before trying again.');
      } else if (error.message.includes('Network')) {
        throw new Error('Connection failed. Please check your internet and try again.');
      } else {
        // Log unexpected errors for debugging
        console.error('Unexpected error:', error);
        throw new Error('An unexpected error occurred. Please try again or contact support.');
      }
    }
  }

  validateTokenData(data) {
    const errors = [];

    if (!data.name?.trim()) errors.push('Token name is required');
    if (!data.symbol?.trim()) errors.push('Token symbol is required');

    if (data.symbol && data.symbol.length > 10) {
      errors.push('Token symbol must be 10 characters or less');
    }

    if (data.caEnding && data.caEnding.length > 3) {
      errors.push('CA ending must be 3 characters or less');
    }

    if (data.caEnding && /[0OIl]/.test(data.caEnding)) {
      errors.push('CA ending contains invalid characters (0, O, I, l not allowed)');
    }

    if (data.website && !this.isValidURL(data.website)) {
      errors.push('Please enter a valid website URL');
    }

    if (errors.length > 0) {
      throw new Error(errors.join('. '));
    }
  }

  isValidURL(string) {
    try {
      new URL(string);
      return true;
    } catch {
      return false;
    }
  }
}
These examples provide complete, production-ready code that you can adapt for your specific use case. Each example includes proper error handling, rate limiting, validation, and follows best practices for API integration.
I