Overview

Hippo Labs is a non custodial liquidity routing layer for Solana that uses Jupiter v6 as its underlying routing engine. It is not a DEX - it is an aggregation and routing interface that sits on top of existing Solana DEXs and AMMs.

The public app at /app is a reference front end that demonstrates how wallets, dApps, and protocols can integrate best price routing without running their own aggregation infrastructure.

Hippo queries Jupiter's quote API to find optimal routes across multiple liquidity sources, then uses Jupiter's swap API to construct versioned transactions that users sign with their own wallet. Hippo does not custody funds at any point in the flow.

The stack is designed for builders who want to offer best execution to their users without managing DEX integrations, route optimization, or liquidity discovery.

Concepts

Understanding the core concepts behind Solana liquidity routing:

Route

A route is a sequence of swaps across one or more DEXs that converts an input token to an output token. Routes can be direct (single swap) or multi-hop (multiple swaps through intermediate tokens). Jupiter finds the route that maximizes output amount while minimizing price impact and transaction cost.

Quote

A quote is a simulated route that estimates output amount, price impact, and fees before execution. Quotes are computed off chain by querying on chain liquidity state across multiple DEXs. A quote is valid for a short time window and may become stale if market conditions change.

Slippage

Slippage is the acceptable difference between expected output (from the quote) and actual output (from execution). It is expressed in basis points (bps), where 50 bps = 0.5%. Higher slippage increases the chance of execution but exposes users to worse prices. Lower slippage protects users but may cause transactions to fail in volatile markets.

Input mint / Output mint

Mints are token addresses on Solana. The input mint is the token being sold and the output mint is the token being bought. For example, SOL has mint addressSo11111111111111111111111111111111111111112and USDC has mint addressEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.

Versioned Transaction

Versioned transactions are a Solana transaction format that supports address lookup tables (ALTs) to reduce transaction size. Jupiter returns swap transactions as versioned transactions serialized to base64. The user deserializes, signs, and sends the transaction to the Solana network.

Key principle: Users always control their private keys and sign transactions from their own wallet. Hippo constructs the transaction but never has access to user funds.

Architecture

Hippo Labs is built on four main layers:

Wallet Layer

Built with @solana/wallet-adapter-react. Supports Phantom, Solflare, and other standard Solana wallets. Handles connection state, public key access, and transaction signing. Users must approve every transaction explicitly.

Routing Layer

Queries https://quote-api.jup.ag/v6/quoteto get the best route. Jupiter v6 aggregates liquidity from Orca, Raydium, Meteora, Phoenix, and other Solana DEXs. Returns quotes with expected output, price impact, and route metadata.

Execution Layer

Calls https://quote-api.jup.ag/v6/swapwith the chosen quote and user public key. Jupiter returns a base64 encoded versioned transaction. The app deserializes the transaction, prompts the user to sign it, and sends it to the Solana network.

RPC Layer

Uses NEXT_PUBLIC_SOLANA_RPC or defaults to https://api.mainnet-beta.solana.com. The RPC endpoint is used for transaction submission and confirmation polling. Production deployments should use dedicated RPC providers for reliability.

System Flow

User Wallet
Connected via wallet adapter
Hippo UI
Next.js front end
↓ Query
Jupiter v6
Quote and swap APIs
↓ Transaction
Solana RPC
Mainnet beta
↓ Confirm
User Wallet
Tokens swapped

Quote Flow

The quote flow retrieves the best route for a given token pair and amount. Here's how it works in the Hippo app:

Step 1: Construct query parameters

const inputMint = "So11111111111111111111111111111111111111112";
const outputMint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const amount = 1_000_000_000; // 1 SOL in lamports
const slippageBps = 50; // 0.5%

const params = new URLSearchParams({
  inputMint,
  outputMint,
  amount: amount.toString(),
  slippageBps: slippageBps.toString(),
});

Step 2: Fetch quote from Jupiter

const response = await fetch(
  `https://quote-api.jup.ag/v6/quote?${params}`
);

if (!response.ok) {
  throw new Error("Failed to fetch quote");
}

const quote = await response.json();

Step 3: Extract route details

const {
  inAmount,
  outAmount,
  priceImpactPct,
  routePlan,
  otherAmountThreshold
} = quote;

console.log("Input:", inAmount, "lamports");
console.log("Expected output:", outAmount, "lamports");
console.log("Price impact:", priceImpactPct, "%");
console.log("Minimum output:", otherAmountThreshold, "lamports");

