Home

Introduction

What Flow Trades is, how it works, and why you'd use it.

What is Flow Trades?

Flow Trades is a self-hosted Solana swap API. You run a single binary on your own server, point it at a Geyser node, and it gives you a REST API for quoting and executing token swaps across 19 DEXes — Raydium, Orca, Meteora, PumpFun, and more.

You run it on your own infrastructure, with your own fee collection, fully self-hosted.

How do users interact with it?

The typical flow is two API calls:

  1. QuoteGET /quote with an input token, output token, and amount. Flow finds the best route across all pools and returns the expected output, price impact, and route details.
  2. SwapPOST /swap with the quote and the user's wallet address. Flow builds an unsigned Solana transaction wrapped in the on-chain router (for fee collection) and returns it as base64. The user signs it with their wallet and submits it to the network.

That's it. Your frontend or bot calls /quote, shows the user what they'll get, then calls /swap, has them sign, and submits. Flow never holds keys or funds — it only builds transactions.

How does fee collection work?

Every swap transaction is wrapped in a CPI to the Flow router program (FLoWxx...UsNqm), an immutable Solana program deployed on mainnet. The router takes a 0.5% fee from the output token after the swap completes and the slippage check passes. 70% of the fee goes to the integrator (you), 30% to the protocol. Fees accumulate in token accounts automatically — SOL swaps collect SOL fees, USDC swaps collect USDC fees, etc.

The router program's upgrade authority has been permanently burned. Nobody can change the fee rate, treasury wallet, or program logic.

What do I need to run it?

Quick Start

Get Flow Trades running in under 5 minutes.

0. Download

Grab the latest binary from GitHub Releases.

chmod +x flow-trades

1. Configure

# config.toml
rpc_url = "https://your-solana-rpc.com"

[streaming]
geyser_endpoint = "http://your-geyser-node:10000"

2. Run

./flow-trades

Server starts on http://127.0.0.1:8080. Pools discovered automatically from Geyser, persisted to SQLite.

3. Quote

curl "http://localhost:8080/quote?\
input=So11111111111111111111111111111111111111112&\
output=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&\
amount=1000000000"

4. Swap

QUOTE=$(curl -s "http://localhost:8080/quote?input=So111...&output=EPjFW...&amount=1000000000")

curl -X POST http://localhost:8080/swap \
  -H "Content-Type: application/json" \
  -d "{\"wallet\": \"YOUR_PUBKEY\", \"quote\": $QUOTE}"

Returns a base64-encoded unsigned VersionedTransaction. Sign with your wallet and submit via sendTransaction.

Configuration

OptionEnv VarDefaultDescription
rpc_urlRPC_URLrequiredSolana RPC endpoint
geyser_endpointGEYSER_ENDPOINT--Yellowstone gRPC endpoint
geyser_tokenGEYSER_TOKEN--gRPC auth token (if required)
listenLISTEN_ADDR127.0.0.1:8080API listen address
referral_accountREFERRAL_ACCOUNT--Your wallet for 70% fee share
pool_dbPOOL_DB_PATH./pools.dbSQLite pool database path
alt_addressesALT_ADDRESSES--Address Lookup Tables (comma-separated)
swap_stream_enabledSWAP_STREAM_ENABLEDtrueEnable /swap-stream (broadcast hub + parser + SOL oracle)
swap_stream_buffer_sizeSWAP_STREAM_BUFFER_SIZE8192Per-subscriber lossy broadcast capacity
sol_price_refresh_secsSOL_PRICE_REFRESH_SECS10SOL/USD oracle refresh cadence
log_levelLOG_LEVELwarntrace, debug, info, warn, error

Requirements

API Reference

GET /quote
POST /swap
GET /health
GET /metrics
GET /program-id-to-label
WS /quote-ws
WS /swap-stream

GET /quote

Find the best swap route for a token pair.

Parameters

ParameterTypeRequiredDefaultDescription
inputstringYes--Input token mint (base58)
outputstringYes--Output token mint (base58)
amountstringYes--Raw amount in smallest units
slippageu16No50Slippage in basis points (50 = 0.5%)
direct_onlyboolNotruefalse enables multi-hop routing
dexesstringNoallWhitelist DEXes (comma-separated)
excludestringNononeBlacklist DEXes (comma-separated)
modestringNoExactInExactIn or ExactOut

Response

{
  "input_token": "So11111111111111111111111111111111111111112",
  "amount_in": "1000000000",
  "output_token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
  "amount_out": "162340000",
  "minimum_out": "161528300",
  "mode": "ExactIn",
  "slippage_bps": 50,
  "price_impact": "0.12",
  "routes": [
    {
      "pool": {
        "pool_address": "...",
        "dex": "Orca",
        "input_token": "So111...",
        "output_token": "EPjFW...",
        "amount_in": "1000000000",
        "amount_out": "162340000",
        "fee": "2500000",
        "fee_token": "So111..."
      },
      "percent": 100
    }
  ],
  "platform_fee": {
    "amount": "811700",
    "fee_bps": 50,
    "fee_token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "side": "output"
  },
  "slot": 412875000,
  "quote_time_ms": 0.04
}

