To find liquidity pools for a Solana token, query a pool inventory by the token's mint address, retain each pool's venue and account identities, then load current on-chain state before quoting a trade. A list of pools is the beginning of a searcher's data pipeline—not evidence that a route is liquid or executable.
This guide shows how to query the Shredo Pool Catalogue from Rust and Go, and how to turn that discovery step into a reliable local inventory. The small clients retrieve one search page; they are not complete snapshot-verification or trading clients.
Start with the mint, not the ticker
A ticker is a display label. Your application needs the actual mint address on the correct network. Two tokens may share a name, while a malicious token can deliberately resemble a familiar asset.
Keep the mint as the identity throughout discovery, storage and route construction. Preserve token decimals and token-program information from authoritative account data; neither follows from the symbol.
For a searcher, useful questions are:
- Which supported pools include this mint?
- Which have the exact two assets I want?
- Which venue and program own each pool?
- Which dependency accounts will my state subscriber need?
Do not start by downloading every account you can find and hoping to classify it later. Define the inventory your strategy actually consumes.
Query one token or an exact pair
The catalogue's authenticated HTTPS endpoint is:
https://console.shredo.xyz/catalogue/v1/pools
Create a catalogue-specific API key in the console and provision it securely in SHREDO_CATALOGUE_API_KEY. Never paste a key into a URL, article comment or public repository.
Set MINT to the token's complete mint address, then request a bounded page:
curl --fail-with-body --get \
-H "Authorization: Bearer $SHREDO_CATALOGUE_API_KEY" \
--data-urlencode "mint=$MINT" \
--data-urlencode 'limit=25' \
'https://console.shredo.xyz/catalogue/v1/pools'
For an exact pair, use pair=MINT_A,MINT_B. To narrow a venue, use its documented lowercase hyphenated slug, such as raydium-clmm. These optional filters combine. A page may be empty because no supported records match; that does not prove no such pool exists anywhere on Solana.
The catalogue contract describes record fields, supported filters and cursor semantics. Check /status separately for publisher readiness and freshness rather than inferring health from a successful search request.
Rust: a bounded first-page client
Create a project with the following manifest:
[package]
name = "shredo-pool-search-example"
version = "0.1.0"
edition = "2024"
publish = false
[dependencies]
reqwest = { version = "=0.12.28", default-features = false, features = ["blocking", "rustls-tls"] }
serde_json = "1"
[[bin]]
name = "pool-search"
path = "pool-search.rs"
Save this as pool-search.rs:
use std::{error::Error, io::Read, time::Duration};
const MAX_BODY: u64 = 4 * 1024 * 1024;
fn main() -> Result<(), Box<dyn Error>> {
let key = std::env::var("SHREDO_CATALOGUE_API_KEY")?;
let mint = std::env::args().nth(1).ok_or("usage: pool-search MINT")?;
let mut url = reqwest::Url::parse("https://console.shredo.xyz/catalogue/v1/pools")?;
url.query_pairs_mut()
.append_pair("mint", &mint)
.append_pair("limit", "25");
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(15))
.redirect(reqwest::redirect::Policy::none())
.build()?;
let response = client.get(url).bearer_auth(key).send()?;
if !response.status().is_success() {
return Err(format!("catalogue returned HTTP {}", response.status()).into());
}
let mut body = Vec::new();
response.take(MAX_BODY + 1).read_to_end(&mut body)?;
if body.len() as u64 > MAX_BODY {
return Err("catalogue response exceeds 4 MiB".into());
}
let page: serde_json::Value = serde_json::from_slice(&body)?;
println!("{}", serde_json::to_string_pretty(&page)?);
Ok(())
}
Run it with the mint as an argument:
cargo run --locked --bin pool-search -- "$MINT"
On a new project, first run cargo generate-lockfile; retain that lockfile for reproducible builds. This example uses a blocking client because it is a small command-line query, not a latency-sensitive ingestion loop.
It encodes query parameters, refuses redirects, enforces a timeout and bounds the response before parsing JSON. Printing the response is useful for integration—not a design for the hot path of a trading bot.
Go: the same request without external packages
Save this as pool-search.go:
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
)
const maxBody = 4 * 1024 * 1024
func fetchPools(ctx context.Context, endpoint, key, mint string) ([]byte, error) {
u, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
q := u.Query()
q.Set("mint", mint)
q.Set("limit", "25")
u.RawQuery = q.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
client := &http.Client{
Timeout: 15 * time.Second,
CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse },
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("catalogue returned HTTP %d", resp.StatusCode)
}
body, err := io.ReadAll(io.LimitReader(resp.Body, maxBody+1))
if err != nil {
return nil, err
}
if len(body) > maxBody {
return nil, fmt.Errorf("response exceeds 4 MiB")
}
if !json.Valid(body) {
return nil, fmt.Errorf("invalid catalogue JSON")
}
return body, nil
}
func main() {
key := os.Getenv("SHREDO_CATALOGUE_API_KEY")
if key == "" || len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "set SHREDO_CATALOGUE_API_KEY; usage: go run pool-search.go MINT")
os.Exit(1)
}
body, err := fetchPools(context.Background(), "https://console.shredo.xyz/catalogue/v1/pools", key, os.Args[1])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println(string(body))
}
Run:
go run pool-search.go "$MINT"
Both examples intentionally print the documented JSON rather than inventing a simplified response schema. Inspect the returned generation and continuation metadata before implementing pagination.
Pagination is a consistency problem
Search pages belong to a particular snapshot view. Retain the response's snapshotId, continuation cursor and original filters together. Treat cursors as opaque; do not build them from page numbers or timestamps.
Follow the next-page cursor supplied by the API using the same filters. If the view can no longer be continued, restart discovery rather than combining the first half of one generation with the second half of another.
There is an important distinction: the search response's snapshot identifier names a logical pagination view. It is not necessarily a downloadable archive identifier. Obtain download descriptors from /snapshots/latest, not by inserting a search-view ID into an archive URL.
Build a durable inventory, not an endless search loop
Interactive queries are useful during development. A production inventory consumer should bootstrap from a complete verified snapshot, then apply resumable catalogue changes.
- Pin the publisher identity using independently trusted configuration.
- Fetch the latest snapshot descriptor, manifest and detached signature.
- Verify the exact manifest bytes and every chunk's digest, length and record count.
- Promote the complete inventory and its cutoff checkpoint atomically.
- Verify and apply each complete change batch before advancing the checkpoint.
Never reserialize JSON before signature verification. Byte-level integrity checks apply to the downloaded representation, not to a prettified equivalent. Do not trust an unfamiliar key merely because the same API advertises it.
Changes include both upserts and removals. Removing a pool from a local route graph matters as much as adding one. Persist the inventory mutation and its checkpoint in one durable transaction so a crash cannot leave a checkpoint ahead of the data it describes.
If retained history has expired, recover from a new verified full snapshot. Do not quietly advance past a missing interval or replace an unavailable catalogue with an empty one.
Discovery is not a quote
A catalogue record identifies a pool and its dependencies. It does not tell you the current reserves, tick state, bin state, price impact, transfer behaviour or whether a proposed trade will succeed.
A practical searcher separates responsibilities:
| Layer | Question answered |
|---|---|
| Pool inventory | Which supported pool and dependency accounts should I track? |
| Current account state | What do those accounts contain at a known observation point? |
| Venue-specific quote logic | What output is expected for this input and direction? |
| Simulation and execution | Does the constructed transaction satisfy its checks? |
Use account updates or an appropriate RPC source to maintain executable state. Use venue-specific decoding and quote logic to interpret it. If you also consume Decoded Transactions, treat those observations as signed intent, not as a replacement for confirmed state.
Where Shredo fits
Pool Catalogue is available for 49 USDC per calendar month. It provides supported pool inventory, authenticated search, signed snapshots and resumable catalogue changes through a separate API key. It is not a promise to cover every DEX or every newly created pool.
For teams already maintaining execution and state infrastructure, it can remove repeated inventory bootstrapping work while leaving strategy logic under their control. See the current pricing and integration contract before connecting.
Frequently asked questions
Can I find a pool from a token symbol?
Resolve and verify the mint address first. Symbols are not unique identifiers.
Does a pool record mean the pool is tradeable now?
No. Inventory membership and capability labels do not guarantee current liquidity, successful simulation or execution.
Are catalogue changes every on-chain pool update?
No. They are coalesced changes to published inventory metadata. Use a separate state source for live account mutations.
Do these examples verify snapshot signatures?
No. They retrieve one authenticated search page. Full-snapshot consumers must implement the verification and recovery steps described above.