Implementation note: The app debounces user input to avoid hammering the Jupiter API. Quotes are refetched when amount, direction, or wallet connection changes.

Swap Flow

Once a quote is selected, the swap flow constructs and executes the transaction:

Step 1: Request swap transaction

const swapResponse = await fetch(
  "https://quote-api.jup.ag/v6/swap",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      quoteResponse: quote,
      userPublicKey: wallet.publicKey.toBase58(),
      wrapAndUnwrapSol: true,
      dynamicComputeUnitLimit: true,
      dynamicSlippage: true,
    }),
  }
);

const { swapTransaction } = await swapResponse.json();

Step 2: Deserialize and sign

import { VersionedTransaction } from "@solana/web3.js";

const txBuf = Buffer.from(swapTransaction, "base64");
const transaction = VersionedTransaction.deserialize(txBuf);

// User signs in their wallet (Phantom, Solflare, etc)
const signature = await wallet.sendTransaction(
  transaction,
  connection
);

Step 3: Confirm transaction

const latestBlockhash = await connection.getLatestBlockhash();

await connection.confirmTransaction(
  {
    signature,
    blockhash: latestBlockhash.blockhash,
    lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
  },
  "confirmed"
);

console.log("Swap confirmed:", signature);
console.log("View on Solscan:");
console.log(`https://solscan.io/tx/${signature}`);

Security note: The transaction is signed by the user's wallet, not by Hippo. The wallet will show transaction details including token amounts and accounts being modified. Users should verify these details before approving.

Configuration

Hippo uses environment variables for configuration. All variables are optional and have sensible defaults.

NEXT_PUBLIC_SOLANA_RPCoptional

Solana RPC endpoint URL. Defaults to the public mainnet endpoint.

NEXT_PUBLIC_SOLANA_RPC=https://api.mainnet-beta.solana.com

For production, use a dedicated RPC provider like Helius, Triton, or QuickNode.

DEFAULT_SLIPPAGE_BPSoptional

Default slippage tolerance in basis points. The app currently uses a hardcoded default of 50 bps (0.5%), but this can be made configurable.

DEFAULT_SLIPPAGE_BPS=50

Production recommendation: Run your own RPC infrastructure or use a paid RPC provider. Public endpoints have rate limits and may be unreliable under heavy load.

Integration Examples

Two common patterns for integrating Hippo's routing into your app:

Recipe 1: Inline swap button in a dApp

Embed a swap button directly into an existing screen without navigating to a separate page.

import { useWallet, useConnection } from '@solana/wallet-adapter-react';
import { useState } from 'react';

export function QuickSwapButton() {
  const { publicKey, sendTransaction } = useWallet();
  const { connection } = useConnection();
  const [loading, setLoading] = useState(false);

  async function handleSwap() {
    if (!publicKey) return;
    setLoading(true);

    try {
      // 1. Fetch quote
      const params = new URLSearchParams({
        inputMint: "So11111111111111111111111111111111111111112",
        outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
        amount: "1000000000",
        slippageBps: "50"
      });
      const quoteRes = await fetch(
        `https://quote-api.jup.ag/v6/quote?${params}`
      );
      const quote = await quoteRes.json();

      // 2. Get swap transaction
      const swapRes = await fetch(
        "https://quote-api.jup.ag/v6/swap",
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            quoteResponse: quote,
            userPublicKey: publicKey.toBase58(),
            wrapAndUnwrapSol: true,
            dynamicComputeUnitLimit: true,
            dynamicSlippage: true,
          })
        }
      );
      const { swapTransaction } = await swapRes.json();

      // 3. Send transaction
      const txBuf = Buffer.from(swapTransaction, "base64");
      const tx = VersionedTransaction.deserialize(txBuf);
      const sig = await sendTransaction(tx, connection);

      // 4. Confirm
      const blockhash = await connection.getLatestBlockhash();
      await connection.confirmTransaction({
        signature: sig,
        ...blockhash
      }, "confirmed");

      console.log("Swap complete:", sig);
    } catch (err) {
      console.error("Swap failed:", err);
    } finally {
      setLoading(false);
    }
  }

  return (
    <button onClick={handleSwap} disabled={!publicKey || loading}>
      {loading ? "Swapping..." : "Swap 1 SOL to USDC"}
    </button>
  );
}