POST /swap

Build an unsigned versioned transaction from a quote. All swaps routed through the on-chain router.

Request Body

FieldTypeRequiredDefaultDescription
walletstringYes--Signer public key (base58)
quoteobjectYes--Full response from GET /quote
priority_feeu64No5000Priority fee in lamports
compute_limitu32NoautoCompute unit limit
simulateboolNofalseSimulate first (returns CU + logs)
tipobjectNo--{ "address": "...", "lamports": 10000 }

Response

{
  "transaction": "base64-encoded-unsigned-VersionedTransaction...",
  "block_height": 412875100,
  "compute_limit": 400000,
  "priority_fee": 5000
}

GET /health

Server status, pool counts, stream stats, blockhash age.

{
  "status": "ok",
  "poolCacheSize": 1333,
  "registrySize": 5787,
  "poolDbSize": 5787,
  "streamUpdates": 19754,
  "streamErrors": 0,
  "blockhashCacheAgeMs": 200,
  "lastStreamUpdateMs": 3
}

GET /metrics

Prometheus exposition format. Counters for quotes, stream updates, errors. Gauges for cache/registry/SQLite sizes.

WS /quote-ws

Push-based quote streaming. Send a subscription, receive quote updates at your interval.

Subscription Message

{
  "input_mint": "So11111111111111111111111111111111111111112",
  "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
  "amount": 1000000000,
  "slippage_bps": 50,
  "interval_ms": 1000
}

Minimum interval: 100ms. Server pushes full QuoteResponse objects at the specified rate.

WS /swap-stream

Live broadcast of every confirmed DEX swap parsed off the block stream, enriched with native price (output ui per input ui + inverted) and USD price (per output token + total trade size).

Distinct from /quote-ws — that streams projected prices for a chosen pair; this streams observed swaps as they confirm on-chain.

Protocol

  1. Connect to ws://your-host:8080/swap-stream.
  2. (Optional) Send a JSON filter as the first frame. Skipping defaults to "match all" after a 5 s grace.
  3. Server replies once with {"type":"subscribed"}, then streams {"type":"swap", …} JSON frames.
  4. Server emits {"type":"ping"} every 30 s as a keep-alive.
  5. Slow consumers see {"type":"lagged","skipped":N} instead of being disconnected — the connection stays open. (Backed by tokio::sync::broadcast — lossy by design so a slow client never blocks the parsing pipeline.)

Subscribe Filter

{
  "type": "subscribe",
  "filter": {
    "dex": ["Raydium V4", "Pumpup Bonding"],
    "mint": "9U3FcH1Z3vZFHvN5KrkHHkuJSPKKnBBLpPQ1FkezxAai",
    "pool": "AQxKPt88jGP1DiwRbqweoo74Yi2o3fMTATAbDDA6BVLT",
    "min_amount_usd": 10.0
  }
}

All filter fields optional and AND'd together. dex matches any of the listed labels (see Supported DEXes). mint matches a swap's input or output. min_amount_usd drops swaps whose amount_usd is null or below the threshold.

Wire Format

{
  "type": "swap",
  "signature": "5xY...",
  "slot": 412341234,
  "block_time": 1745000000,
  "dex": "Pumpup Bonding",
  "pool": "AQxKPt88jGP1DiwRbqweoo74Yi2o3fMTATAbDDA6BVLT",
  "user": "7AakHWVQ42d1FyaG7pqvE9ArmhWUpcpvd4yhKKd2dBnt",
  "input":  { "mint": "So11...", "amount": "1000000",     "decimals": 9, "ui_amount": "0.001"   },
  "output": { "mint": "9U3F...", "amount": "24637903819", "decimals": 6, "ui_amount": "24637.9" },
  "price_native":          "24637903.82",
  "price_native_inverted": "0.0000000406",
  "price_usd":             "0.00000647",
  "amount_usd":            "0.16"
}
FieldDescription
signatureSolana transaction signature (base58)
slotSlot the swap landed in
block_timeUnix epoch seconds (RPC-provided; nullable)
dexHuman-readable DEX label (matches /program-id-to-label)
poolPool address (or pool_sol_account for bonding curves)
userFee payer of the underlying tx — the user who did the swap
input.amountAtomic units, decimal string (avoids JSON-number precision loss)
input.ui_amountamount / 10^decimals formatted as a decimal string
price_nativeOutput ui per input ui (output amount the user got, divided by what they paid)
price_native_invertedInput ui per output ui
price_usdUSD per output token (one-sided). null when neither side is a quote mint.
amount_usdTotal trade size in USD. null when neither side is a quote mint.

USD Enrichment

