Skip to main content

API Architecture

The Token Launch API uses a partial transaction signing model that provides security while maintaining user control:
Phase 1: Prepare
  • API creates unsigned transaction
  • User receives transaction for signing
  • No sensitive data exposed
Phase 2: Complete
  • User signs transaction with their wallet
  • API adds protocol signature
  • Transaction submitted to blockchain
  • User Control: Users sign all transactions
  • Key Isolation: API never sees user private keys
  • Transparency: All operations visible on-chain
  • Verification: Users can inspect transactions before signing
  • Stateless: No session management required
  • RESTful: Standard HTTP methods and status codes
  • JSON: All data exchange in JSON format
  • Rate Limited: IP-based throttling for fair usage

Integration Patterns

Web Application Integration

Perfect for dApps and web interfaces:
import { useWallet } from '@solana/wallet-adapter-react';
import { Transaction } from '@solana/web3.js';
import bs58 from 'bs58';

function TokenLauncher() {
  const { publicKey, signTransaction } = useWallet();
  const [launching, setLaunching] = useState(false);

  const launchToken = async (tokenData) => {
    if (!publicKey || !signTransaction) return;

    setLaunching(true);
    try {
      // Step 1: Create unsigned transaction
      const launchResponse = await fetch('https://api.zcombinator.io/launch', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          ...tokenData,
          payerPublicKey: publicKey.toString(),
          quoteToken: "SOL" // Optional: "SOL" or "ZC"
        })
      });

      const launchResult = await launchResponse.json();

      // Step 2: Sign transaction
      const transaction = Transaction.from(bs58.decode(launchResult.transaction));
      const signedTransaction = await signTransaction(transaction);

      // Step 3: Confirm launch
      const confirmResponse = await fetch('https://api.zcombinator.io/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: publicKey.toString()
        })
      });

      const confirmResult = await confirmResponse.json();

      if (confirmResult.success) {
        console.log('Token launched!', confirmResult.transactionSignature);
        return confirmResult;
      }
    } catch (error) {
      console.error('Launch failed:', error);
      throw error;
    } finally {
      setLaunching(false);
    }
  };

  return (
    <div>
      {/* Your UI components */}
      <button onClick={() => launchToken(formData)} disabled={launching}>
        {launching ? 'Launching...' : 'Launch Token'}
      </button>
    </div>
  );
}

Backend Service Integration

For automated systems and batch operations:
from flask import Flask, request, jsonify
import requests
from solders.pubkey import Pubkey
from solders.transaction import Transaction
import base58

app = Flask(__name__)

class TokenLaunchService:
    def __init__(self):
        self.api_base = 'https://api.zcombinator.io'
        self.session = requests.Session()

    def launch_token(self, token_data, wallet_keypair):
        """Launch a token programmatically"""

        # Step 1: Create launch transaction
        launch_response = self.session.post(
            f'{self.api_base}/launch',
            json={
                **token_data,
                'payerPublicKey': str(wallet_keypair.pubkey()),
                'quoteToken': 'SOL'  # Optional: 'SOL' or 'ZC'
            }
        )
        launch_response.raise_for_status()
        launch_result = launch_response.json()

        # Step 2: Sign transaction
        transaction_bytes = base58.b58decode(launch_result['transaction'])
        transaction = Transaction.from_bytes(transaction_bytes)

        # Sign with your keypair
        transaction.sign([wallet_keypair])

        # Step 3: Confirm launch
        confirm_response = self.session.post(
            f'{self.api_base}/confirm-launch',
            json={
                'signedTransaction': base58.b58encode(bytes(transaction)).decode(),
                'baseMint': launch_result['baseMint'],
                'metadataUrl': launch_result['metadataUrl'],
                'name': token_data['name'],
                'symbol': token_data['symbol'],
                'payerPublicKey': str(wallet_keypair.pubkey())
            }
        )
        confirm_response.raise_for_status()

        return confirm_response.json()

@app.route('/internal/launch-token', methods=['POST'])
def internal_launch():
    """Internal endpoint for launching tokens"""
    data = request.get_json()

    # Your wallet management logic here
    wallet_keypair = load_wallet_keypair(data['wallet_id'])

    service = TokenLaunchService()
    result = service.launch_token(data['token_data'], wallet_keypair)

    return jsonify(result)

Mobile App Integration

For React Native and mobile applications:
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { Transaction } from '@solana/web3.js';
import bs58 from 'bs58';

export const useTokenLauncher = () => {
  const { connection } = useConnection();
  const { publicKey, signTransaction } = useWallet();

  const launchToken = async (tokenData) => {
    if (!publicKey || !signTransaction) {
      throw new Error('Wallet not connected');
    }

    try {
      // Create unsigned transaction
      const response = await fetch('https://api.zcombinator.io/launch', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          ...tokenData,
          payerPublicKey: publicKey.toString(),
          quoteToken: "SOL", // Optional: "SOL" or "ZC"
        }),
      });

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

      const result = await response.json();

      // Sign transaction
      const transaction = Transaction.from(bs58.decode(result.transaction));
      const signedTransaction = await signTransaction(transaction);

      // Confirm launch
      const confirmResponse = await fetch('https://api.zcombinator.io/confirm-launch', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          signedTransaction: bs58.encode(signedTransaction.serialize()),
          baseMint: result.baseMint,
          metadataUrl: result.metadataUrl,
          name: tokenData.name,
          symbol: tokenData.symbol,
          payerPublicKey: publicKey.toString(),
        }),
      });

      const confirmResult = await confirmResponse.json();

      if (!confirmResponse.ok) {
        throw new Error(confirmResult.error);
      }

      return confirmResult;
    } catch (error) {
      console.error('Token launch error:', error);
      throw error;
    }
  };

  return { launchToken };
};

