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
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_RPCoptionalSolana RPC endpoint URL. Defaults to the public mainnet endpoint.
NEXT_PUBLIC_SOLANA_RPC=https://api.mainnet-beta.solana.comFor production, use a dedicated RPC provider like Helius, Triton, or QuickNode.
DEFAULT_SLIPPAGE_BPSoptionalDefault 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=50Production 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/quotePublicRetrieves the best swap route for a given token pair and amount.
Query Parameters
inputMintoutputMintamountslippageBpsonlyDirectRoutesasLegacyTransactionResponse Fields
inAmountInput amount in base unitsoutAmountExpected output amount in base unitspriceImpactPctPrice impact as percentageotherAmountThresholdMinimum output considering slippageroutePlanArray of swap steps through DEXsSwap API
POST /v6/swapPublicConstructs a transaction from a quote that can be signed and sent by the user.
Request Body
quoteResponseuserPublicKeywrapAndUnwrapSoldynamicComputeUnitLimitdynamicSlippageprioritizationFeeLamportsResponse
swapTransactionBase64 encoded versioned transactionlastValidBlockHeightTransaction expiry block heightRate 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 ErrorsTransaction ErrorsRobust 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
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
Use Helius, Triton, or QuickNode instead of public endpoints. Set NEXT_PUBLIC_SOLANA_RPC in production environment.
Track quote fetch times, swap success rates, and error frequencies. Use services like Sentry, LogRocket, or DataDog.
Execute test swaps with $1-10 before launching. Verify quote accuracy, transaction confirmation, and error handling.
Prevent abuse by limiting quote fetches per user. Consider implementing IP-based or wallet-based rate limits.
Track user behavior: common token pairs, average swap size, completion rate. Use Google Analytics or Mixpanel.
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.