Recipe 2: Wallet extension style flow

Build a swap interface inside a wallet context where users can swap any token pair.

// Minimal swap hook for wallet extensions
import { useCallback, useState } from 'react';
import { useWallet, useConnection } from '@solana/wallet-adapter-react';
import { VersionedTransaction } from '@solana/web3.js';

export function useJupiterSwap() {
  const { publicKey, sendTransaction } = useWallet();
  const { connection } = useConnection();
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const swap = useCallback(async (
    inputMint: string,
    outputMint: string,
    amount: number,
    slippageBps: number = 50
  ) => {
    if (!publicKey) throw new Error("Wallet not connected");

    setLoading(true);
    setError(null);

    try {
      const params = new URLSearchParams({
        inputMint,
        outputMint,
        amount: amount.toString(),
        slippageBps: slippageBps.toString()
      });

      const quoteRes = await fetch(
        `https://quote-api.jup.ag/v6/quote?${params}`
      );
      if (!quoteRes.ok) throw new Error("Quote failed");
      const quote = await quoteRes.json();

      const swapRes = await fetch("https://quote-api.jup.ag/v6/swap", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          quoteResponse: quote,
          userPublicKey: publicKey.toBase58(),
          wrapAndUnwrapSol: true,
          dynamicComputeUnitLimit: true,
          dynamicSlippage: true,
        })
      });
      if (!swapRes.ok) throw new Error("Swap preparation failed");
      const { swapTransaction } = await swapRes.json();

      const txBuf = Buffer.from(swapTransaction, "base64");
      const tx = VersionedTransaction.deserialize(txBuf);
      const signature = await sendTransaction(tx, connection);

      const blockhash = await connection.getLatestBlockhash();
      await connection.confirmTransaction({
        signature,
        ...blockhash
      }, "confirmed");

      return signature;
    } catch (err) {
      const msg = err instanceof Error ? err.message : "Unknown error";
      setError(msg);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [publicKey, connection, sendTransaction]);

  return { swap, loading, error };
}

// Usage in your wallet UI
function WalletSwapScreen() {
  const { swap, loading, error } = useJupiterSwap();

  async function handleSwap() {
    try {
      const sig = await swap(
        "So11111111111111111111111111111111111111112",
        "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
        1_000_000_000
      );
      alert(`Swap complete: ${sig}`);
    } catch (err) {
      console.error(err);
    }
  }

  return (
    <div>
      <button onClick={handleSwap} disabled={loading}>
        {loading ? "Swapping..." : "Swap"}
      </button>
      {error && <p>Error: {error}</p>}
    </div>
  );
}

Integration tip: Always display expected output and price impact to users before they confirm a swap. Consider adding a slippage selector for advanced users.

API Reference

Complete reference for Jupiter v6 APIs used by Hippo Labs.

Quote API

GET /v6/quotePublic

Retrieves the best swap route for a given token pair and amount.

Query Parameters

inputMint
string (required)
Token mint address to swap from
outputMint
string (required)
Token mint address to swap to
amount
string (required)
Amount in smallest unit (lamports for SOL)
slippageBps
number (optional)
Slippage tolerance in basis points. Default: 50
onlyDirectRoutes
boolean (optional)
Only return direct routes. Default: false
asLegacyTransaction
boolean (optional)
Return legacy transaction format. Default: false

Response Fields

inAmountInput amount in base units
outAmountExpected output amount in base units
priceImpactPctPrice impact as percentage
otherAmountThresholdMinimum output considering slippage
routePlanArray of swap steps through DEXs

Swap API

POST /v6/swapPublic

Constructs a transaction from a quote that can be signed and sent by the user.

Request Body

quoteResponse
object (required)
The full quote object from /v6/quote
userPublicKey
string (required)
User's wallet public key in base58
wrapAndUnwrapSol
boolean (optional)
Auto wrap/unwrap SOL. Default: true
dynamicComputeUnitLimit
boolean (optional)
Dynamic compute units. Default: false
dynamicSlippage
boolean (optional)
Adjust slippage dynamically. Default: false
prioritizationFeeLamports
number (optional)
Priority fee in lamports

Response

swapTransactionBase64 encoded versioned transaction
lastValidBlockHeightTransaction expiry block height

Rate limits: Public Jupiter APIs have rate limits. For production applications with high volume, consider contacting Jupiter for dedicated endpoints or implementing request throttling.