Node.js Example

import WebSocket from "ws";

const ws = new WebSocket("ws://localhost:8080/swap-stream");

ws.on("open", () => ws.send(JSON.stringify({
  type: "subscribe",
  filter: { min_amount_usd: 10 }
})));

ws.on("message", (raw) => {
  const msg = JSON.parse(raw);
  if (msg.type === "swap") {
    console.log(
      `${msg.dex} ${msg.input.ui_amount} ${msg.input.mint.slice(0,4)}... ` +
      `-> ${msg.output.ui_amount} ${msg.output.mint.slice(0,4)}... ` +
      `$${msg.amount_usd}`
    );
  } else if (msg.type === "ping") {
    ws.send(JSON.stringify({ type: "pong" }));
  } else if (msg.type === "lagged") {
    console.warn(`stream lagged: ${msg.skipped} swaps dropped for slow consumer`);
  }
});

Disabling

Set --swap-stream-enabled false (or SWAP_STREAM_ENABLED=false) to disable the broadcast hub, parser, and SOL/USD oracle entirely. Saves a small amount of memory and the periodic background HTTP fetch.

Verified throughput

A 20-minute mainnet soak streamed 340,613 swaps at 283.84 swaps/sec sustained with 99.1% USD coverage, zero broadcast lag, zero reconnects, and zero consumer errors. Server stayed at 16.6% CPU and ~300 MB RSS with no leak.

Fee Structure

PropertyValue
Router ProgramFLoWxxKoBrZtNj5NTPuy1tZcSU6Nnjtz7v5snrrUsNqm
Platform Fee0.5% (50 bps) on output token
Integrator Share70% of fee
Protocol Share30% of fee
Upgrade Authoritynone (immutable)

Fees are always taken from the output token after the swap completes and slippage is verified. Collected in any SPL token — SOL, USDC, memecoins, Token-2022.

Treasury + referral ATAs are auto-created on first swap per output mint (~0.002 SOL rent, paid by signer once).

On-Chain Router

Every POST /swap transaction is wrapped in the flow-router CPI. The router program is immutable — upgrade authority has been permanently burned. No one can change the fee rate, treasury, or program logic.

How It Works

User TX -> flow-router::swap
  |-- Read config PDA (fee, treasury, split)
  |-- Validate token program (SPL v1 or Token-2022)
  |-- Execute N sequential DEX CPIs (1-5 hops)
  |-- Verify final output >= min_amount_out
  |-- Validate fee account owned by treasury
  '-- Collect fee from output token

Integrators

Earn 70% of the 0.5% platform fee on every swap routed through your integration.

  1. Contact the admin to get your wallet whitelisted
  2. Set referral_account in your config.toml
  3. Fees accumulate in your wallet's ATAs automatically

Supported DEXes (21 pool types)

#DEXType
1Raydium V4Legacy AMM
2Raydium CPMMConstant Product
3Raydium CLMMConcentrated Liquidity
4Raydium LPStableSwap
5PumpFunBonding Curve
6PumpFun AMMGraduated AMM
7Orca WhirlpoolConcentrated Liquidity
8Meteora StandardConstant Product
9Meteora DLMMDynamic LMM
10Meteora DAMM v2Dynamic AMM
11Meteora DBCBonding Curve
12FluxBeamSPL Token Swap
13DefiTuna FusionCLMM
14DefiTuna PoolsPosition Manager
15SarosSPL Token Swap
16DooarSPL Token Swap
17PancakeSwapCLMM
18FlashTradeCustom
19ByrealCLMM
20Pumpup AMMConstant Product (post-graduation)
21Pumpup BondingBonding Curve (pre-graduation, native SOL)

Plus OnChain Labs DEX V2 (proVF4...) — aggregator router into 80+ private MM venues. Discovery-only: pool addresses behind it are registered against their underlying DEXes by the block scanner; we do not quote or execute against the aggregator program itself.

Performance

MetricValue
Quote latency0-block latency
Quote p99 (1000 iterations)47µs
Concurrent throughput29,359 quotes/sec
Full pipeline (quote -> IX -> TX)816µs avg
Pool discovery rate~500 pools/min
RPC calls (steady state)0
CPU<1%
Memory~35MB

Error Codes

Router Errors (on-chain)

CodeNameDescription
0InvalidInstructionDataBad instruction format
1SlippageExceededOutput < min_amount_out
2InvalidFeeBpsFee > 10000 bps
3BalanceReadFailedToken account unreadable
5NotEnoughAccountsMissing required accounts
6InvalidAccountWrong owner or discriminator
7AlreadyInitializedConfig/integrator PDA exists
8UnauthorizedNot admin or not whitelisted

API Errors

StatusWhen
400Invalid parameters (bad mint, zero amount, same input/output)
404No route found for the given pair
500Router not configured, RPC failure, or internal error

Security

The Flow router smart contract has been audited by SigIntZero.