This stream has pre-calculated OHLC data in the response which you can feed directly to your charting solution without additional calculations. Our **cryptocurrency price api** is accessible through GraphQL queries and subscriptions, making it easy to integrate into your applications. The **crypto prices api** supports **real-time streaming** and **recent OHLC history** (**rolling 30 days**); for **older** OHLC, use **`DEXTradeByTokens`** as in the [OHLC guide](/docs/trading/crypto-price-api/crypto-ohlc-candle-k-line-api/#crypto-price-api-vs-dextradebytoken).
> **Note**: Most queries can be converted to a GraphQL stream (WebSocket) by changing the keyword `query` to `subscription`.
> Trading cube queries are the exception — `selectWhere`, `if`, most statistical metrics and `interval:` do not
> survive the conversion, and `calculate` behaves differently. See
> [What does not survive streaming](/docs/subscriptions/what-does-not-survive-streaming/).
## Kafka Topic for Crypto Price Stream: `trading.prices`
This Kafka topic delivers **real-time, pre-aggregated price data** for tokens, currencies, and trading pairs across all supported blockchains. The data structure is a combination of all 3 cubes described in next section.
Schema for the proto topic is [here](https://github.com/bitquery/streaming_protobuf/tree/main/market).
The [python package](https://pypi.org/project/bitquery-pb2-kafka-package/) and [npm package](https://www.npmjs.com/package/bitquery-protobuf-schema) already have all schema updated.
Each message contains:
- **Price metrics** – OHLC (Open, High, Low, Close), Mean Price, SMA, WMA, EMA in USD
- **Volume data** – Base, Quote, and Base in USD
- **Supply data** – currency-level MarketCap, FullyDilutedValuationUsd, CirculatingSupply, TotalSupply, and MaxSupply ([Supply fields reference](/docs/trading/crypto-price-api/supply-fields))
- **Interval-based aggregation** – fixed durations (1s, 3s, 5s, 10s, etc.)
- **Clean feed** – low-quality and outlier trades are filtered automatically for accuracy as per the price algorithm discussed above.
> **Note on `"Quotes"`**:
> The `"Quotes": [...]` section in the Kafka stream shows how much was traded in the native token (e.g., SOL) and how the USD value was derived.
> All following price entries (`MeanPrice`, `SMA`, `OHLC`, etc.) are already expressed in USD and ready for direct use in charting, bots, or analytics.
## Cubes in the API
The Price APIs have three core data cubes:
- **Tokens**: One blended price per token per chain, aggregated across every pool the token trades in. Use it for chain-wide streams and per-chain aggregates.
- **Currencies**: An aggregated view of tokens that represent the same underlying asset. For example, tokens like cbBTC, WBTC, and other Bitcoin-wrapped tokens are all grouped under the Bitcoin currency.
- **Pairs**: Price and volume data for token pairs on specific markets/protocols. E.g., SOL/USDC on Raydium (Solana) or ETH/USDT on Uniswap (Ethereum). **This is also the recommended cube for a specific token's price** — filter to its top market with [`Ranking: { Position: { eq: 1 } }`](/docs/trading/crypto-price-api/pairs#most-accurate-token-price).
> Note: Expressions are supported in this API.
### Currencies
Currencies are representation of all tokens on various chains supported in Crypto Price API(Price Index). For example, take the case of Bitcoin, while it is a native token on Bitcoin chain, it is also traded on EVM chains as WBTC ( wrapped BTC). Now all these representations of BTC are represented as a single currency.
> Note: We include `Volume: {Usd: {gt: 5}}` to further remove extreme outliers. The stream already pre-filters outliers, so this is an additional check.
```graphql
{
Trading {
Currencies(
limit: { count: 1 }
orderBy: { descending: Block_Time }
where: {
Currency: { Id: { is: "bid:bitcoin" } }
Volume: { Usd: { gt: 5 } }
}
) {
Volume {
Usd
Quote
BaseAttributedToUsd
Base
}
Supply {
MarketCap
FullyDilutedValuationUsd
CirculatingSupply
TotalSupply
MaxSupply
}
Price {
Ohlc {
Open
Low
High
Close
}
IsQuotedInUsd
Average {
Mean
WeightedSimpleMoving
SimpleMoving
ExponentialMoving
}
}
Currency {
Symbol
Name
Id
}
Block {
Timestamp
}
Interval {
Time {
Duration
Start
End
}
}
}
}
}
```
#### How does the above query work?
It takes amounts and prices from all chains that use BTC and wrapped versions (including bridged versions) and presents an aggregated view. The OHLC, mean and other values represent a stable BTC picture.
### Tokens
Let's say you don't want a chain agnostic view, but want to focus on a particular chain. How to stream or query prices for it? This is where tokens come in.
> **Note:** the price below is blended across **all pools** where the token is base. For a single token, the [rank-1 Pairs query](/docs/trading/crypto-price-api/pairs#most-accurate-token-price) prices it from its top market instead — recommended whenever you care about one specific token.
> Same outlier filter as above.
```graphql
{
Trading {
Tokens(
where: {
Token: {
Address: { is: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" }
Network: { is: "Solana" }
}
Interval: { Time: { Duration: { eq: 1 } } }
Volume: { Usd: { gt: 5 } }
}
limit: { count: 1 }
orderBy: { descending: Block_Time }
) {
Token {
Address
Id
IsNative
Name
Network
Symbol
TokenId
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
BaseAttributedToUsd
Quote
Usd
}
Supply {
MarketCap
FullyDilutedValuationUsd
CirculatingSupply
TotalSupply
MaxSupply
}
Price {
IsQuotedInUsd
Ohlc {
Close
High
Low
Open
}
Average {
ExponentialMoving
Mean
SimpleMoving
WeightedSimpleMoving
}
}
}
}
}
```
We filter for specific tokens using the token `Address` and `Network`. If you need to stream all token prices on say Solana, simply set `Network` to `solana`.
> Same outlier filter as above.
```graphql
subscription {
Trading {
Tokens(
where: {
Token: { Network: { is: "Solana" } }
Volume: { Usd: { gt: 5 } }
}
) {
Token {
Address
Id
IsNative
Name
Network
Symbol
TokenId
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
BaseAttributedToUsd
Quote
Usd
}
Supply {
MarketCap
FullyDilutedValuationUsd
CirculatingSupply
TotalSupply
MaxSupply
}
Price {
IsQuotedInUsd
Ohlc {
Close
High
Low
Open
}
Average {
ExponentialMoving
Mean
SimpleMoving
WeightedSimpleMoving
}
}
}
}
}
```
### Pairs
This is the 3rd cube in these set of APIs. The Pairs cube gives you price, volume, and market-level trading data between two tokens — a base token and a quote token.
We will breakdown in detail how base token and Quote are chosen in the next section.
> **Recommended for single-token prices:** add `Ranking: { Position: { eq: 1 } }` to a Pairs query to get a token's price from its **top market** — the pool carrying the most volume for it. See [Getting the Most Accurate Token Price](/docs/trading/crypto-price-api/pairs#most-accurate-token-price).
> **Tip**: Use `TokenId` instead of `Token.Address` to fetch all variants of the same token (e.g., ETH, WETH, bridged ETH) across multiple chains.
> Same outlier filter as above.
```graphql
subscription {
Trading {
Pairs(
where: {
Market: { Network: { is: "Base" } }
QuoteToken: {
Address: { is: "0x4200000000000000000000000000000000000006" }
}
Token: { Address: { is: "0x940181a94a35a4569e4529a3cdfb74e38fd98631" } }
Volume: { Usd: { gt: 5 } }
}
) {
Currency {
Symbol
Name
Id
}
Market {
Protocol
Program
Network
Name
Address
}
Token {
Address
Id
Name
Symbol
TokenId
}
Volume {
Usd
Base
Quote
BaseAttributedToUsd
}
Supply {
MarketCap
FullyDilutedValuationUsd
CirculatingSupply
TotalSupply
MaxSupply
}
QuoteToken {
TokenId
Name
Id
Address
Symbol
}
QuoteCurrency {
Id
}
Price {
Ohlc {
Open
Low
High
Close
}
Average {
WeightedSimpleMoving
SimpleMoving
Mean
ExponentialMoving
}
}
}
}
}
```
## Understanding Intervals
Unlike DEXtrades APIs, the intervals here are fixed and cannot be arbitrary.
### Supported Time Intervals
The following durations (in seconds) are supported for querying or streaming historical and real-time data, unlike DEX APIs, these intervals are fixed, other values are not supported.
`1, 3, 5, 10, 30, 60, 300, 900, 1800, 3600`
### Supported Volume Aggregation Levels
Use `TargetVolume` to get price intervals aggregated over a volume threshold:
`1000, 10000, 100000, 1000000 (USD)`
## When to Choose Which Cube (Token, Currency, Pair)?
:::tip The short answer
For the price of a **specific token**, use **`Pairs` with `Ranking: { Position: { eq: 1 } }`** — see [Getting the Most Accurate Token Price](/docs/trading/crypto-price-api/pairs#most-accurate-token-price). Use `Tokens` for chain-wide streams and per-chain aggregates, and `Currencies` for one cross-chain number per asset.
:::
### Use the **`Pairs`** cube when:
- You want the **price of a specific token** — filter to its top market with `Ranking: { Position: { eq: 1 } }` so thin pools do not affect the number ([how and why](/docs/trading/crypto-price-api/pairs#most-accurate-token-price)).
- You want **pair-level trading data** (e.g., SOL/USDC, ETH/DAI).
- You’re analyzing trading activity **on a specific market or DEX** (e.g., Uniswap, PancakeSwap, Raydium).
- You need **OHLC, volume, liquidity**, and market-specific pricing between two tokens.
- You're exploring **price arbitrage** or spreads across chains or platforms.
### Use the **`Tokens`** cube when:
- You want a **firehose** of price updates for every token on a chain.
- You want **one chain-wide number per token**, blended across all of its pools, rather than the price on a single market.
- You're interested in **aggregate volume across all of a token's pools**, or the currency-level `Supply` and `MarketCap` fields.
> For a **single token's price**, prefer `Pairs` with rank 1: the `Tokens` price is a volume-weighted blend of every pool, so thin pools can pull it away from the primary market. See [Tokens cube](/docs/trading/crypto-price-api/tokens).
### Use the **`Currencies`** cube when:
- You want a **chain-agnostic view** of a token (e.g., BTC across Bitcoin, Ethereum (WBTC), Solana, etc.).
- You need a **global price** for a currency, combining its various representations.
- You're looking for **aggregated OHLC and average prices** for a token across chains.
## Crypto Price API for TradingView
With the new Price API, you can simply feed the stream to your custom datafeed object in the TradingView code and have it update charts in real-time.
- A sample tutorial is available [here](/docs/usecases/tradingview-subscription-realtime/getting-started/)
- The stream is available ready-to-chart SDK [here](https://www.npmjs.com/package/@bitquery/tradingview-sdk). Simply copy paste the advanced charting library into the correct folder, add Bitquery access token and it is ready.
## Video Tutorial | Introduction to Crypto Price APIs
'; echo formatArrayAsTable($sandwichOpportunityData); echo ''; echo ''; ``` The formatArrayAsTable() method is used to render the array as a table. ### Adding a Table #### Adding table headers This function will be used to get the column headings for the table. ```php function getTableHeaders($data) { $headers = []; foreach ($data as $row) { foreach ($row as $key => $value) { if (!in_array($key, $headers)) { $headers[] = $key; } } } return $headers; } ``` #### Helper function This function will be used to recursively flattens the nested arrays and objects into a single-level array with concatenated keys. ```php function flattenArray($array, $prefix = '') { $result = []; foreach ($array as $key => $value) { $newKey = $prefix ? "{$prefix}_{$key}" : $key; if (is_array($value)) { $result = array_merge($result, flattenArray($value, $newKey)); } else { $result[$newKey] = $value; } } return $result; } ``` Finally this code snippet takes up the latest DEX trades with large purchase amount from the EVM chains, and displays them in a table using simple HTML. ```php function formatArrayAsTable($data) { $flattenedData = array_map('flattenArray', $data); $html = '
| ' . htmlspecialchars($column) . ' | '; } $html .= '
|---|
| ' . htmlspecialchars($row[$column] ?? '') . ' | '; } $html .= '
Status: {isConnected ? "Connected" : "Disconnected"}
{error &&Error: {error}
} {data ? (| Protocol Family | {data.Solana.DEXTrades[0].Trade.Dex.ProtocolFamily} |
|---|---|
| Protocol Name | {data.Solana.DEXTrades[0].Trade.Dex.ProtocolName} |
| Buy Amount | {data.Solana.DEXTrades[0].Trade.Buy.Amount} |
| Buy Currency | {data.Solana.DEXTrades[0].Trade.Buy.Currency.Symbol} |
| Sell Amount | {data.Solana.DEXTrades[0].Trade.Sell.Amount} |
| Sell Currency | {data.Solana.DEXTrades[0].Trade.Sell.Currency.Symbol} |
| Transaction Signature | {data.Solana.DEXTrades[0].Transaction.Signature} |
Loading data...
)}
Aggregations (pool, token, and **non-stablecoin** currency) use these **decayed** volume contributions instead of raw sums over the window.
## How Token Prices Are Determined
Token prices are determined by **volume-weighted aggregation** over eligible trades in the **last hour**, using decayed volumes as above.
### Step 1: Identify pairs where the token is base
We determine in which pairs the token acts as the **base** currency. A token can be quote in one pair (e.g. PUMP/WSOL) and base in another (e.g. WSOL/USDT); for token price we only use pairs where the token is **base**.
### Step 2: Aggregate from pools to token price
For each such pair we use the markets (DEX pools) where it is traded. Conceptually, for pools that trade the same logical pair, the combined price uses **volume-weighted** pooling with **decayed** base-token volumes over the 1-hour window:
```
price(USD) = sum( price(p) × effectiveVolume(p) ) / sum( effectiveVolume(p) )
```
Where:
- **price(p)** = latest price of a trade in pool `p` (in USD; see [How Pool Prices Are Normalized to the Current Quote Token](#how-pool-prices-are-normalized-to-the-current-quote-token) below).
- **effectiveVolume(p)** = sum of **base-token** amounts for trades in pool `p` in the window, each multiplied by **`exp(-decayRate × age)`** for that trade.
The **token** price is then the same style of **volume-weighted** combination across all pairs where the token is base, again using decayed volumes.
## How Currency Prices Are Determined
The same rules apply for **non-stablecoin** currencies: a **1-hour** window, **exponentially decayed** volume weights (50% at the midpoint), and aggregation over **tokens** (volume and prices of each token representation) instead of over pairs and pools. Currency price is the volume-weighted combination of its token representations (e.g. WBTC, cbBTC, etc.) across chains.
**Stablecoins** are **not** included in this trade-based currency path. Processing skips them in the currency aggregation loop that builds prices from DEX trades; stablecoin USD (or peg) prices come from a **separate pipeline** fed by an **external** spot source, **not** from decay-weighted DEX trade aggregation.
**Ranking** is **not** exposed on currency-level responses. Internal logic may still derive token→currency weights while computing prices, but only **pair** and **token** updates are given a **`Ranking`** on the outbound message—do not look for **`Ranking`** on currency entities in the API.
## Ranking on Trades, Pairs, and Tokens
The API for **Trades**, **Pairs**, and **Tokens** exposes a **Ranking** object with two fields. It does **not** appear on **currencies** (see [How Currency Prices Are Determined](#how-currency-prices-are-determined)):
| Field | Meaning |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Position** | **1-based** index describing **priority** when multiple sources contribute: order of the **pair** in **token price**, or of the **token** among representations that feed a **non-stablecoin** currency blend. Lower number means higher priority in that ordering. |
| **Weight** | Float in **`[0, 1]`** — this pool's share of the total decay-weighted volume at the current moment. Reflects how much this pool drives the blended token price. Weights across all contributing pools for a given token sum to 1. |
Use **Position** for display order or precedence; use **Weight** for how much each contributor matters in the blended price.
:::tip Practical use: pricing a token from its top market
Filtering **`Pairs`** to **`Ranking: { Position: { eq: 1 } }`** gives you a token's price on the single market contributing the most decay-weighted volume, instead of the blend across all of its pools. This is the recommended way to price a specific token — see [Getting the Most Accurate Token Price](/docs/trading/crypto-price-api/pairs#most-accurate-token-price) for queries, streaming, and caveats.
:::
### Example: filter by ranking position
Filter pairs in **`where`** using **`Ranking.Position`** when you only want rows that are among the top contributors to the token price (for example positions **1**, **2**, and **3**). Request **`Ranking { Position Weight }`** on each pair to see both the rank and that pair’s **relative weight** in the blend. **`Weight`** is normally used as a returned field rather than as a filter.
```graphql
{
Trading {
Pairs(
where: {
Ranking: { Position: { in: [1, 2, 3] } }
Interval: { Time: { Duration: { eq: 3600 } } }
Block: { Time: { since_relative: { hours_ago: 1 } } }
}
limit: { count: 10 }
) {
Block {
Time(maximum: Block_Time)
}
Currency {
Id
}
Token {
Id
}
QuoteToken {
Id
}
Market {
Id
Protocol
Address
}
Price {
Ohlc {
Close
}
}
Ranking {
Position
Weight
}
Interval {
Time {
Start
End
}
}
}
}
}
```
## Volume and Amounts: Quote Token Amounts vs USD
When pricing is in USD (USD-base):
- **GraphQL** `Volume { Quote }` and **Protobuf** `Amounts.Quote` = **sum of quote token amounts** (e.g. sum of USDT, USDC, etc. amounts), **not** sum of USD.
- For **USD amounts** use **`Volume { Usd }`** (GraphQL) and **`Amounts.Usd`** (Protobuf) as before.
So for USD-based pricing, use `Volume.Usd` / `Amounts.Usd` when you need USD totals; use `Volume.Quote` / `Amounts.Quote` when you need the total in quote token units.
## How Pool Prices Are Normalized to the Current Quote Token
The **latest price of a trade in a pool** is not taken at the time that trade happened. It is **normalized** using the **current** price of the quote token at the time when the weighting is performed. So the quote side of the price is evaluated at weighting time, not at the time of the last trade in the pool. This keeps aggregated prices consistent with current quote token (e.g. stablecoin) valuation.
---
## Pump.fun Token Sniffer
URL: https://docs.bitquery.io/docs/usecases/pumpfun-token-sniffer/
Build Pump.fun Token Sniffer: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code.
# Pump.fun Token Sniffer
This guide demonstrates how to build a basic Pump.fun token analysis tool using Bitquery APIs. The tool displays various on-chain metrics related to Pump.fun tokens on Solana, helping users understand token distribution, holder behavior, and transfer patterns.
It is built on the [Bitquery Pump.fun API](https://bitquery.io/products/pumpfun-api) — see the product page for channels, pricing and the free trial.
> **⚠️ Important: Proof of Concept**
>
> This tool is a **proof of concept** that displays various metrics and data points about Pump.fun tokens. It does **not** make definitive claims about whether a token is a scam or legitimate. The metrics shown are for informational purposes only. You should conduct your own research and due diligence before making any investment decisions. This tool demonstrates how to use Bitquery APIs to build token analysis tools - you can extend this foundation to create more advanced analysis systems.
GitHub Repository: [pumpfun-token-sniffer](https://github.com/Akshat-cs/pumpfun-token-sniffer)
## Overview
The Pump.fun Token Sniffer is a Python-based analysis tool built with Bitquery APIs that displays various metrics about Pump.fun tokens. The tool provides:
### Token Information Metrics
- **Bonding Curve Address**: Automatically detects and displays the bonding curve address
- **Token Creation Details**: Creator address, creation timestamp, and transaction signature
- **Token Metadata**: Name, symbol, IPFS metadata (image, website, social links, description)
- **Token Status**: Mayhem mode status and graduation status (whether token has migrated to PumpSwap)
### Holder Distribution Metrics
- **Top 10 Holders**: List of addresses with the largest token holdings
- **Holding Percentages**: Percentage of total supply held by each top holder
- **Creator Holdings**: Percentage of tokens held by the creator (if in top 10)
- **Top 10 Concentration**: Total percentage of supply held by top 10 holders
- **Holder Distribution Checks**: Flags if any single holder (excluding bonding curve) holds more than 5% of supply
### Holder Activity Metrics
- **Pump Token Portfolio**: Number of different Pump.fun tokens each holder owns
- **Trading Activity**: Number of trades executed by each holder in the last 6 hours
- **Holder Engagement**: Activity level indicators based on recent trading
### Transfer and Purchase Analysis Metrics
- **Transfer Statistics**: Total addresses that received token transfers
- **Purchase Statistics**: Number of addresses that purchased the token
- **Transfer vs Purchase Comparison**: Analysis of addresses that received transfers vs those that made purchases
- **Transfer Timing Analysis**: Comparison of first transfer timestamps vs first purchase timestamps
- **Transfer Amounts**: Total amounts transferred, purchased, and transferred without corresponding purchases
### Liquidity Metrics
- **Pool Liquidity**: Current SOL liquidity in the bonding curve pool
### Interface Options
- **Web UI**: Modern web interface for interactive analysis
- **Command Line**: Terminal-based interface for quick checks
## Prerequisites
1. **Python 3.8+** installed on your system
2. **Bitquery API Token** - Get your API token [here](/docs/authorization/how-to-generate/)
3. Basic understanding of Solana blockchain and Pump.fun tokens
## Installation
1. Clone the repository:
```bash
git clone https://github.com/Akshat-cs/pumpfun-token-sniffer
cd pumpfun-token-sniffer
```
2. Install the required dependencies:
```bash
pip install -r requirements.txt
```
3. Set up your API key:
- Copy `.env.sample` to `.env`:
```bash
cp .env.sample .env
```
- Edit `.env` and replace `your_api_key_here` with your actual Bitquery API key:
```
BITQUERY_API_KEY=BQ_your_actual_api_key_here
```
## Project Structure
The project consists of two main files:
- **`check_phishy_token.py`**: Core analysis logic with GraphQL queries and detection algorithms
- **`app.py`**: Flask web application that provides the UI and API endpoints
## How It Works
The tool uses multiple GraphQL queries from Bitquery APIs to fetch and display various token metrics:
1. **Bonding Curve Detection**: Finds the bonding curve address by querying Pump.fun creation instructions
2. **Token Creation Data**: Retrieves creator address, creation time, and transaction details
3. **Token Metadata**: Fetches token name, symbol, and IPFS metadata
4. **First Transfers Query**: Fetches the first transfers of a token to various addresses
5. **First Buys Query**: Retrieves purchase data for addresses that received transfers
6. **Top Holders Query**: Gets top 10 holders with their current holdings
7. **Holder Activity Analytics**: Fetches pump token portfolio counts and recent trade activity for each holder
8. **Holder Distribution Analysis**: Calculates concentration metrics and distribution patterns
9. **Liquidity Query**: Gets current pool liquidity information
10. **Graduation Check**: Verifies if token has migrated to PumpSwap
### Transfer vs Purchase Analysis
One of the metrics the tool displays is a comparison between token transfers and purchases:
- **Addresses with transfers but no purchases**: Shows addresses that received tokens via transfer but never purchased them
- **Transfer timing vs purchase timing**: Compares when addresses first received transfers vs when they first made purchases
- **Transfer amounts**: Displays total amounts transferred, purchased, and the difference
**Note**: This metric is displayed for informational purposes. While unusual patterns (like many addresses receiving transfers without purchases) can be noteworthy, they do not definitively indicate whether a token is legitimate or not. Many factors contribute to token distribution patterns, and this tool simply presents the data for your analysis.
### Code Walkthrough
#### 1. Bonding Curve Detection
The tool first identifies the bonding curve address by querying Pump.fun creation instructions:
```python
def get_bonding_curve_address(token_address: str, api_key: str) -> Optional[Dict]:
"""
Get the bonding curve address for a Pump.fun token by querying the create instruction.
The bonding curve is the 3rd account in the Instruction accounts array.
"""
query = """
query MyQuery($token: String) {
Solana {
Instructions(
where: {
Instruction: {
Program: {
Address: {is: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"}
Method: {in: ["create", "create_v2"]}
}
Accounts: {includes: {Address: {is: $token}}}
}
Transaction: {Result: {Success: true}}
}
) {
Block {
Creation_time: Time
}
Instruction {
Accounts {
Address
}
Program {
AccountNames
Method
Arguments {
Name
Type
Value {
... on Solana_ABI_String_Value_Arg {
string
}
... on Solana_ABI_Boolean_Value_Arg {
bool
}
}
}
}
}
Transaction {
Creation_transaction: Signature
DevAddress: Signer
}
}
}
}
"""
```
The bonding curve is extracted from the 3rd account (index 2) in the accounts array. This query also extracts token metadata (name, symbol, URI), creator address, creation time, and Mayhem mode status.
#### 2. First Transfers Query
This query fetches the first transfers of the token to addresses (excluding the bonding curve):
```python
def get_first_transfers_pumpfun(token_address: str, bonding_curve: str, api_key: str) -> List[Dict]:
"""
Query 1: Get the first transfers of a Pump.fun token to addresses (Solana).
"""
query = """
query MyQuery($token: String, $bonding_curve: String) {
Solana {
Transfers(
limit: { count: 1000 }
orderBy: { ascendingByField: "Block_first_transfer" }
where: {
Transfer: {
Receiver: {
Token: {
Owner: {
not: $bonding_curve
notIn: ["8psNvWTrdNTiVRNzAgsou9kETXNJm2SXZyaKuJraVRtf", "AkTgH1uW6J6j6QHmFNGzZuZwwXaHQsPCpHUriED28tRj"]
}
}
}
Currency: { MintAddress: { is: $token } }
}
Transaction: { Result: { Success: true } }
}
) {
Transfer {
Receiver {
Token {
Owner
}
}
}
Block {
first_transfer: Time(minimum: Block_Time)
}
total_transferred_amount: sum(of: Transfer_Amount)
}
}
}
"""
```
This query:
- Limits results to 1,000 addresses
- Excludes the bonding curve and known system addresses
- Groups by receiver address
- Gets the first transfer timestamp and total transferred amount
#### 3. First Buys Query
This query checks if the addresses that received transfers ever bought the token:
```python
def get_first_buys_pumpfun(token_address: str, buyers_list: List[str], api_key: str) -> Dict[str, Dict]:
"""
Query 2: Get first buys of an address list for a specific Pump.fun token (Solana).
"""
query = """
query MyQuery($token: String!, $buyersList: [String!]) {
Solana {
DEXTradeByTokens(
orderBy: { ascendingByField: "Block_first_buy" }
where: {
Trade: {
Account: { Token: { Owner: { in: $buyersList } } }
Currency: { MintAddress: { is: $token } }
Side: { Type: { is: buy } }
}
Transaction: { Result: { Success: true } }
}
) {
Trade {
Account {
Token {
Owner
}
}
Currency {
Name
Symbol
MintAddress
}
Side {
Type
}
}
Block {
first_buy: Time(minimum: Block_Time)
}
total_bought_amount: sum(of: Trade_Amount)
}
}
}
"""
```
This query:
- Filters for buy-side trades only
- Groups by buyer address
- Gets the first buy timestamp and total bought amount
#### 4. Transfer vs Purchase Analysis
The analysis function compares transfers and purchases to identify patterns:
```python
def analyze_phishy_behavior_pumpfun(transfers: List[Dict], buy_data: Dict[str, Dict]) -> Tuple[int, List[Dict]]:
"""
Analyze transfers vs purchases to identify patterns for Pump.fun tokens.
"""
addresses_with_patterns = []
for transfer in transfers:
receiver = transfer["Transfer"]["Receiver"]["Token"]["Owner"]
first_transfer_time = transfer["Block"].get("first_transfer")
total_transferred = transfer.get("total_transferred_amount", 0)
buy_info = buy_data.get(receiver)
total_bought = buy_info.get("total_amount", 0) if buy_info else 0
transferred_without_buy = float(total_transferred) - float(total_bought)
if buy_info is None:
# Address received transfer but never purchased
addresses_with_patterns.append({
"address": receiver,
"first_transfer_time": first_transfer_time,
"first_buy_time": None,
"total_transferred": total_transferred,
"total_bought": 0,
"transferred_without_buy": transferred_without_buy,
"pattern": "Never bought the token"
})
else:
first_buy_time = buy_info.get("first_buy_time")
# Compare timestamps - if transfer happened before buy, note the pattern
if first_transfer_time < first_buy_time:
addresses_with_patterns.append({
"address": receiver,
"first_transfer_time": first_transfer_time,
"first_buy_time": first_buy_time,
"total_transferred": total_transferred,
"total_bought": total_bought,
"transferred_without_buy": transferred_without_buy,
"pattern": "Transfer before buy"
})
return len(addresses_with_patterns), addresses_with_patterns
```
#### 5. Top Holders Analysis
The tool also fetches top 10 holders with additional analytics:
```python
def get_top_holders_pumpfun(token_address: str, api_key: str) -> List[Dict]:
"""
Get top 10 holders of a Pump.fun token.
"""
query = """
query MyQuery($token: String) {
Solana {
BalanceUpdates(
limit: {count: 10}
orderBy: {descendingByField: "BalanceUpdate_Holding_maximum"}
where: {
BalanceUpdate: {
Currency: {MintAddress: {is: $token}}
}
Transaction: {Result: {Success: true}}
}
) {
BalanceUpdate {
Currency {
Name
MintAddress
Symbol
}
Account {
Token {
Owner
}
}
Holding: PostBalance(maximum: Block_Slot, selectWhere: {ne: "0"})
}
}
}
}
"""
```
For each holder, the tool also fetches:
- **Pump token count**: Number of Pump.fun tokens held by the address
- **Trade activity**: Number of trades in the last 6 hours
#### 6. Web Application (Flask)
The Flask app (`app.py`) provides a REST API endpoint:
```python
@app.route(f'{APPLICATION_ROOT}/api/check', methods=['POST'])
def check_token():
"""API endpoint to analyze a token and return various metrics."""
data = request.get_json()
token_address = data.get('token_address', '').strip()
# Find bonding curve
bonding_curve_data = get_bonding_curve_address(token_address, API_KEY)
# Get transfers and analyze patterns
transfers = get_first_transfers_pumpfun(token_address, bonding_curve, API_KEY)
addresses = [t["Transfer"]["Receiver"]["Token"]["Owner"] for t in transfers]
buy_data = get_first_buys_pumpfun(token_address, addresses, API_KEY)
pattern_count, addresses_with_patterns = analyze_phishy_behavior_pumpfun(transfers, buy_data)
# Get top holders with stats
top_holders = get_top_holders_pumpfun(token_address, API_KEY)
# ... enrich with pump token counts and trade activity
return jsonify(result)
```
### Additional Features
- **Bonding Curve Detection**: Automatically finds the bonding curve address associated with the token
- **Top Holders Analysis**: Shows the top 10 holders with:
- Number of pump tokens each holds
- Recent trading activity (last 6 hours)
- Percentage of total supply
- **Holder Distribution Analysis**: Displays concentration metrics showing if creator or other holders hold more than 5% of supply
- **Token Metadata**: Fetches token name, symbol, and IPFS metadata (image, website, social links)
- **Liquidity Information**: Gets current pool liquidity
- **Graduation Check**: Verifies if token has graduated to PumpSwap (not supported)
- **Summary Statistics**: Provides aggregated totals of transferred amounts, bought amounts, and amounts transferred without purchase
## Running the Tool
### Option 1: Web UI (Recommended)
1. Start the web server:
```bash
python app.py
```
2. Open your browser and navigate to `http://localhost:8080`
3. Enter a Pump.fun token address in the input field
4. Click "Check Token" to run the analysis
The web UI features:
- Modern web3-styled interface with dark theme
- Automatic bonding curve detection
- Top 10 holders table with pump token counts and trade stats
- Clickable addresses linking to DEXrabbit
- Copy-to-clipboard functionality for addresses
- Detailed breakdown of transfer vs purchase analysis
- All token metrics displayed in organized sections
- Summary statistics for all metrics
- Responsive design
### Option 2: Command Line
Run the analysis directly from the command line:
```bash
python check_phishy_token.py WZrxegwJK4vWFGC149Ajt86vbKA9tsrJxu8mJFdpump
```
Note: The bonding curve is automatically detected, so you only need to provide the token address.
## Understanding the Output
The tool displays various metrics organized into different sections:
### Token Information
- **Bonding Curve Address**: The bonding curve program address for this token
- **Creator Address**: The wallet address that created the token
- **Creation Time**: When the token was created
- **Transaction Signature**: The transaction that created the token
- **Token Name & Symbol**: Basic token information
- **Token Metadata**: IPFS metadata including image, website, social links, and description
- **Mayhem Mode**: Whether the token was created in Mayhem mode (affects total supply)
- **Graduation Status**: Whether the token has migrated to PumpSwap
### Top Holders Metrics
For each of the top 10 holders, the tool displays:
- **Address**: The wallet address
- **Holding Amount**: Number of tokens held
- **Percentage of Supply**: What percentage of total supply this holder owns
- **Pump Token Count**: How many different Pump.fun tokens this address holds
- **Trades (Last 6h)**: Number of trades executed in the last 6 hours
### Holder Distribution Analysis
- **Creator Percentage**: If creator is in top 10, shows their holding percentage
- **Creator Check**: Indicates if creator holds less than 5% (or not in top 10)
- **Other Holders Check**: Indicates if any other holder (excluding creator) holds more than 5%
- **Top 10 Total Percentage**: Combined percentage of supply held by top 10 holders
- **Top 10 Check**: Indicates if top 10 hold less than 70% of supply
### Transfer vs Purchase Analysis Metrics
This section displays data comparing token transfers and purchases:
- **Total Addresses with Transfers**: Count of all addresses that received the token via transfer
- **Addresses with Transfers but No Purchases**: Number of addresses that received transfers but never purchased
- **Addresses with Both Transfers and Purchases**: Number of addresses that both received transfers and made purchases
For addresses that received transfers but didn't purchase (or received transfers before purchases), the tool shows:
- **Address**: The wallet address
- **First Transfer Time**: When the address first received the token
- **First Buy Time**: When the address first purchased (if any)
- **Total Transferred**: Total amount of tokens transferred to this address
- **Total Bought**: Total amount of tokens purchased by this address
- **Amount Transferred Without Buy**: Difference between transferred and bought amounts
- **Pattern Type**: Description of the pattern observed (e.g., "Never bought the token" or "Transfer before buy")
**Summary Totals**:
- Total Amount Transferred to All Addresses
- Total Amount Bought by All Addresses
- Total Amount Transferred Without Purchase
### Liquidity Metrics
- **Current Pool Liquidity**: Amount of SOL currently in the bonding curve pool
### Example Output
The command-line output displays metrics in a structured format:
```
============================================================
Checking Pump.fun token: WZrxegwJK4vWFGC149Ajt86vbKA9tsrJxu8mJFdpump
============================================================
Finding bonding curve address...
Found bonding curve: ABC123...
Found 150 addresses that received transfers
Found buy records for 45 addresses
============================================================
RESULTS
============================================================
Total addresses that received transfers: 150
Addresses with transfers but no purchases: 105
Addresses with both transfers and purchases: 45
Transfer vs Purchase Analysis:
Found 105 address(es) with transfers but no purchases:
1. Address: ABC123...
First Transfer: 2024-01-15 10:30:00 UTC
First Buy: N/A
Total Transferred: 1,000,000.00
Total Bought: 0
Amount Transferred Without Buy: 1,000,000.00
Pattern: Never bought the token
------------------------------------------------------------
TRANSFER VS PURCHASE SUMMARY:
------------------------------------------------------------
Total Amount Transferred: 5,000,000.00
Total Amount Bought: 500,000.00
Total Amount Transferred Without Purchase: 4,500,000.00
------------------------------------------------------------
```
The web UI displays all metrics in an organized dashboard format with sections for token information, top holders, holder distribution analysis, transfer vs purchase metrics, and liquidity data.
## API Endpoints
The Flask web application provides the following endpoints:
### POST `/api/check`
Analyzes a Pump.fun token for phishy behavior.
**Request Body:**
```json
{
"token_address": "WZrxegwJK4vWFGC149Ajt86vbKA9tsrJxu8mJFdpump"
}
```
**Response:**
```json
{
"success": true,
"token_address": "WZrxegwJK4vWFGC149Ajt86vbKA9tsrJxu8mJFdpump",
"token_type": "pumpfun",
"data": {
"total_addresses": 150,
"transfer_purchase_analysis": {
"addresses_with_transfers_no_purchases": 105,
"addresses_with_both": 45
},
"transfer_purchase_details": [
{
"address": "ABC123...",
"first_transfer_time": "2024-01-15T10:30:00Z",
"first_buy_time": null,
"total_transferred": "1000000.0",
"total_bought": "0",
"transferred_without_buy": 1000000.0,
"pattern": "Never bought the token"
}
],
"top_holders": [
{
"address": "XYZ789...",
"holding": "50000000",
"percent_holding": 5.0,
"pump_tokens_count": 12,
"trades_6h": 5
}
],
"bonding_curve": "BondingCurveAddress...",
"token_creation": {
"transaction_signature": "Signature...",
"creator_address": "CreatorAddress...",
"creation_time": "2024-01-15T09:00:00Z"
},
"token_metadata": {
"name": "Token Name",
"symbol": "SYMBOL",
"is_mayhem_mode": false,
"image": "https://ipfs.io/ipfs/...",
"website": "https://example.com",
"twitter": "@example"
},
"holder_analysis": {
"creator_percent": 2.5,
"creator_check_passed": true,
"other_holders_check_passed": true,
"top10_percent": 45.0,
"top10_check_passed": true
},
"liquidity_sol": 125.5,
"totals": {
"total_transferred": 5000000.0,
"total_bought": 500000.0,
"total_without_buy": 4500000.0
}
}
}
```
### GET `/api/recent-phishy`
Returns a list of recently analyzed tokens from the cache (only tokens with transfer vs purchase patterns are cached).
**Response:**
```json
{
"success": true,
"tokens": [
{
"token_address": "WZrxegwJK4vWFGC149Ajt86vbKA9tsrJxu8mJFdpump",
"token_type": "pumpfun",
"transfer_purchase_count": 105,
"timestamp": "2024-01-15T12:00:00Z",
"totals": {
"total_transferred": 5000000.0,
"total_bought": 500000.0,
"total_without_buy": 4500000.0
}
}
],
"count": 1
}
```
## Limitations
- **Supported Tokens**: Only Pump.fun tokens on Solana are supported
- **Token Age**: The tool works best with tokens created recently (bonding curve detection may fail for very old tokens)
- **Graduated Tokens**: Tokens that have graduated to PumpSwap are not supported (tool will return an error)
- **Address Limit**: Analyzes up to 1,000 addresses per token (limit in the GraphQL query)
- **Query Time**: Queries may take 10-60+ seconds depending on data size (normal for blockchain queries)
- **API Rate Limits**: Subject to Bitquery API rate limits and quota restrictions
## Use Cases
This proof-of-concept tool demonstrates how to use Bitquery APIs to build token analysis tools. It can be used for:
- **Token Research**: View various on-chain metrics about Pump.fun tokens in one place
- **Holder Analysis**: Understand token distribution and concentration patterns
- **Activity Monitoring**: See trading activity and engagement levels of top holders
- **Data Aggregation**: Collect multiple data points from different Bitquery APIs
- **Learning Tool**: Understand how to query and analyze Solana blockchain data using Bitquery
- **Foundation for Advanced Tools**: Use this as a starting point to build more sophisticated analysis systems
## Building Advanced Tools
This is a basic proof-of-concept tool that demonstrates the capabilities of Bitquery APIs. You can extend this foundation to build more advanced analysis systems by:
- Adding more sophisticated analysis algorithms
- Implementing machine learning models for pattern detection
- Creating real-time monitoring and alerting systems
- Building comprehensive risk scoring systems
- Integrating with other data sources
- Adding historical trend analysis
- Creating comparative analysis across multiple tokens
- Building portfolio tracking and management tools
The Bitquery APIs provide access to comprehensive blockchain data that can power much more advanced analysis tools than this basic example.
## Important Considerations
- **This tool displays metrics only**: It does not make definitive claims about token legitimacy or risk
- **Metrics are informational**: All data points shown are for your analysis and research purposes
- **Not investment advice**: This tool is not financial or investment advice
- **Conduct your own research**: Always perform thorough due diligence before making any decisions
- **Proof of concept**: This is a basic demonstration - you can build more advanced tools using the same Bitquery APIs
- **Token age limitations**: The tool works best with recently created tokens (bonding curve detection may fail for very old tokens)
## Support
For questions or issues:
- Check the [GitHub repository](https://github.com/Akshat-cs/pumpfun-token-sniffer) for updates and issues
- Contact Bitquery support via [Telegram](https://t.me/Bloxy_info) or create a ticket [here](https://support.bitquery.io/)
---
## Quick Start Examples
URL: https://docs.bitquery.io/docs/trading/crypto-price-api/examples/
Quick Start Examples via Bitquery Trading APIs for multi-chain prices, OHLC candles, volume metrics, and live streams. See examples in the Bitquery IDE.
# Quick Start Examples
## Most Accurate Price for a Token (Top Market, Rank 1) {#most-accurate-price-for-a-token}
The recommended way to price a **specific token**: query the `Pairs` cube with `Ranking: { Position: { eq: 1 } }` to get the price from the token's **top market** — the pool currently carrying the most volume for it — instead of a value blended across every pool it trades in. Thin, fragmented pools therefore cannot pull the number away from the market where the token actually trades.
Full explanation, streaming variant, and caveats: [Getting the Most Accurate Token Price](/docs/trading/crypto-price-api/pairs#most-accurate-token-price).
[Run query ➤](https://ide.bitquery.io/Token-price-from-top-market--rank-1_2)
```graphql
{
Trading {
Pairs(
where: {
Token: {
Address: { is: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263" }
Network: { is: "Solana" }
}
Ranking: { Position: { eq: 1 } }
Interval: { Time: { Duration: { eq: 60 } } }
Price: { IsQuotedInUsd: true }
}
limit: { count: 1 }
orderBy: { descending: Block_Time }
) {
Token {
Symbol
Address
}
QuoteToken {
Symbol
}
Market {
Protocol
Address
Network
}
Price {
IsQuotedInUsd
Ohlc {
Open
High
Low
Close
}
}
Ranking {
Position
Weight
}
Volume {
Usd
}
Block {
Time
}
}
}
}
```
`Price.Ohlc.Close` is the latest price on the top market. Keep `Price: { IsQuotedInUsd: true }` in the filter — every market also publishes rows priced in **quote token units**, so without it a WBTC/WSOL market would return the price of WBTC in SOL rather than in dollars.
`Ranking.Weight` tells you how concentrated the token's liquidity is: near 1 means a single pool drives the price; a low value means it is fragmented across many pools — the case where this query differs most from the blended `Tokens` price.
Change `query` to `subscription` and drop `limit`/`orderBy` to stream it live.
## Real-Time Token Prices in USD on Solana
Stream live OHLC (Open, High, Low, Close) price and volume data for all tokens on Solana, quoted directly in USD. Useful for dashboards, analytics, or bots that need stable fiat-based prices.
Here we have selected the filter `Price: {IsQuotedInUsd: true}`, this means that any price values such as OHLC or Average indicators will be in USD. If you want them denominated in quote currency, change the filter to `Price: {IsQuotedInUsd: false}`.
[Run Stream ➤](https://ide.bitquery.io/Real-Time-USD-price-on-solana-chain_2)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
subscription {
Trading {
Pairs(
where: {Interval: {Time: {Duration: {eq: 1}}}, Price: {IsQuotedInUsd: true}, Market: {Network: {is: "Solana"}}, Volume: {Usd: {gt: 5}}}
) {
Token {
Name
Symbol
Address
}
Market {
Protocol
Program
Network
Name
Address
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Close
High
Low
Open
}
}
}
}
}
```
## Real-Time Token Prices in Quote Pair (USDC, USDT, etc.)
Stream live OHLC prices for Solana tokens denominated in their trading pair token (e.g., USDC, USDT, or another crypto), instead of direct USD. Great for analyzing token behavior relative to stablecoins or other assets.
Here we have selected the filter `Price: {IsQuotedInUsd: false}`, this means that any price values such as OHLC or Average indicators will be in quote currency. If you want them denominated in USD, change the filter to `Price: {IsQuotedInUsd: true}`.
[Run Stream ➤](https://ide.bitquery.io/Real-Time-usd-price-on-solana-chain-in-paired-token)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
subscription {
Trading {
Pairs(
where: {Interval: {Time: {Duration: {eq: 1}}}, Price: {IsQuotedInUsd: false}, Market: {Network: {is: "Solana"}}, Volume: {Usd: {gt: 5}}}
) {
Token {
Name
Symbol
Address
}
QuoteToken {
Name
Symbol
Address
}
Market {
Protocol
Program
Network
Name
Address
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Close
High
Low
Open
}
}
}
}
}
```
## Real-Time Token Prices Against SOL/WSOL
Stream real-time OHLC and volume data for Solana tokens specifically paired against SOL or WSOL. Useful when building apps or bots that want token values expressed relative to Solana’s native currency.
Here we have selected the filter `Price: {IsQuotedInUsd: false}`, this means that any price values such as OHLC or Average indicators will be in quote currency. If you want them denominated in USD, change the filter to `Price: {IsQuotedInUsd: true}`.
[Run Stream ➤](https://ide.bitquery.io/Real-Time-usd-price-on-solana-against-WSOLSOL)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
subscription {
Trading {
Pairs(
where: {Interval: {Time: {Duration: {eq: 1}}},
Price: {IsQuotedInUsd: false},
QuoteToken:{
Address:{
in:["So11111111111111111111111111111111111111112"
,"11111111111111111111111111111111"
]
}
},
Market: {Network: {is: "Solana"}}, Volume: {Usd: {gt: 5}}}
) {
Token {
Name
Symbol
Address
}
QuoteToken {
Name
Symbol
Address
}
Market {
Protocol
Program
Network
Name
Address
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Close
High
Low
Open
}
}
}
}
}
```
## Aggregated Token Data (Volume & Price, Last 24h)
Get a snapshot of tokens with aggregated USD volume and average price over the last 24 hours. The query uses `limitBy: { count: 1, by: Token_Id }` to return one row per token, and conditional metrics (`Volume.Usd(if: ...)`, `Price.Average.Mean(..., if: ...)`) to show volume and price for the last 1h, 4h, and 24h. Useful for dashboards, top-movers lists, or comparing short-term vs daily metrics.
> The `Tokens` cube is the right choice here: volume is summed across all of a token's pools. Note that its prices are blended across those pools too — to price one specific token from its top market instead, use [Pairs with rank 1](/docs/trading/crypto-price-api/pairs#most-accurate-token-price).
[Run query ➤](https://ide.bitquery.io/aggregated-data-for-tokens)
```graphql
{
Trading {
Tokens(
limit: {count: 100}
limitBy: {count: 1, by: Token_Id}
where: {Block: {Time: {since_relative: {hours_ago: 24}}}}
) {
Token {
Address
Id
IsNative
Name
Network
Name
Symbol
TokenId
}
Volume {
Usd
H4VAgo: Usd(if: {Block: {Time: {since_relative: {hours_ago: 4}}}})
}
Price {
Average {
currentPrice: Mean(maximum: Block_Time)
H24Ago: Mean(
minimum: Block_Time
if: {Block: {Time: {since_relative: {hours_ago: 24}}}}
)
}
}
Supply {
TotalSupply
MarketCap
FullyDilutedValuationUsd
}
}
}
}
```
## OHLC of a currency on multiple blockchains
This query retrieves the OHLC (Open, High, Low, Close) prices of a currency(in this eg Bitcoin; it will include all sorts of currencies whose underlying asset is Bitcoin like cbBTC, WBTC, etc) across all supported blockchains, aggregated into a given time interval (e.g., 60 seconds in this example).
[Run Stream ➤](https://ide.bitquery.io/OHLC-of-a-currency-on-multiple-blockchains)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
{
Trading {
Currencies(
where: {
Currency: { Id: { is: "bid:bitcoin" } },
Interval: { Time: { Duration: { eq: 60 } } },
Volume: { Usd: { gt: 5 } }
},
limit: { count: 1 },
orderBy: { descending: Block_Time }
) {
Currency {
Id
Name
Symbol
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
BaseAttributedToUsd
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
IsQuotedInUsd #The price is shown in USD (`IsQuotedInUsd: true` by default).
Ohlc {
Open # Earliest price across chains in the interval
High # Highest price across chains in the interval
Low # Lowest price across chains in the interval
Close # Latest price across chains in the interval
}
Average {
Estimate
ExponentialMoving
Mean
SimpleMoving
WeightedSimpleMoving
}
}
}
}
}
```
## OHLC Stream on a Chain
Mention the chain/network using the `Token: {Network}` filter.
Available values: `Ethereum`, `Solana`, `Base`, `Optimism`, `Opbnb`, `Matic`, `Arbitrum`, `Binance Smart Chain`, `Tron`.
The available duration intervals are listed [here](/docs/trading/crypto-price-api/#understanding-intervals).
In Tokens cube, only `IsQuotedInUsd:true` is supported so you will see OHLC and Price values in USD only.
Stream real-time OHLC (Open, High, Low, Close) prices, trading volume, and moving averages for all tokens on a specific blockchain (e.g., Solana). Useful for market dashboards or monitoring live token activity on one chain.
[Run Stream ➤](https://ide.bitquery.io/Aggregated-Price-of-all-tokens-in-real-time-on-one-chain)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
subscription {
Trading {
Tokens(
where: {
Token: { Network: { is: "Solana" } }
Interval: { Time: { Duration: { eq: 60 } } }
Volume: { Usd: { gt: 5 } }
}
) {
Token {
Address
Id
IsNative
Name
Network
Name
Symbol
TokenId
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
IsQuotedInUsd
Ohlc {
Close
High
Low
Open
}
Average {
ExponentialMoving
Mean
SimpleMoving
WeightedSimpleMoving
}
}
}
}
}
```
## OHLC of a Token Pair Across Chains
This subscription fetches real-time OHLC (Open, High, Low, Close) price data for a token pair across different blockchains.
For **native tokens**, you only need to specify their ID (e.g., `bid:eth` for ETH).
Here we have selected the filter `Price: {IsQuotedInUsd: false}`, this means that any price values such as OHLC or Average indicators will be in terms of quote currency instead of USD. If you want them in USD, change the filter to `Price: {IsQuotedInUsd: true}`.
[Run Stream ➤](https://ide.bitquery.io/Token-OHLC-Stream-1-second-Multi-Chains_1)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
subscription {
Trading {
Pairs(
where: {
Price: { IsQuotedInUsd: false }
Interval: { Time: { Duration: { eq: 1 } } }
Currency: { Id: { is: "bid:eth" } }
QuoteCurrency: { Id: { is: "usdc" } }
Volume: { Usd: { gt: 5 } }
}
) {
Token {
Id
Symbol
Address
NetworkBid
Network
Name
}
QuoteToken {
Id
Symbol
Address
Name
NetworkBid
}
Interval {
Time {
Start
End
Duration
}
}
Volume {
Usd
Quote
Base
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
IsQuotedInUsd
Ohlc {
Open
High
Low
Close
}
Average {
Estimate
ExponentialMoving
Mean
SimpleMoving
WeightedSimpleMoving
}
}
}
}
}
```
## Find Price Arbitrage Opportunity of Pair Across Chains
Compare token prices (e.g., BTC/USDT) across multiple markets and chains to identify arbitrage opportunities. Returns one latest price per market.
Here we have selected the filter `Price: {IsQuotedInUsd: true}`, this means that any price values such as OHLC or Average indicators will be in USD. If you want them in quote currency, change the filter to `Price: {IsQuotedInUsd: false}`.
[Run Stream ➤](https://ide.bitquery.io/Find-arbitrage-opportunity-with-same-token-across-chains_1)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
query {
Trading {
Pairs(
where: {
Price: { IsQuotedInUsd: true }
Currency: { Id: { is: "bid:bitcoin" } }
QuoteCurrency: { Id: { is: "usdt" } }
Volume: { Usd: { gt: 5 } }
}
limit: { count: 10 }
orderBy: { descending: Block_Time }
limitBy: { by: Market_Address, count: 1 }
) {
Currency {
Id
Name
Symbol
}
QuoteCurrency {
Id
Name
Symbol
}
Market {
Name
NetworkBid
Network
Address
}
Price {
IsQuotedInUsd
Average {
Mean
Estimate
SimpleMoving
ExponentialMoving
WeightedSimpleMoving
}
}
QuoteToken {
Symbol
Name
Id
NetworkBid
Network
Did
Address
}
Token {
Symbol
Name
Id
NetworkBid
Network
Did
Address
}
}
}
}
```
## 5 Minute Price Change API
Fetch the top 10 tokens by 5-minute percentage price change (USD-based), only including tokens with at least $100k trading volume. Ideal for building a "top movers" list.
> Scanning every token on a chain is exactly what the `Tokens` cube is for. Once you have picked a token out of the list, price it from its top market with [Pairs + rank 1](/docs/trading/crypto-price-api/pairs#most-accurate-token-price).
Here we have selected the filter `Price: {IsQuotedInUsd: true}`, this means that any price values such as OHLC or Average indicators will be in USD. If you want them in quote currency, change the filter to `Price: {IsQuotedInUsd: false}`.
This stream uses [expressions](/docs/graphql/capabilities/expression/)
[Run Stream ➤](https://ide.bitquery.io/5-minute-price-change-api_2)
> Note: We include `Volume: { Usd: { gt: 5 } }` in most examples to remove extreme outliers; this stream already filters by `Volume: { Usd: { gt: 100000 } }`.
```graphql
{
Trading {
Tokens(
limit: { count: 10 }
orderBy: { descendingByField: "change" }
where: {
Price: { IsQuotedInUsd: true }
Volume: { Usd: { gt: 100000 } }
Interval: { Time: { Duration: { eq: 300 } } }
}
) {
Token {
Address
Did
Id
IsNative
Name
Network
Name
Symbol
TokenId
}
Currency {
Symbol
Id
Name
}
Interval {
VolumeBased
Time {
Start
End
Duration
}
}
Volume {
Base
BaseAttributedToUsd
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
IsQuotedInUsd
Ohlc {
Close
High
Low
Open
}
Average {
Estimate
ExponentialMoving
Mean
SimpleMoving
WeightedSimpleMoving
}
}
diff: calculate(expression: "Price_Ohlc_Close - Price_Ohlc_Open")
change: calculate(expression: "round(($diff / Price_Ohlc_Open), 3) * 100")
}
}
}
```
## 5 Minute Price Change Stream on Solana
Stream the top 10 tokens on Solana by 5-minute price change (in USD), filtered by $100k+ volume. Updates continuously.
> As above, this is a chain-wide scan. For a watchlist of specific tokens, stream their top markets instead — see [Watchlist: top-market price for several tokens](/docs/trading/crypto-price-api/pairs#most-accurate-token-price).
Here we have selected the filter `Price: {IsQuotedInUsd: true}`, this means that any price values such as OHLC or Average indicators will be in USD. If you want them in quote currency, change the filter to `Price: {IsQuotedInUsd: false}`.
This stream uses [expressions](/docs/graphql/capabilities/expression/)
[Run Stream ➤](https://ide.bitquery.io/5-minute-price-change-api-on-solana_6)
> Note: We include `Volume: { Usd: { gt: 5 } }` in most examples to remove extreme outliers; this stream already filters by `Volume: { Usd: { gt: 100000 } }`.
```graphql
subscription{
Trading {
Tokens(
orderBy:[{descending:Block_Time} {descendingByField:"change"}]
where: {
Price:{IsQuotedInUsd:true}
Token:{Network:{is:"Solana"}}
Volume:{Usd:{gt:100000}}
Interval: {Time: {Duration: {eq: 300}}}}) {
Token {
Address
Did
Id
IsNative
Name
Network
Name
Symbol
TokenId
}
Currency {
Symbol
Id
Name
}
Interval {
VolumeBased
Time {
Start
End
Duration
}
}
Volume {
Base
BaseAttributedToUsd
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
IsQuotedInUsd
Ohlc {
Close
High
Low
Open
}
Average {
Estimate
ExponentialMoving
Mean
SimpleMoving
WeightedSimpleMoving
}
}
diff: calculate(expression:"$Price_Ohlc_Close - $Price_Ohlc_Open")
change: calculate(expression:"$diff / $Price_Ohlc_Open * 100")
}
}
}
```
## Drawdown of an Asset in the Last Hour
Calculate the percentage drawdown (price decline) for tokens of a specific currency (e.g., Bitcoin) over a 1-hour interval.
This query uses [expressions](/docs/graphql/capabilities/expression/) to calculate drawdown as: `((Close - Open) / Open) * 100`.
> This compares a currency's token representations against each other, so `Tokens` fits. To measure one token's drawdown on the market where it actually trades, run the same expression against [Pairs with rank 1](/docs/trading/crypto-price-api/pairs#most-accurate-token-price).
> **Note:** You can use `Token: {Address: {is: "token_address"}}` filter instead of `Currency: {Id: {is: "bid:bitcoin"}}` to filter by token address. We include `Volume: { Usd: { gt: 10 } }` to filter out tokens with very low trading volume.
[Run query](https://ide.bitquery.io/Drawdown-of-a-token-last-hour)
```graphql
{
Trading {
Tokens(
limit: { count: 1 }
orderBy: { ascendingByField: "drawdown" }
where: {
Volume: { Usd: { gt: 10 } }
Interval: { Time: { Duration: { eq: 3600 } } }
Currency: { Id: { is: "bid:bitcoin" } }
}
) {
Token {
Address
Did
Id
IsNative
Name
Network
Symbol
TokenId
}
Currency {
Symbol
Id
Name
}
Interval {
VolumeBased
Time {
Start
End
Duration
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
IsQuotedInUsd
Ohlc {
Close
High
Low
Open
}
Average {
Estimate
ExponentialMoving
Mean
SimpleMoving
WeightedSimpleMoving
}
}
diff: calculate(expression: "Price_Ohlc_Close - Price_Ohlc_Open")
drawdown: calculate(expression: "($diff / Price_Ohlc_Open) * 100")
}
}
}
```
## Top Solana Price Movers (5-minute, high volume)
Rank Solana tokens by 5-minute price change, restricted to tokens with meaningful volume. This
is a query over 300-second candles, not a volume-based interval — see
[Volume-Based Aggregation](/docs/trading/crypto-price-api/crypto-ohlc-candle-k-line-api#volume-based-aggregation).
Here we have selected the filter `Price: {IsQuotedInUsd: true}`, this means that any price values such as OHLC or Average indicators will be in USD. If you want them denominated in quote currency, change the filter to `Price: {IsQuotedInUsd: false}`.
[Run Stream ➤](https://ide.bitquery.io/5-minute-price-change-api-on-solana_5)
> Note: We include `Volume: { Usd: { gt: 5 } }` in most examples to remove extreme outliers; this stream already filters by `Volume: { Usd: { gt: 100000 } }`.
```graphql
{
Trading {
Tokens(
limit: { count: 10 }
limitBy: { count: 1, by: Token_Id }
orderBy: [{ descending: Block_Time }, { descendingByField: "change" }]
where: {
Price: { IsQuotedInUsd: true }
Token: { Network: { is: "Solana" } }
Volume: { Usd: { gt: 100000 } }
Interval: { Time: { Duration: { eq: 300 } } }
}
) {
Token {
Address
Did
Id
IsNative
Name
Network
Name
Symbol
TokenId
}
Currency {
Symbol
Id
Name
}
Interval {
VolumeBased
Time {
Start
End
}
}
Volume {
Base
BaseAttributedToUsd
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
IsQuotedInUsd
Ohlc {
Close
High
Low
Open
}
Average {
Estimate
ExponentialMoving
Mean
SimpleMoving
WeightedSimpleMoving
}
}
diff: calculate(expression: "Price_Ohlc_Close - Price_Ohlc_Open")
change: calculate(expression: "round(($diff / Price_Ohlc_Open), 3) * 100")
}
}
}
```
## Trending Tokens (Multi-Chain)
Rank the top trending tokens **across all supported chains in a single query** by trailing 1-hour USD volume. It is built on 1-minute `Tokens` candles over the last hour: `vol_1h` sums the window with a **$10k floor** via `selectWhere`, `vol_5m` is a conditional sum for the last 5 minutes, and `priceMove` is the **absolute 1h price change (%)** computed with an [expression](/docs/graphql/capabilities/expression/). Quote/wrapped assets are excluded by symbol and market cap is capped at **$100M**.
> The Trading cube has **no liquidity/TVL field** — for pool depth, join a chain-level [`DEXPools`](/docs/trading/trading-data-overview#what-the-trading-cube-cannot-do) query client-side. Apply the **≤ 500% price-move cut client-side** (a computed expression cannot be filtered server-side).
[Run query ➤](https://ide.bitquery.io/Trending-tokens-multi-chain)
```graphql
{
Trading {
Tokens(
limit: { count: 20 }
orderBy: { descendingByField: "vol_1h" }
where: {
Price: { IsQuotedInUsd: true }
Interval: { Time: { Duration: { eq: 60 } } }
Block: { Time: { since_relative: { hours_ago: 1 } } }
Supply: { MarketCap: { le: 100000000 } }
Token: {
Symbol: { notIn: ["WSOL", "USDC", "USDT", "WETH", "DAI", "cbBTC"] }
}
}
) {
Token {
Id
Network
Symbol(maximum: Block_Time)
Name(maximum: Block_Time)
Address(maximum: Block_Time)
}
vol_1h: sum(of: Volume_Usd, selectWhere: { ge: "10000" })
vol_5m: sum(of: Volume_Usd, if: { Block: { Time: { since_relative: { minutes_ago: 5 } } } })
Price {
Ohlc {
open: Open(minimum: Interval_Time_Start)
high: High(maximum: Price_Ohlc_High)
low: Low(minimum: Price_Ohlc_Low)
close: Close(maximum: Interval_Time_Start)
}
}
priceMove: calculate(expression: "abs(($Price_Ohlc_close - $Price_Ohlc_open) / $Price_Ohlc_open) * 100")
Supply {
MarketCap(maximum: Block_Time)
FullyDilutedValuationUsd(maximum: Block_Time)
}
}
}
}
```
## PumpAMM 1-second Price, OHLC, Volume, SMA, EMA Stream for Traders
Real-time (1-second interval) price, OHLC, volume, and moving averages for Pump.fun AMM tokens on Solana. Useful for high-frequency trading bots.
Here we have selected the filter `Price: {IsQuotedInUsd: true}`, this means that any price values such as OHLC or Average indicators will be in USD. If you want them in quote currency, change the filter to `Price: {IsQuotedInUsd: false}`.
[Run Stream ➤](https://ide.bitquery.io/PumpAMM-tokens-1-second-price-stream-with-OHLC_1)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
subscription {
Trading {
Pairs(
where: {Interval: {Time: {Duration: {eq: 1}}}, Price: {IsQuotedInUsd: true}, Market: {Network: {is: "Solana"}, Program: {is: "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"}}, Volume: {Usd: {gt: 5}}}
) {
Market {
Protocol
Program
Network
Name
Address
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Close
High
Low
Open
}
IsQuotedInUsd
}
Currency {
Symbol
Name
Id
}
QuoteCurrency{
Name
Symbol
Id
}
Token{
Name
Symbol
Address
Id
NetworkBid
}
QuoteToken{
Name
Symbol
Id
Address
NetworkBid
}
}
}
}
```
## Heaven DEX 1-second Price, OHLC, Volume, SMA, EMA Stream for Traders
Real-time (1s) stream of prices, OHLC, and volumes for tokens traded on Heaven DEX (Solana).
Here we have selected the filter `Price: {IsQuotedInUsd: true}`, this means that any price values such as OHLC or Average indicators will be in USD. If you want them in quote currency, change the filter to `Price: {IsQuotedInUsd: false}`.
[Run Stream ➤](https://ide.bitquery.io/Heaven-DEX-tokens-1-second-price-stream-with-OHLC)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
subscription {
Trading {
Pairs(
where: {Interval: {Time: {Duration: {eq: 1}}}, Price: {IsQuotedInUsd: true}, Market: {Network: {is: "Solana"}, Program: {is: "HEAVENoP2qxoeuF8Dj2oT1GHEnu49U5mJYkdeC8BAX2o"}}, Volume: {Usd: {gt: 5}}}
) {
Market {
Protocol
Program
Network
Name
Address
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Close
High
Low
Open
}
IsQuotedInUsd
}
Currency {
Symbol
Name
Id
}
QuoteCurrency{
Name
Symbol
Id
}
Token{
Name
Symbol
Address
Id
NetworkBid
}
QuoteToken{
Name
Symbol
Id
Address
NetworkBid
}
}
}
}
```
## Meteora DBC 1-second Price, OHLC, Volume, SMA, EMA Stream for Traders
Real-time (1s) OHLC, price, and volume feed for Meteora DBC DEX on Solana.
Here we have selected the filter `Price: {IsQuotedInUsd: true}`, this means that any price values such as OHLC or Average indicators will be in USD. If you want them in quote currency, change the filter to `Price: {IsQuotedInUsd: false}`.
[Run Stream ➤](https://ide.bitquery.io/Meteora-DBC-DEX-tokens-1-second-price-stream-with-OHLC)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
subscription {
Trading {
Pairs(
where: {Interval: {Time: {Duration: {eq: 1}}}, Price: {IsQuotedInUsd: true}, Market: {Network: {is: "Solana"}, Program: {is: "HEAVENoP2qxoeuF8Dj2oT1GHEnu49U5mJYkdeC8BAX2o"}}, Volume: {Usd: {gt: 5}}}
) {
Market {
Protocol
Program
Network
Name
Address
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Close
High
Low
Open
}
IsQuotedInUsd
}
Currency {
Symbol
Name
Id
}
QuoteCurrency{
Name
Symbol
Id
}
Token{
Name
Symbol
Address
Id
NetworkBid
}
QuoteToken{
Name
Symbol
Id
Address
NetworkBid
}
}
}
}
```
## Raydium Launchlab 1-second Price, OHLC, Volume, SMA, EMA Stream for Traders
Monitor Raydium Launchlab token listings on Solana with 1-second OHLC and volume streams. Perfect for tracking new token launches.
Here we have selected the filter `Price: {IsQuotedInUsd: true}`, this means that any price values such as OHLC or Average indicators will be in USD. If you want them in quote currency, change the filter to `Price: {IsQuotedInUsd: false}`.
[Run Stream ➤](https://ide.bitquery.io/Raydium-Launchpad-DEX-tokens-1-second-price-stream-with-OHLC)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
subscription {
Trading {
Pairs(
where: {Interval: {Time: {Duration: {eq: 1}}}, Price: {IsQuotedInUsd: true}, Market: {Network: {is: "Solana"}, Program: {is: "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"}}, Volume: {Usd: {gt: 5}}}
) {
Market {
Protocol
Program
Network
Name
Address
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Close
High
Low
Open
}
IsQuotedInUsd
}
Currency {
Symbol
Name
Id
}
QuoteCurrency{
Name
Symbol
Id
}
Token{
Name
Symbol
Address
Id
NetworkBid
}
QuoteToken{
Name
Symbol
Id
Address
NetworkBid
}
}
}
}
```
## Uniswap v3 1-second Price, OHLC, Volume, SMA, EMA Stream for Traders
1-second OHLC and volume stream for tokens traded on Uniswap v3 (Ethereum). Great for bot trading strategies.
Here we have selected the filter `Price: {IsQuotedInUsd: true}`, this means that any price values such as OHLC or Average indicators will be in USD. If you want them in quote currency, change the filter to `Price: {IsQuotedInUsd: false}`.
[Run Stream ➤](https://ide.bitquery.io/Uniswap-v3-DEX-tokens-1-second-price-stream-with-OHLC)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
subscription {
Trading {
Pairs(
where: {Interval: {Time: {Duration: {eq: 1}}}, Price: {IsQuotedInUsd: true}, Market: {Network: {is: "Ethereum"}, Address: {is: "0x1f98431c8ad98523631ae4a59f267346ea31f984"}}, Volume: {Usd: {gt: 5}}}
) {
Market {
Protocol
Program
Network
Name
Address
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Close
High
Low
Open
}
IsQuotedInUsd
}
Currency {
Symbol
Name
Id
}
QuoteCurrency{
Name
Symbol
Id
}
Token{
Name
Symbol
Address
Id
NetworkBid
}
QuoteToken{
Name
Symbol
Id
Address
NetworkBid
}
}
}
}
```
## Sushiswap 1-second Price, OHLC, Volume, SMA, EMA Stream for Traders
Stream 1-second OHLC, price, and volume data from Sushiswap DEX (Ethereum).
Here we have selected the filter `Price: {IsQuotedInUsd: true}`, this means that any price values such as OHLC or Average indicators will be in USD. If you want them in quote currency, change the filter to `Price: {IsQuotedInUsd: false}`.
[Run Stream ➤](https://ide.bitquery.io/Sushiswap-DEX-tokens-1-second-price-stream-with-OHLC)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
subscription {
Trading {
Pairs(
where: {Interval: {Time: {Duration: {eq: 1}}}, Price: {IsQuotedInUsd: true}, Market: {Network: {is: "Ethereum"}, Address: {is: "0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac"}}, Volume: {Usd: {gt: 5}}}
) {
Market {
Protocol
Program
Network
Name
Address
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Close
High
Low
Open
}
IsQuotedInUsd
}
Currency {
Symbol
Name
Id
}
QuoteCurrency{
Name
Symbol
Id
}
Token{
Name
Symbol
Address
Id
NetworkBid
}
QuoteToken{
Name
Symbol
Id
Address
NetworkBid
}
}
}
}
```
## PancakeSwap v3 1-second Price, OHLC, Volume, SMA, EMA Stream for Traders
1-second OHLC and volume stream for tokens traded on PancakeSwap v3 (Ethereum).
Here we have selected the filter `Price: {IsQuotedInUsd: true}`, this means that any price values such as OHLC or Average indicators will be in USD. If you want them in quote currency, change the filter to `Price: {IsQuotedInUsd: false}`.
[Run Stream ➤](https://ide.bitquery.io/PancakeSwap-v3-DEX-tokens-1-second-price-stream-with-OHLC_1)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
subscription {
Trading {
Pairs(
where: {Interval: {Time: {Duration: {eq: 1}}}, Price: {IsQuotedInUsd: true}, Market: {Network: {is: "Ethereum"}, Address: {is: "0x0bfbcf9fa4f9c56b0f40a671ad40e0805a091865"}}, Volume: {Usd: {gt: 5}}}
) {
Market {
Protocol
Program
Network
Name
Address
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Close
High
Low
Open
}
IsQuotedInUsd
}
Currency {
Symbol
Name
Id
}
QuoteCurrency{
Name
Symbol
Id
}
Token{
Name
Symbol
Address
Id
NetworkBid
}
QuoteToken{
Name
Symbol
Id
Address
NetworkBid
}
}
}
}
```
## FourMeme 1-second Price, OHLC, Volume, SMA, EMA Stream for Traders
Track token activity (OHLC, price, volume) every 1 second on FourMeme DEX (BSC).
Here we have selected the filter `Price: {IsQuotedInUsd: true}`, this means that any price values such as OHLC or Average indicators will be in USD. If you want them in quote currency, change the filter to `Price: {IsQuotedInUsd: false}`.
[Run Stream ➤](https://ide.bitquery.io/FourMeme-DEX-tokens-1-second-price-stream-with-OHLC)
> Note: We include `Volume: { Usd: { gt: 5 } }` to further remove extreme outliers; the stream already pre-filters outliers—this is an additional check.
```graphql
subscription {
Trading {
Pairs(
where: {Interval: {Time: {Duration: {eq: 1}}}, Price: {IsQuotedInUsd: true}, Market: {Network: {is: "Binance Smart Chain"}, Address: {is: "0x5c952063c7fc8610ffdb798152d69f0b9550762b"}}, Volume: {Usd: {gt: 5}}}
) {
Market {
Protocol
Program
Network
Name
Address
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Close
High
Low
Open
}
IsQuotedInUsd
}
Currency {
Symbol
Name
Id
}
QuoteCurrency{
Name
Symbol
Id
}
Token{
Name
Symbol
Address
Id
NetworkBid
}
QuoteToken{
Name
Symbol
Id
Address
NetworkBid
}
}
}
}
```
---
## Real-Time Wallet Balance Tracker
URL: https://docs.bitquery.io/docs/usecases/real-time-balance-tracker/overview/
Plan a live wallet balance tracker with Bitquery balance streams, Node.js processing, and a simple browser UI for updates.
# Overview
Building a real time balance tracker can be really helpful for the investigation teams to closely monitor the activities of a wallet. In this tutorial we will learn how to build a simple real time balance tracker using Javascript and NodeJS for the logical reasoning, using Bitquery's [Balance API](/docs/blockchain/Ethereum/balances/balance-api/), while using HTML, CSS and JavaScript to make the monitoring application more intuitive.
## Real Time Balance Calculation Logic
To calculate or monitor the real time balance of a wallet, we will need a GraphQL API that returns the current balance along with a stream that reports every balance change, using Bitquery's [Websocket Connection Implementation](/docs/subscriptions/examples/#implementation-exampleusing-websocket-using-javascript).
This is the simplified formula we will use in this example:
```
Current Balance = Balances query result, then PostBalance from each TransactionBalances stream event
```
If during the building of project you are stuck, you can refer to the following [Github Repository](https://github.com/Kshitij0O7/real-time-balance).
---
## Realtime Liquidity Drain Detector
URL: https://docs.bitquery.io/docs/usecases/realtime-liquidity-drain-detector/
Build Realtime Liquidity Drain Detector: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code.
# Realtime Liquidity Drain Detector
This guide demonstrates how to build a real-time DeFi security tool that monitors DEX pools to detect liquidity drains using Bitquery's Kafka streams. The tool provides instant alerts when significant liquidity drops are detected in DEX pools, helping protect against potential liquidity drains.
> **⚠️ Important: Proof of Concept**
>
> This tool is provided as a **proof of concept** for educational purposes. The detection logic may generate false positives and should be modified based on your requirements. Always verify alerts by checking on-chain transaction data before taking action.
GitHub Repository: [realtime-liquidity-drain-detector](https://github.com/Akshat-cs/realtime-liquidity-drain-detector)
## Overview
The Realtime Liquidity Drain Detector is a Python-based security tool that:
- Monitors DEX pools in real-time using Bitquery's Kafka streams
- Detects significant liquidity drops that may indicate malicious activity
- Provides a web dashboard for instant alerts and monitoring
- Configurable thresholds for warning and critical alerts
- Supports multiple DEX protocols including Uniswap V2, V3, and V4
## Prerequisites
1. **Python 3.8+** installed on your system
2. **Bitquery Kafka Credentials** - Contact sales via [Telegram](https://t.me/Bloxy_info) or fill out the [form](https://bitquery.io/forms/api) for Kafka access
3. **Bitquery API Token** - Get your API token [here](/docs/authorization/how-to-generate/)
4. Basic understanding of Kafka streams and Python
> **Note:** IDE credentials will not work with Kafka Streams. You need separate Kafka credentials for this tool.
## Installation
1. Clone the repository:
```bash
git clone https://github.com/Akshat-cs/realtime-liquidity-drain-detector
cd realtime-liquidity-drain-detector
```
2. Install the required dependencies:
```bash
pip install -r requirements.txt
```
3. Configure your credentials:
- Copy `config.sample.py` to `config.py`:
```bash
cp config.sample.py config.py
```
- Edit `config.py` with your Kafka credentials:
```python
username = "your_kafka_username"
password = "your_kafka_password"
```
## Configuration
> **Note:** The default configuration values are starting points for a proof of concept. You should **tune these parameters** based on your specific requirements and testing to minimize false positives and optimize detection accuracy for your use case.
Edit thresholds in `detection_config.py` to customize alert sensitivity:
```python
class DetectionConfig:
# Liquidity drop percentage thresholds
LIQUIDITY_DROP_WARNING = 20.0 # 20% drop = warning level alert
LIQUIDITY_DROP_CRITICAL = 40.0 # 40% drop = critical level alert
# Time windows
BASELINE_WINDOW_HOURS = 24 # Hours of data to build baseline
LOOKBACK_WINDOW_MINUTES = 30 # Minutes to check for recovery (false positive filter)
RAPID_DRAIN_WINDOW_MINUTES = 5 # Window for detecting sudden drains
# Max trade size decrease thresholds
MAX_AMOUNT_DECREASE_WARNING = 30.0 # 30% decrease = warning
MAX_AMOUNT_DECREASE_CRITICAL = 50.0 # 50% decrease = critical
# Alert management
ALERT_COOLDOWN_MINUTES = 10 # Minutes between alerts for same pool
RECOVERY_CHECK_ENABLED = True # Check if liquidity recovers quickly (false positive filter)
# False positive prevention
DROP_CONFIRMATION_COUNT = 2 # Number of consecutive measurements showing drop required before alerting
ENABLE_MAX_AMOUNT_DECREASE_ALERTS = False # Set to False to disable max_amount_decrease alerts
# Pool size filter
MIN_LIQUIDITY_TOKENS = 1000 # Minimum liquidity to monitor (filters out very small pools)
# Baseline reliability
MIN_MEASUREMENTS_FOR_BASELINE = 10 # Need at least 10 measurements before alerting
MIN_TIME_FOR_BASELINE_MINUTES = 5 # Need at least 5 minutes of data before alerting
```
## Running the Tool
### Start the API Server (Frontend Dashboard)
The API server provides a web dashboard for viewing alerts:
```bash
python api_server.py
```
Access the dashboard at: `http://localhost:5001`
### Start the Detector
The detector monitors Kafka streams and sends alerts to the API server:
```bash
python liquidity_drain_detector.py
```
The detector will automatically send alerts to the API server when liquidity drains are detected.
## How It Works
### 1. Real-Time Monitoring
The tool connects to Bitquery's Kafka streams for Ethereum DEX pools using the topic:
- **`eth.dexpools.proto`**
The Kafka consumer is configured with:
```python
conf = {
'bootstrap.servers': 'rpk0.bitquery.io:9092,rpk1.bitquery.io:9092,rpk2.bitquery.io:9092',
'group.id': f'{username}-liquidity-drain-{group_id_suffix}',
'session.timeout.ms': 30000,
'security.protocol': 'SASL_PLAINTEXT',
'ssl.endpoint.identification.algorithm': 'none',
'sasl.mechanisms': 'SCRAM-SHA-512',
'sasl.username': config.username,
'sasl.password': config.password,
'auto.offset.reset': 'latest',
}
```
Kafka streams provide:
- Lower latency due to shorter data pipeline
- Better reliability with persistent connections
- Ability to read from latest offset without gaps
- Better scalability with multiple consumers
For detailed information on Kafka streams, refer to the [Kafka Streaming Concepts documentation](/docs/streams/kafka-streaming-concepts/).
### 2. Data Structures
The tool uses three main data structures:
#### PoolState
Tracks the current state of a liquidity pool:
```python
@dataclass
class PoolState:
pool_id: str
pool_address: str
currency_a: str
currency_b: str
currency_a_symbol: str
currency_b_symbol: str
dex_protocol: str
amount_a: float = 0.0 # Human-readable liquidity for currency A
amount_b: float = 0.0 # Human-readable liquidity for currency B
slippage_at_bps_a_to_b: Dict[int, Dict] # Slippage data for A->B swaps
slippage_at_bps_b_to_a: Dict[int, Dict] # Slippage data for B->A swaps
price_a_to_b: float = 0.0
price_b_to_a: float = 0.0
last_updated: datetime
```
#### PoolHistory
Maintains historical data for baseline calculation and drain detection:
```python
@dataclass
class PoolHistory:
pool_id: str
amount_a_history: deque # Historical liquidity for currency A
amount_b_history: deque # Historical liquidity for currency B
max_amount_history_a_to_b_100bp: deque # Max trade size history (A->B at 100bp)
max_amount_history_b_to_a_100bp: deque # Max trade size history (B->A at 100bp)
baseline_amount_a: Optional[float] # Baseline liquidity for currency A
baseline_amount_b: Optional[float] # Baseline liquidity for currency B
baseline_max_amount_a_to_b_100bp: Optional[float]
baseline_max_amount_b_to_a_100bp: Optional[float]
recent_drop_measurements_a: deque # Track recent drops for confirmation
recent_drop_measurements_b: deque
```
### 3. Liquidity Drain Detection
The detector processes pool events from Kafka and performs three types of checks:
#### A. Liquidity Drop Detection
Tracks each currency separately and detects drops from baseline:
```python
def _check_liquidity_drop(self, state: PoolState, history: PoolHistory,
current_amount_a: float, current_amount_b: float,
current_time: datetime) -> List[DrainAlert]:
# Calculate drop percentage for each currency
drop_a, drop_b = history.amount_drop_percent(current_amount_a, current_amount_b)
# Pre-alert confirmation: Require multiple consecutive measurements
if len(history.recent_drop_measurements_a) >= DROP_CONFIRMATION_COUNT:
if drop_a >= LIQUIDITY_DROP_CRITICAL:
severity = 'critical'
elif drop_a >= LIQUIDITY_DROP_WARNING:
severity = 'warning'
# Generate alert...
```
**Key Features:**
- Tracks each currency (A and B) separately
- Requires `DROP_CONFIRMATION_COUNT` consecutive measurements showing drop
- Uses mean of last 24 hours as baseline
- Checks for recovery to filter false positives
#### B. Max Amount Decrease Detection
Monitors when maximum trade sizes decrease significantly:
```python
def _check_max_amount_decrease(self, state: PoolState, history: PoolHistory,
max_amount_a_to_b: Optional[float],
max_amount_b_to_a: Optional[float],
current_time: datetime) -> List[DrainAlert]:
# Check both swap directions
decrease_a = history.max_amount_decrease_percent(max_amount_a_to_b, 'a_to_b')
decrease_b = history.max_amount_decrease_percent(max_amount_b_to_a, 'b_to_a')
# Use worst-case direction
if worst_decrease >= 50.0:
severity = 'critical'
elif worst_decrease >= 30.0:
severity = 'warning'
```
**Note:** This alert type is disabled by default (`ENABLE_MAX_AMOUNT_DECREASE_ALERTS = False`) as it can cause false positives.
#### C. Rapid Drain Detection
Detects sudden drops within a short time window:
```python
def _check_rapid_drain(self, state: PoolState, history: PoolHistory,
current_amount_a: float, current_amount_b: float,
current_time: datetime) -> List[DrainAlert]:
# Check if liquidity dropped rapidly within RAPID_DRAIN_WINDOW_MINUTES
if history.is_rapid_drain(current_amount_a, current_amount_b, current_time):
# Generate critical alert...
```
### 4. Baseline Calculation
The baseline is calculated using the mean of measurements from the last 24 hours:
```python
def update_baseline(self, current_time: datetime):
cutoff_time = current_time - timedelta(hours=BASELINE_WINDOW_HOURS)
# Baseline for currency A (mean - better for gradual drain detection)
recent_amount_a = [amt for ts, amt in self.amount_a_history
if ts >= cutoff_time and amt > 0]
if recent_amount_a:
self.baseline_amount_a = sum(recent_amount_a) / len(recent_amount_a)
```
**Baseline Requirements:**
- Minimum 10 measurements (`MIN_MEASUREMENTS_FOR_BASELINE`)
- Minimum 5 minutes of data (`MIN_TIME_FOR_BASELINE_MINUTES`)
- Uses mean (not max) for better sensitivity to gradual drains
### 5. Alert Generation
When a liquidity drain is detected, the tool generates a comprehensive alert including:
- **Alert Type**: `liquidity_drop`, `max_amount_decrease`, or `rapid_drain`
- **Severity**: Warning or Critical
- **Pool Information**: Pool ID, address, token pair, DEX protocol
- **Transaction Details**: Transaction hash and timestamp
- **Liquidity Metrics**: Current vs baseline liquidity for both tokens
- **Slippage Data**: Max trade sizes at different slippage levels (10bp, 50bp, 100bp, 200bp, 500bp, 1000bp)
- **Drop Percentages**: Detailed breakdown of liquidity drops
## Sample Alert Output
```
================================================================================
🚨 LIQUIDITY DRAIN ALERT - CRITICAL
================================================================================
Type: liquidity_drop
Pool ID: 0x10bd2f65f40bc8b7ddb6f104c603d022cd8a0ddf_0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2_0xdd9f7920b7c77efa8d1c19e3a7c1151f985f75a6
Pool Address: 0x10bd2f65f40bc8b7ddb6f104c603d022cd8a0ddf
Pair: WETH/ASTRE
DEX: uniswap_v2
Transaction Hash: 0x73470ed71e7b251d0e94078559d3c9005dc14187f55b61ad9435e99feb2341ea
Time: 2026-01-15 13:17:25 UTC
ASTRE dropped 90.4% from baseline
Current State:
Currency A: WETH (0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2)
- Current Liquidity: 0.000000 WETH
- Baseline Liquidity: 1.300800 WETH
- Drop: +100.0%
Currency B: ASTRE (0xdd9f7920b7c77efa8d1c19e3a7c1151f985f75a6)
- Current Liquidity: 15,357,735,936.000000 ASTRE
- Baseline Liquidity: 159,272,254,854.947357 ASTRE
- Drop: +90.4%
A->B MaxAmountIn (all slippage levels):
(10bp): Baseline: 0.000654 WETH, Drop: +100.0%
(50bp): Baseline: 0.003282 WETH, Drop: +100.0%
(100bp): Baseline: 0.006592 WETH, Drop: +100.0%
(200bp): Baseline: 0.013285 WETH, Drop: +100.0%
(500bp): Baseline: 0.034013 WETH, Drop: +100.0%
(1000bp): Baseline: 0.070822 WETH, Drop: +100.0%
B->A MaxAmountIn (all slippage levels):
(10bp): Baseline: 80,055,434.984426 ASTRE, Drop: +90.4%
(50bp): Baseline: 399,877,993.949424 ASTRE, Drop: +90.4%
(100bp): Baseline: 799,081,216.317845 ASTRE, Drop: +90.4%
(200bp): Baseline: 1,594,216,258.495066 ASTRE, Drop: +90.4%
(500bp): Baseline: 3,958,883,536.978619 ASTRE, Drop: +90.4%
(1000bp): Baseline: 7,824,577,583.154605 ASTRE, Drop: +90.4%
History Stats:
- Liquidity A measurements: 19
- Liquidity B measurements: 19
- MaxAmountIn measurements: 19
- Baseline window: Last 24 hours
⚠️ VALIDATION NOTES:
- For Uniswap V4: Liquidity amounts are AGGREGATED across all pools
- MaxAmountIn is POOL-SPECIFIC (drops reflect this specific pool's drain)
- Baseline uses last 24 hours of data
- Check transaction hash on Etherscan to verify on-chain events
================================================================================
```
## Project Structure
The project consists of three main files:
1. **`liquidity_drain_detector.py`** (1052 lines): Main detector logic
- Data structures (`PoolState`, `PoolHistory`, `DrainAlert`)
- Detection engine (`LiquidityDrainDetector` class)
- Kafka consumer integration
- Alert formatting and API communication
2. **`detection_config.py`** (55 lines): Configuration class
- All detection thresholds and parameters
- Time windows and filtering options
- Baseline requirements
3. **`api_server.py`** (345 lines): Flask API server
- REST API endpoints for alerts
- Frontend dashboard serving
- Alert storage and filtering
- Configuration management
## Code Walkthrough
### Main Execution Flow
The main function sets up the Kafka consumer and processes messages:
```python
def main():
# Setup logging
logging.basicConfig(level=logging.INFO)
# Initialize detector
detector = LiquidityDrainDetector()
# Configure Kafka consumer
conf = {
'bootstrap.servers': 'rpk0.bitquery.io:9092,rpk1.bitquery.io:9092,rpk2.bitquery.io:9092',
'group.id': f'{config.username}-liquidity-drain-{group_id_suffix}',
'security.protocol': 'SASL_PLAINTEXT',
'sasl.mechanisms': 'SCRAM-SHA-512',
'sasl.username': config.username,
'sasl.password': config.password,
'auto.offset.reset': 'latest',
}
consumer = Consumer(conf)
consumer.subscribe(['eth.dexpools.proto'])
# Main processing loop
while True:
msg = consumer.poll(timeout=1.0)
if msg is None:
continue
# Parse protobuf message
dex_pool_block = dex_pool_block_message_pb2.DexPoolBlockMessage()
dex_pool_block.ParseFromString(msg.value())
# Process each pool event
for pool_event in dex_pool_block.PoolEvents:
alerts = detector.process_pool_update(pool_event)
# Handle alerts
for alert in alerts:
print(format_alert(alert, state, history))
send_alert_to_api(alert, api_url, logger)
```
### Processing Pool Updates
The `process_pool_update` method handles each pool event:
```python
def process_pool_update(self, dex_pool_event) -> List[DrainAlert]:
# Extract pool information
pool = dex_pool_event.Pool
liquidity = dex_pool_event.Liquidity
price_table = dex_pool_event.PoolPriceTable
# For Uniswap V4, use PoolId; for others, use composite key
if protocol_name == 'uniswap_v4':
pool_id = convert_bytes_to_hex(pool.PoolId).lower()
else:
pool_id = f"{pool_address}_{currency_a}_{currency_b}"
# Update pool state with current liquidity and slippage data
state.amount_a = liquidity.AmountCurrencyA # Already human-readable
state.amount_b = liquidity.AmountCurrencyB # Already human-readable
# Process slippage data at multiple levels (10bp, 50bp, 100bp, etc.)
for price_info in price_table.AtoBPrices:
bps = price_info.SlippageBasisPoints
max_in = price_info.MaxAmountIn # Already human-readable
state.slippage_at_bps_a_to_b[bps] = {
'max_amount_in': max_in,
'min_amount_out': price_info.MinAmountOut,
'price': price_info.Price
}
# Add measurement to history
history.add_measurement(amount_a_human, amount_b_human,
max_amount_a_to_b_100bp, max_amount_b_to_a_100bp,
max_amounts_a_to_b_all, max_amounts_b_to_a_all,
current_time)
# Run detection checks if baseline is sufficient
if history.has_sufficient_baseline(current_time):
alerts.extend(self._check_liquidity_drop(...))
alerts.extend(self._check_max_amount_decrease(...))
alerts.extend(self._check_rapid_drain(...))
```
### API Server Endpoints
The Flask API server provides several endpoints for the web dashboard:
#### Get Alerts
```http
GET /api/alerts?pool=| Sr. No. | Timestamp | DEX Name | Price | DEX Address |
|---|---|---|---|---|
| {index + 1 + currentPage * itemsPerPage} | {trade.Block.Time} | {trade.Trade.Dex.ProtocolFamily} | {trade.Trade.Buy.PriceInUSD} | {trade.Trade.Dex.ProgramAddress} |