Advanced Patterns

Advanced integration patterns for production applications.

Pattern 1: Multi-hop routing with route inspection

Inspect and display the complete route path to users before execution.

function analyzeRoute(quote: JupiterQuote) {
  const { routePlan } = quote;

  const steps = routePlan.map((step, index) => ({
    stepNumber: index + 1,
    dex: step.swapInfo.label || 'Unknown DEX',
    inputMint: step.swapInfo.inputMint,
    outputMint: step.swapInfo.outputMint,
    // Additional metadata from step
  }));

  return {
    isDirectRoute: steps.length === 1,
    hopCount: steps.length,
    steps,
    dexesUsed: [...new Set(steps.map(s => s.dex))],
  };
}

// Usage
const quote = await fetchQuote(/* params */);
const routeInfo = analyzeRoute(quote);

console.log(`Route uses ${routeInfo.hopCount} hops`);
console.log(`DEXs: ${routeInfo.dexesUsed.join(', ')}`);

Pattern 2: Priority fee optimization

Dynamically adjust priority fees based on network congestion.

async function getOptimalPriorityFee(connection: Connection) {
  const recentFees = await connection.getRecentPrioritizationFees();

  if (recentFees.length === 0) {
    return 1000; // Minimum fee
  }

  // Use 75th percentile for reliable execution
  const sorted = recentFees
    .map(f => f.prioritizationFee)
    .sort((a, b) => a - b);

  const p75Index = Math.floor(sorted.length * 0.75);
  return sorted[p75Index];
}

// Include in swap request
const priorityFee = await getOptimalPriorityFee(connection);

const swapResponse = await fetch('/v6/swap', {
  method: 'POST',
  body: JSON.stringify({
    quoteResponse: quote,
    userPublicKey: wallet.publicKey.toBase58(),
    prioritizationFeeLamports: priorityFee,
    dynamicComputeUnitLimit: true,
  })
});

Pattern 3: Token balance verification

Verify sufficient balance before attempting swap to provide better UX.

import { TOKEN_PROGRAM_ID } from '@solana/spl-token';

async function checkTokenBalance(
  connection: Connection,
  wallet: PublicKey,
  mintAddress: string,
  requiredAmount: number
): Promise<{ sufficient: boolean; balance: number }> {
  if (mintAddress === 'So11111111111111111111111111111111111111112') {
    // Native SOL
    const balance = await connection.getBalance(wallet);
    return {
      sufficient: balance >= requiredAmount,
      balance,
    };
  }

  // SPL Token
  const tokenAccounts = await connection.getParsedTokenAccountsByOwner(
    wallet,
    { mint: new PublicKey(mintAddress) }
  );

  if (tokenAccounts.value.length === 0) {
    return { sufficient: false, balance: 0 };
  }

  const balance = tokenAccounts.value[0].account.data.parsed.info
    .tokenAmount.amount;

  return {
    sufficient: parseInt(balance) >= requiredAmount,
    balance: parseInt(balance),
  };
}

// Before swap
const { sufficient, balance } = await checkTokenBalance(
  connection,
  wallet.publicKey,
  inputMint,
  amountInBaseUnits
);

if (!sufficient) {
  throw new Error(`Insufficient balance: have ${balance}, need ${amountInBaseUnits}`);
}

Pattern 4: Quote comparison and refresh

Continuously refresh quotes and alert users to price changes before execution.

function useQuoteRefresh(
  inputMint: string,
  outputMint: string,
  amount: number,
  enabled: boolean
) {
  const [quote, setQuote] = useState<JupiterQuote | null>(null);
  const [priceChange, setPriceChange] = useState<number>(0);

  useEffect(() => {
    if (!enabled) return;

    const fetchAndCompare = async () => {
      const newQuote = await fetchQuote(inputMint, outputMint, amount);

      if (quote) {
        const oldOutput = parseInt(quote.outAmount);
        const newOutput = parseInt(newQuote.outAmount);
        const change = ((newOutput - oldOutput) / oldOutput) * 100;

        setPriceChange(change);

        // Alert on significant change
        if (Math.abs(change) > 2) {
          console.warn(`Price changed by ${change.toFixed(2)}%`);
        }
      }

      setQuote(newQuote);
    };

    fetchAndCompare();
    const interval = setInterval(fetchAndCompare, 10000); // Every 10s

    return () => clearInterval(interval);
  }, [inputMint, outputMint, amount, enabled]);

  return { quote, priceChange };
}