Common Integration Patterns

Error Handling

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

      if (response.status === 429) {
        // Rate limited - wait before retry
        const delay = Math.min(1000 * (2 ** attempt), 30000);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }

      if (response.status >= 500) {
        // Server error - retry with backoff
        if (attempt < maxRetries - 1) {
          const delay = 1000 * (2 ** attempt);
          await new Promise(resolve => setTimeout(resolve, delay));
          continue;
        }
      }

      return response;
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
      await new Promise(resolve => setTimeout(resolve, 1000 * (2 ** attempt)));
    }
  }
}
const handleLaunch = async (tokenData) => {
  setStatus('Creating transaction...');

  try {
    const launchResult = await createLaunchTransaction(tokenData);
    setStatus('Please sign the transaction in your wallet...');

    const signedTransaction = await signTransaction(launchResult.transaction);
    setStatus('Submitting to blockchain...');

    const confirmResult = await confirmLaunch(signedTransaction);
    setStatus('Token launched successfully!');

    return confirmResult;
  } catch (error) {
    if (error.message.includes('rate limit')) {
      setStatus('Rate limited. Please wait before trying again.');
    } else if (error.message.includes('User rejected')) {
      setStatus('Transaction cancelled by user.');
    } else {
      setStatus('Launch failed. Please try again.');
    }
    throw error;
  }
};
const 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.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');
  }

  try {
    new PublicKey(data.payerPublicKey);
  } catch {
    errors.push('Invalid payer public key');
  }

  return errors;
};

const launchToken = async (tokenData) => {
  const validationErrors = validateTokenData(tokenData);
  if (validationErrors.length > 0) {
    throw new Error(`Validation failed: ${validationErrors.join(', ')}`);
  }

  // Proceed with launch...
};

Performance Optimization

class BatchAPIClient {
  constructor() {
    this.requestQueue = [];
    this.processing = false;
  }

  async queueRequest(endpoint, options) {
    return new Promise((resolve, reject) => {
      this.requestQueue.push({ endpoint, options, resolve, reject });
      this.processQueue();
    });
  }

  async processQueue() {
    if (this.processing || this.requestQueue.length === 0) return;

    this.processing = true;

    while (this.requestQueue.length > 0) {
      const batch = this.requestQueue.splice(0, 4); // Respect rate limit

      const promises = batch.map(async ({ endpoint, options, resolve, reject }) => {
        try {
          const response = await fetch(endpoint, options);
          const result = await response.json();
          resolve(result);
        } catch (error) {
          reject(error);
        }
      });

      await Promise.all(promises);

      // Wait for rate limit window if needed
      if (this.requestQueue.length > 0) {
        await new Promise(resolve => setTimeout(resolve, 2 * 60 * 1000));
      }
    }

    this.processing = false;
  }
}
class CachedAPIClient {
  constructor() {
    this.cache = new Map();
    this.cacheTimeout = 60000; // 1 minute
  }

  async get(url, cacheable = false) {
    const cacheKey = url;

    if (cacheable && this.cache.has(cacheKey)) {
      const { data, timestamp } = this.cache.get(cacheKey);
      if (Date.now() - timestamp < this.cacheTimeout) {
        return data;
      }
    }

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

    if (cacheable) {
      this.cache.set(cacheKey, {
        data,
        timestamp: Date.now()
      });
    }

    return data;
  }

  // Cache token verification permanently
  async verifyToken(address) {
    return this.get(
      `https://api.zcombinator.io/verify-token/${address}`,
      true // Cache token verification
    );
  }

  // Don't cache claim eligibility (changes over time)
  async getClaimEligibility(tokenAddress, wallet) {
    return this.get(
      `https://api.zcombinator.io/claims/${tokenAddress}?wallet=${wallet}`,
      false // Don't cache eligibility
    );
  }
}

Best Practices

  • Never store private keys in client-side applications
  • Validate all inputs before sending to API
  • Use HTTPS for all API communications
  • Implement proper error handling to avoid exposing sensitive information
  • Verify transaction contents before signing
  • Show loading states during API calls
  • Provide clear error messages with actionable guidance
  • Implement proper retry logic for transient failures
  • Cache appropriate responses to reduce API calls
  • Respect rate limits to maintain service availability
  • Track API response times and error rates
  • Monitor rate limit usage to avoid 429 errors
  • Log transaction signatures for debugging
  • Implement health checks using /health endpoint
  • Set up alerts for persistent API failures
I