Error Handling

Comprehensive error handling strategies for production applications.

Common Error Scenarios

Quote API Errors
404:No route found between token pair
429:Rate limit exceeded
500:Jupiter API temporarily unavailable
Transaction Errors
SlippageToleranceExceeded:Price moved beyond slippage threshold
InsufficientFunds:User lacks required token balance
BlockhashNotFound:Transaction expired before confirmation
User rejected:User cancelled transaction in wallet

Robust Error Handler Implementation

class SwapError extends Error {
  constructor(
    message: string,
    public code: string,
    public userMessage: string,
    public retryable: boolean
  ) {
    super(message);
    this.name = 'SwapError';
  }
}

function handleSwapError(error: any): SwapError {
  // Network errors
  if (error.message?.includes('fetch')) {
    return new SwapError(
      error.message,
      'NETWORK_ERROR',
      'Network connection failed. Please check your internet.',
      true
    );
  }

  // Wallet errors
  if (error.message?.includes('User rejected')) {
    return new SwapError(
      error.message,
      'USER_REJECTED',
      'Transaction was cancelled.',
      false
    );
  }

  // Slippage errors
  if (error.message?.includes('slippage')) {
    return new SwapError(
      error.message,
      'SLIPPAGE_EXCEEDED',
      'Price moved too much. Try increasing slippage tolerance.',
      true
    );
  }

  // Insufficient balance
  if (error.message?.includes('insufficient')) {
    return new SwapError(
      error.message,
      'INSUFFICIENT_BALANCE',
      'Not enough tokens to complete this swap.',
      false
    );
  }

  // Rate limit
  if (error.status === 429) {
    return new SwapError(
      'Rate limit exceeded',
      'RATE_LIMIT',
      'Too many requests. Please wait a moment.',
      true
    );
  }

  // Transaction timeout
  if (error.message?.includes('timeout') || error.message?.includes('blockhash')) {
    return new SwapError(
      error.message,
      'TRANSACTION_TIMEOUT',
      'Transaction took too long. It may still complete.',
      true
    );
  }

  // Generic error
  return new SwapError(
    error.message || 'Unknown error',
    'UNKNOWN_ERROR',
    'Something went wrong. Please try again.',
    true
  );
}

// Usage
try {
  await executeSwap(/* params */);
} catch (error) {
  const swapError = handleSwapError(error);

  console.error(`[${swapError.code}] ${swapError.message}`);

  // Show user-friendly message
  showError(swapError.userMessage);

  // Log for debugging
  logError({
    code: swapError.code,
    message: swapError.message,
    retryable: swapError.retryable,
    timestamp: new Date().toISOString(),
  });

  // Retry if applicable
  if (swapError.retryable) {
    showRetryButton();
  }
}

Performance Optimization

Optimize quote fetching, caching, and transaction confirmation for better UX.

Quote Caching Strategy

class QuoteCache {
  private cache = new Map<string, { quote: JupiterQuote; timestamp: number }>();
  private readonly TTL = 5000; // 5 seconds

  private getCacheKey(inputMint: string, outputMint: string, amount: number): string {
    return `${inputMint}-${outputMint}-${amount}`;
  }

  get(inputMint: string, outputMint: string, amount: number): JupiterQuote | null {
    const key = this.getCacheKey(inputMint, outputMint, amount);
    const cached = this.cache.get(key);

    if (!cached) return null;

    const age = Date.now() - cached.timestamp;
    if (age > this.TTL) {
      this.cache.delete(key);
      return null;
    }

    return cached.quote;
  }

  set(inputMint: string, outputMint: string, amount: number, quote: JupiterQuote): void {
    const key = this.getCacheKey(inputMint, outputMint, amount);
    this.cache.set(key, {
      quote,
      timestamp: Date.now(),
    });
  }

  clear(): void {
    this.cache.clear();
  }
}

const quoteCache = new QuoteCache();

async function fetchQuoteWithCache(
  inputMint: string,
  outputMint: string,
  amount: number
): Promise<JupiterQuote> {
  // Check cache first
  const cached = quoteCache.get(inputMint, outputMint, amount);
  if (cached) {
    console.log('Using cached quote');
    return cached;
  }

  // Fetch fresh quote
  const quote = await fetchQuote(inputMint, outputMint, amount);
  quoteCache.set(inputMint, outputMint, amount, quote);

  return quote;
}

Debounced Input Handling

function useDebouncedValue<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

// Usage in component
function SwapComponent() {
  const [inputAmount, setInputAmount] = useState('');
  const debouncedAmount = useDebouncedValue(inputAmount, 500);

  useEffect(() => {
    if (debouncedAmount) {
      fetchQuote(debouncedAmount);
    }
  }, [debouncedAmount]);

  return (
    <input
      value={inputAmount}
      onChange={(e) => setInputAmount(e.target.value)}
      placeholder="Amount"
    />
  );
}

Parallel RPC Requests

Use Promise.all to fetch multiple pieces of data concurrently.

async function fetchSwapData(connection: Connection, wallet: PublicKey) {
  const [balance, tokenAccounts, recentBlockhash, priorityFees] = await Promise.all([
    connection.getBalance(wallet),
    connection.getParsedTokenAccountsByOwner(wallet, {
      programId: TOKEN_PROGRAM_ID
    }),
    connection.getLatestBlockhash(),
    connection.getRecentPrioritizationFees(),
  ]);

  return {
    solBalance: balance,
    tokens: tokenAccounts.value,
    blockhash: recentBlockhash,
    suggestedFee: calculateMedianFee(priorityFees),
  };
}

Testing Strategies

Testing approaches for swap functionality without spending real funds.

Testing Environments

Devnet
Use Solana devnet for integration testing. Request devnet SOL from faucets. Limited DEX liquidity available.
Mainnet
Test with small amounts on mainnet for production validation. Start with $1-5 swaps to verify full flow.
Simulation
Use transaction simulation to test without executing. Call connection.simulateTransaction() before sending.

Unit Test Example

import { describe, it, expect, vi } from 'vitest';

describe('Quote Fetching', () => {
  it('should fetch valid quote for SOL to USDC', async () => {
    const quote = await fetchQuote(
      'So11111111111111111111111111111111111111112',
      'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
      1_000_000_000 // 1 SOL
    );

    expect(quote).toBeDefined();
    expect(quote.inAmount).toBe('1000000000');
    expect(parseInt(quote.outAmount)).toBeGreaterThan(0);
    expect(quote.priceImpactPct).toBeLessThan(1); // Less than 1%
  });

  it('should handle invalid token pair', async () => {
    await expect(
      fetchQuote('invalid-mint', 'also-invalid', 1000)
    ).rejects.toThrow();
  });

  it('should respect slippage parameter', async () => {
    const quote = await fetchQuote(
      'So11111111111111111111111111111111111111112',
      'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
      1_000_000_000,
      100 // 1% slippage
    );

    expect(quote.slippageBps).toBe(100);
  });
});

Deployment Guide

Production deployment checklist and best practices.

Pre-deployment Checklist

Configure dedicated RPC endpoint

Use Helius, Triton, or QuickNode instead of public endpoints. Set NEXT_PUBLIC_SOLANA_RPC in production environment.

Enable monitoring and logging

Track quote fetch times, swap success rates, and error frequencies. Use services like Sentry, LogRocket, or DataDog.

Test with real funds (small amounts)

Execute test swaps with $1-10 before launching. Verify quote accuracy, transaction confirmation, and error handling.

Implement rate limiting

Prevent abuse by limiting quote fetches per user. Consider implementing IP-based or wallet-based rate limits.

Add analytics

Track user behavior: common token pairs, average swap size, completion rate. Use Google Analytics or Mixpanel.

Configure CDN and caching

Use Vercel, Netlify, or Cloudflare for static assets. Configure proper cache headers for optimal performance.

Environment Variables

# .env.production
NEXT_PUBLIC_SOLANA_RPC=https://your-dedicated-rpc.com
NEXT_PUBLIC_ENABLE_ANALYTICS=true
NEXT_PUBLIC_ENVIRONMENT=production

# Optional: Add your own API endpoint if proxying Jupiter
NEXT_PUBLIC_JUPITER_API_URL=https://quote-api.jup.ag/v6

Security note: Never commit API keys, RPC URLs, or other secrets to version control. Use environment variables and secret management systems.

Troubleshooting

Common issues and their solutions.

Quote fetching fails consistently

Possible causes:

  • Invalid token mint addresses
  • No liquidity available for pair
  • Amount too large or too small
  • RPC endpoint down or rate limited

Solutions:

  • Verify mint addresses on Solscan
  • Check Jupiter UI directly for the pair
  • Try different amount values
  • Switch to backup RPC endpoint

Transactions fail with slippage error

Possible causes:

  • Price moved between quote and execution
  • Slippage tolerance too low
  • High volatility in token pair

Solutions:

  • Increase slippage tolerance to 1-2%
  • Fetch fresh quote immediately before swap
  • Reduce swap amount to minimize impact
  • Wait for lower volatility period

Wallet connection issues

Possible causes:

  • Wallet extension not installed
  • Wrong network selected in wallet
  • Browser compatibility issues

Solutions:

  • Ensure Phantom or Solflare is installed
  • Switch wallet to Solana mainnet
  • Try different browser (Chrome, Brave)
  • Clear browser cache and reload

Transaction pending forever

Possible causes:

  • Network congestion
  • Insufficient priority fee
  • RPC not forwarding transaction

Solutions:

  • Check transaction on Solscan by signature
  • Wait 30-60 seconds before retry
  • Increase priority fee in next attempt
  • Use different RPC endpoint

High price impact warnings

Possible causes:

  • Low liquidity in pool
  • Swap amount too large relative to pool size
  • Illiquid token pair

Solutions:

  • Reduce swap amount
  • Split into multiple smaller swaps
  • Wait for more liquidity to be added
  • Consider alternative routes

Need more help? Join the Hippo Labs Discord or reach out on Twitter @hippolabs__ with your issue description and transaction signature if applicable.

Safety

Hippo Labs is an experimental interface. Users and integrators should follow these safety guidelines:

Verify token lists and contracts

Always verify that token mint addresses match the tokens you intend to swap. Scam tokens can have similar names to legitimate projects. Use Jupiter's strict token list mode when available.

Keep wallets and keys safe

Never share your private keys or seed phrases. Hippo does not request or store any wallet credentials. All signing happens in your browser wallet extension.

Non custodial by design

All swaps execute directly from your wallet. Hippo never takes custody of user funds. Transactions are atomic - they either complete fully or fail with no partial execution.

Test with small amounts first

This is beta software. Start with small test swaps to verify functionality before committing significant capital. Monitor price impact and slippage on larger trades.

Understand slippage and price impact

Slippage protects you from adverse price movements but may cause transactions to fail. Price impact shows how much your trade moves the market. High impact trades may get worse execution than expected.

Monitor RPC reliability

Public RPC endpoints can experience downtime or rate limiting. For production integrations, use dedicated RPC providers with SLAs and monitoring.

FAQ

What chains does Hippo support?

The current version focuses exclusively on Solana mainnet. The legacy Aptos version exists but is no longer actively developed. Future support for additional chains will depend on ecosystem liquidity and builder demand.

Is there a dedicated backend or is this purely client side?

The public app at /app is purely client side - it runs entirely in the browser. All API calls go directly from the user's browser to Jupiter's public APIs and the Solana RPC. There is no Hippo backend that routes or proxies transactions.

How does fee routing work? Does Hippo take a cut?

The public reference app does not take any fees. All trades route through Jupiter directly. In the future, integrators may be able to specify a referral fee that gets split between them and Jupiter. This would be transparent and configurable.

Can integrators add their own DEX as a liquidity source?

Hippo routes through Jupiter, so any DEX integrated with Jupiter will automatically be available as a liquidity source. To add a new DEX, work with the Jupiter team to integrate your protocol into their routing engine. Hippo will then discover it automatically.

What happens if a transaction fails?

Failed transactions do not transfer any tokens. You may lose a small amount of SOL for transaction fees depending on where the failure occurred. Common failure causes include slippage exceeded, insufficient balance, and network congestion.

How do I get price updates in real time?

The app refetches quotes when input changes. For real time price tracking, implement polling or WebSocket connections to Jupiter's quote API. Keep in mind that frequent polling may hit rate limits on public endpoints.

Is there a maximum swap size?

There is no hard limit imposed by Hippo. However, very large swaps may experience high price impact, exceed available liquidity, or fail due to slippage. Always check the price impact percentage before confirming large trades.

Where can I contact the team?

Reach out on Twitter at @hippolabs__ or join the Discord (link in footer). For integration support or partnership discussions, DM the team on Twitter.

Ready to try the routing layer?